From e3fe484aaeaa2e96c8b5b84176d518654b7577ac Mon Sep 17 00:00:00 2001 From: Thomas Huehn Date: Sat, 8 Oct 2022 15:11:17 +0200 Subject: [PATCH] cpusage: add new package This PR adds the new package "cpusage" to OpenWrt packages. Cpusage is a small utility that prints cpu usage per second as output. Signed-off-by: Thomas Huehn --- utils/cpusage/Makefile | 39 ++++ utils/cpusage/src/cpusage.c | 343 ++++++++++++++++++++++++++++++++++++ 2 files changed, 382 insertions(+) create mode 100644 utils/cpusage/Makefile create mode 100644 utils/cpusage/src/cpusage.c diff --git a/utils/cpusage/Makefile b/utils/cpusage/Makefile new file mode 100644 index 0000000000..f1f63ab3de --- /dev/null +++ b/utils/cpusage/Makefile @@ -0,0 +1,39 @@ +# SPDX-Identifier-License: GPL-2.0-only +# +# Copyright (C) 2005 Fabian Schneider, +# 2010 Florian Sesser, +# 2022 Thomas Hühn + +include $(TOPDIR)/rules.mk + +PKG_NAME:=cpusage +PKG_VERSION:=$(AUTORELEASE) +PKG_MAINTAINER:=Thomas Hühn + +include $(INCLUDE_DIR)/package.mk + +define Package/cpusage + SECTION:=utils + CATEGORY:=Utilities + TITLE:=Outputs CPU usage statistics once per second +endef + +define Package/cpusage/description + CPUsage outputs CPU usage statistics once per second. + Optionally writes CSV output (see '-o' option). + Originally written by Fabian Schneider (TUM, TUB) in 2005. + Timestamp and CSV-compliance by Florian Sesser (TUM), 2010. + Refreshed by Thomas Hühn in 2022. +endef + +define Build/Compile + $(TARGET_CC) $(TARGET_CFLAGS) \ + -o $(PKG_BUILD_DIR)/cpusage $(PKG_BUILD_DIR)/cpusage.c +endef + +define Package/cpusage/install + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) $(PKG_BUILD_DIR)/cpusage $(1)/usr/bin/ +endef + +$(eval $(call BuildPackage,cpusage)) diff --git a/utils/cpusage/src/cpusage.c b/utils/cpusage/src/cpusage.c new file mode 100644 index 0000000000..300fb62e21 --- /dev/null +++ b/utils/cpusage/src/cpusage.c @@ -0,0 +1,343 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define CPUSTATES 7 +#define IDLEI 3 +/* long names: + * user - nice - system - idle - iowait - irq - soft irq */ +char *cpustatenames[] = {"user", "nice", "system", "idle", + "iowait", "irq", "softirq", NULL}; + +#define LIMIT 95 + +static const char usage[] = + "\n usage: cpusage [ -hos ] [ -a | -l limit | -o ] [ -c CPU ]\n"; + +char *appname; + +static float cpu_perc[CPUSTATES]; +static float cpu_max[CPUSTATES]; +static float cpu_min[CPUSTATES]; + +int cpunum; /* -1 all, 0-n CPU/Core 0-n */ + +int output; + +int breakloop; + +/* returns 1-n yielding the number of CPU's/Cores */ +int getNumCPU() { + char buffer[32768]; + int fd, len, i; + char *test; + + fd = open("/proc/stat", O_RDONLY); + if (fd <= 0) + fprintf(stderr, "%s: cannot open /proc/stat \n", appname); + + len = read(fd, buffer, sizeof(buffer) - 1); + close(fd); + buffer[len] = '\0'; + + i = 0; + + test = strstr(buffer, "cpu"); + if (test != NULL) { + test += sizeof("cpu"); + test = strstr(test, "cpu"); + } + + while (test != NULL) { + test += sizeof("cpu"); + /* fprintf(stderr, "%s: DEBUG: %s\n", appname, test); */ + i++; + test = strstr(test, "cpu"); + } + return i; +} + +void getSysinfo(unsigned long *ptr, size_t size) { + char buffer[4096]; + char match[100]; + char *start; + int fd, len, j; + + for (j = 0; j < size; j++) + ptr[j] = 0; + + fd = open("/proc/stat", O_RDONLY); + if (fd <= 0) + fprintf(stderr, "%s: cannot open /proc/stat\n", appname); + + len = read(fd, buffer, sizeof(buffer) - 1); + close(fd); + buffer[len] = '\0'; + + strcpy(match, "cpu "); + start = buffer; + if (cpunum != -1) { + sprintf(match, "cpu%d ", cpunum); + start = strstr(buffer, match); + } + + strcat(match, "%ld %ld %ld %ld %ld %ld %ld"); + if (sscanf(start, match, &ptr[0], &ptr[1], &ptr[2], &ptr[3], &ptr[4], &ptr[5], + &ptr[6]) != 7) { + fprintf(stderr, "%s: wrong /proc/stat format\n", appname); + } +} + +long perc(int cpustates, long *cp_time, long *cp_old, long *cp_diff) { + + int i = 0; + long total = 0; + + for (i = 0; i < cpustates; i++) { + cp_diff[i] = cp_time[i] - cp_old[i]; + total += cp_diff[i]; + } + + for (i = 0; i < cpustates; i++) { + cpu_perc[i] = ((float)cp_diff[i] * 100.0 / total); + /* new max ? */ + if (cpu_perc[i] > cpu_max[i]) + cpu_max[i] = cpu_perc[i]; + /* new min ? */ + if (cpu_perc[i] < cpu_min[i]) + cpu_min[i] = cpu_perc[i]; + } + + return total; +} + +void print_perc(float *perc, const char *head) { + int i; + time_t Zeitstempel; + struct tm *now; + + /* human readable */ + if ((output == 0) && (head != "")) + printf("%s: ", head); + + /* machine readable */ + if ((output == 1) && (head != "")) + printf("%s;", head); + + /* timestamp */ + time(&Zeitstempel); + now = localtime(&Zeitstempel); + if (output == 0) + printf("timestamp: %04d-%02d-%02d %02d.%02d.%02d, ", now->tm_year + 1900, + now->tm_mon + 1, now->tm_mday, now->tm_hour, now->tm_min, + now->tm_sec); + else + printf("%04d-%02d-%02d;%02d:%02d:%02d;", now->tm_year + 1900, + now->tm_mon + 1, now->tm_mday, now->tm_hour, now->tm_min, + now->tm_sec); + + if (output == 0) + printf("%s: %5.1f%%, ", cpustatenames[0], perc[0]); + else + printf("%.1f", perc[0]); + + /* print out calculated information in percentages */ + for (i = 1; i < CPUSTATES; i++) { + if (output == 0) + printf("%s: %5.1f%%, ", cpustatenames[i], perc[i]); + else + printf(";%.1f", perc[i]); + } + printf("\n"); +} + +/* to catch Strg+C when looping */ +void loop_term_handler(int signum) { breakloop = 1; } + +int main(int argc, char **argv) { + + appname = argv[0]; + + int i, c, limit; + int runonce; /* run just once and exit */ + int avg; /* is avg measurement allready running */ + int avg_run; /* did we allready had an avg measurement */ + static long cp_time1[CPUSTATES]; + static long cp_time2[CPUSTATES]; + static long cp_avg_start[CPUSTATES]; + static long cp_avg_stop[CPUSTATES]; + static long cp_diff[CPUSTATES]; + + struct sigaction sigold, signew; + + long *old = cp_time2; + long *new = cp_time1; + + long total; + limit = LIMIT; + output = 0; /* 0: human readable; 1: machine readable */ + runonce = 0; /* 0: run continuesly; 1: run once */ + + cpunum = -1; /* -1: all CPUs/Cores, 0-n: special CPU/Core */ + + /* reading commandline options */ + while (1) { + c = getopt(argc, argv, "saohl:c:"); + + if (c == -1) { + break; + } + + switch (c) { + /*run once and exit */ + case 's': + runonce = 1; + break; + /* use avg from begin to end -> same as "-l 100" */ + case 'a': + limit = 100; + break; + case 'o': + output = 1; /* machine readable */ + // header for CSV output + printf("date;time;user;nice;system;idle;iowait;irq;softirq\n"); + break; + /* print usage */ + case 'h': + fprintf(stderr, "%s: %s", appname, usage); + exit(0); + break; + /* set limit */ + case 'l': + if (!(sscanf(optarg, "%d", &limit) == 1)) { + fprintf(stderr, "%s: option for -l should be integer (is %s)\n", + appname, optarg); + exit(1); + } + break; + /* select CPU/Core */ + case 'c': + if (!(sscanf(optarg, "%d", &cpunum) == 1)) { + fprintf(stderr, "%s: option for -c should be integer (is %s)\n", + appname, optarg); + exit(1); + } + break; + } + } + + if (cpunum != -1) { + int numcpu = getNumCPU(); + if (cpunum < numcpu) { + printf("-- Selected CPU %d\n", cpunum); + } else { + if (numcpu == 1) { + fprintf(stderr, "%s: CPU %d not available (found %d CPU: [0])\n", + appname, cpunum, numcpu); + } else { + fprintf(stderr, + "%s: CPU %d not available (found %d CPU's: [0]-[%d])\n ", + appname, cpunum, numcpu, numcpu - 1); + } + exit(1); + } + } + + breakloop = 0; + + for (i = 0; i < CPUSTATES; i++) { + cpu_max[i] = 0; + cpu_min[i] = 100; + } + + /* get information */ + getSysinfo((unsigned long *)new, CPUSTATES); + + /* catch Strg+C when capturing to call pcap_breakloop() */ + memset(&signew, 0, sizeof(signew)); + signew.sa_handler = loop_term_handler; + if (sigaction(SIGINT, &signew, &sigold) < 0) { + fprintf(stderr, "Could not set signal handler -> exiting"); + } + + avg = 0; + avg_run = 0; + + if (runonce) { + breakloop = 1; + } + + while (1) { + usleep(1000000); + + if (new == cp_time1) { + new = cp_time2; + old = cp_time1; + } else { + new = cp_time1; + old = cp_time2; + } + + /* get information again */ + getSysinfo((unsigned long *)new, CPUSTATES); + + /* convert cp_time counts to percentages */ + total = perc(CPUSTATES, new, old, cp_diff); + + /* check for avg measurement start */ + if (!avg_run && !avg && (cpu_perc[IDLEI] <= limit)) { + avg = 1; + for (i = 0; i < CPUSTATES; i++) + cp_avg_start[i] = new[i]; + } + + /* check for avg measurement stop */ + if (!avg_run && avg && (cpu_perc[IDLEI] > limit)) { + avg = 0; + for (i = 0; i < CPUSTATES; i++) + cp_avg_stop[i] = new[i]; + avg_run = 1; + } + + print_perc(cpu_perc, ""); + + if (breakloop) { + if (avg) { + avg = 0; + for (i = 0; i < CPUSTATES; i++) + cp_avg_stop[i] = new[i]; + } + break; + } + } + + /* Set default behaviour when loop is done */ + if (sigaction(SIGINT, &sigold, &signew) < 0) { + fprintf(stderr, "%s: Could not restore signal handler -> exiting", appname); + } + + if (!runonce && output == 0) { + // print avg only when not making a one-shot msg and + // when not writing CSV output + printf("---Summary----\n"); + + print_perc(cpu_min, "Min"); + + print_perc(cpu_max, "Max"); + + perc(CPUSTATES, cp_avg_start, cp_avg_stop, cp_diff); + + print_perc(cpu_perc, "Avg"); + } + + return 0; +} -- 2.30.2