*
* Copyright (C) 2010 Marek Polacek <mmpolacek@gmail.com>
*
- * Licensed under GPLv2, see file License in this tarball for details.
+ * Licensed under GPLv2, see file LICENSE in this source tree.
*/
-
-//applet:IF_MPSTAT(APPLET(mpstat, _BB_DIR_BIN, _BB_SUID_DROP))
-
-//kbuild:lib-$(CONFIG_MPSTAT) += mpstat.o
-
//config:config MPSTAT
-//config: bool "mpstat"
+//config: bool "mpstat (10 kb)"
//config: default y
//config: help
-//config: Per-processor statistics
+//config: Per-processor statistics
+
+//applet:IF_MPSTAT(APPLET(mpstat, BB_DIR_BIN, BB_SUID_DROP))
+/* shouldn't be noexec: "mpstat INTERVAL" runs indefinitely */
+
+//kbuild:lib-$(CONFIG_MPSTAT) += mpstat.o
#include "libbb.h"
-#include <sys/utsname.h> /* struct utsname */
+#include <sys/utsname.h> /* struct utsname */
//#define debug(fmt, ...) fprintf(stderr, fmt, ## __VA_ARGS__)
#define debug(fmt, ...) ((void)0)
/* Maximum number of interrupts */
#define NR_IRQS 256
#define NR_IRQCPU_PREALLOC 3
-#define MAX_IRQ_LEN 16
+#define MAX_IRQNAME_LEN 16
#define MAX_PF_NAME 512
+/* sysstat 9.0.6 uses width 8, but newer code which also prints /proc/softirqs
+ * data needs more: "interrupts" in /proc/softirqs have longer names,
+ * most are up to 8 chars, one (BLOCK_IOPOLL) is even longer.
+ * We are printing headers in the " IRQNAME/s" form, experimentally
+ * anything smaller than 10 chars looks ugly for /proc/softirqs stats.
+ */
+#define INTRATE_SCRWIDTH 10
+#define INTRATE_SCRWIDTH_STR "10"
/* System files */
-#define SYSFS_DEVCPU "/sys/devices/system/cpu"
#define PROCFS_STAT "/proc/stat"
#define PROCFS_INTERRUPTS "/proc/interrupts"
#define PROCFS_SOFTIRQS "/proc/softirqs"
struct stats_irqcpu {
- unsigned interrupt;
- char irq_name[MAX_IRQ_LEN];
+ unsigned interrupts;
+ char irq_name[MAX_IRQNAME_LEN];
};
struct stats_cpu {
unsigned hz;
unsigned cpu_bitmap_len;
smallint p_option;
- smallint header_done;
- smallint avg_header_done;
+ // 9.0.6 does not do it. Try "mpstat -A 1 2" - headers are repeated!
+ //smallint header_done;
+ //smallint avg_header_done;
unsigned char *cpu_bitmap; /* Bit 0: global, bit 1: 1st proc... */
data_t global_uptime[3];
data_t per_cpu_uptime[3];
};
-/* Does str start with "cpu"? */
-static int starts_with_cpu(const char *str)
-{
- return !((str[0] - 'c') | (str[1] - 'p') | (str[2] - 'u'));
-}
-
/* Is option on? */
static ALWAYS_INLINE int display_opt(int opt)
{
static double hz_value(data_t prev, data_t curr, data_t itv)
{
+ //bb_error_msg("curr:%lld prev:%lld G.hz:%u", curr, prev, G.hz);
return ((double)overflow_safe_sub(prev, curr)) / itv * G.hz;
}
/* Print header */
printf("\n%-11s CPU", prev_str);
- for (j = 0; j < total_irqs; j++) {
- p0 = &per_cpu_stats[current][j];
- if (p0->irq_name[0] != '\0')
- printf(" %8s/s", p0->irq_name);
+ {
+ /* A bit complex code to "buy back" space if one header is too wide.
+ * Here's how it looks like. BLOCK_IOPOLL eats too much space,
+ * and latter headers use smaller width to compensate:
+ * ...BLOCK/s BLOCK_IOPOLL/s TASKLET/s SCHED/s HRTIMER/s RCU/s
+ * ... 2.32 0.00 0.01 17.58 0.14 141.96
+ */
+ int expected_len = 0;
+ int printed_len = 0;
+ for (j = 0; j < total_irqs; j++) {
+ p0 = &per_cpu_stats[current][j];
+ if (p0->irq_name[0] != '\0') {
+ int n = (INTRATE_SCRWIDTH-3) - (printed_len - expected_len);
+ printed_len += printf(" %*s/s", n > 0 ? n : 0, skip_whitespace(p0->irq_name));
+ expected_len += INTRATE_SCRWIDTH;
+ }
+ }
}
bb_putchar('\n');
struct stats_irqcpu *p, *q;
p = &per_cpu_stats[current][(cpu - 1) * total_irqs + j];
q = &per_cpu_stats[prev][(cpu - 1) * total_irqs + offset];
- printf(" %10.2f",
- (double)(p->interrupt - q->interrupt) / itv * G.hz);
+ printf("%"INTRATE_SCRWIDTH_STR".2f",
+ (double)(p->interrupts - q->interrupts) / itv * G.hz);
} else {
printf(" N/A");
}
/* Print CPU stats */
if (display_opt(D_CPU)) {
- /* This is done exactly once */
- if (!G.header_done) {
+ ///* This is done exactly once */
+ //if (!G.header_done) {
printf("\n%-11s CPU %%usr %%nice %%sys %%iowait %%irq %%soft %%steal %%guest %%idle\n",
prev_str
);
- G.header_done = 1;
- }
+ // G.header_done = 1;
+ //}
for (cpu = 0; cpu <= G.cpu_nr; cpu++) {
data_t per_cpu_itv;
/* Print total number of IRQs per CPU */
if (display_opt(D_IRQ_SUM)) {
- /* Print average header, this is done exactly once */
- if (!G.avg_header_done) {
+ ///* Print average header, this is done exactly once */
+ //if (!G.avg_header_done) {
printf("\n%-11s CPU intr/s\n", prev_str);
- G.avg_header_done = 1;
- }
+ // G.avg_header_done = 1;
+ //}
for (cpu = 0; cpu <= G.cpu_nr; cpu++) {
data_t per_cpu_itv;
continue;
}
}
+ //bb_error_msg("G.st_irq[%u][%u].irq_nr:%lld - G.st_irq[%u][%u].irq_nr:%lld",
+ // current, cpu, G.st_irq[prev][cpu].irq_nr, prev, cpu, G.st_irq[current][cpu].irq_nr);
printf(" %9.2f\n", hz_value(G.st_irq[prev][cpu].irq_nr, G.st_irq[current][cpu].irq_nr, per_cpu_itv));
}
}
FILE *fp;
char buf[1024];
- fp = fopen_for_read(PROCFS_STAT);
- if (!fp)
- return;
+ fp = xfopen_for_read(PROCFS_STAT);
while (fgets(buf, sizeof(buf), fp)) {
- if (strncmp(buf, "intr ", 5) == 0)
+ //bb_error_msg("/proc/stat:'%s'", buf);
+ if (is_prefixed_with(buf, "intr ")) {
/* Read total number of IRQs since system boot */
sscanf(buf + 5, "%"FMT_DATA"u", &irq->irq_nr);
+ }
}
fclose(fp);
unsigned irq;
int cpu_index[G.cpu_nr];
int iindex;
- int len, digit;
- for (cpu = 1; cpu <= G.cpu_nr; cpu++) {
- irq_i = &G.st_irq[current][cpu];
- irq_i->irq_nr = 0;
- }
+// Moved to caller.
+// Otherwise reading of /proc/softirqs
+// was resetting counts to 0 after we painstakingly collected them from
+// /proc/interrupts. Which resulted in:
+// 01:32:47 PM CPU intr/s
+// 01:32:47 PM all 591.47
+// 01:32:47 PM 0 0.00 <= ???
+// 01:32:47 PM 1 0.00 <= ???
+// for (cpu = 1; cpu <= G.cpu_nr; cpu++) {
+// G.st_irq[current][cpu].irq_nr = 0;
+// //bb_error_msg("G.st_irq[%u][%u].irq_nr=0", current, cpu);
+// }
fp = fopen_for_read(fname);
if (!fp)
while (fgets(buf, buflen, fp)
&& irq < irqs_per_cpu
) {
+ int len;
+ char last_char;
char *cp;
- /* Skip over "<irq>:" */
+
+ /* Skip over "IRQNAME:" */
cp = strchr(buf, ':');
if (!cp)
continue;
+ last_char = cp[-1];
ic = &per_cpu_stats[current][irq];
len = cp - buf;
- if (len > sizeof(ic->irq_name)) {
- len = sizeof(ic->irq_name);
+ if (len >= sizeof(ic->irq_name)) {
+ len = sizeof(ic->irq_name) - 1;
}
- safe_strncpy(ic->irq_name, buf, len);
- digit = isdigit(buf[len - 1]);
+ safe_strncpy(ic->irq_name, buf, len + 1);
+ //bb_error_msg("%s: irq%d:'%s' buf:'%s'", fname, irq, ic->irq_name, buf);
cp++;
for (cpu = 0; cpu < iindex; cpu++) {
char *next;
ic = &per_cpu_stats[current][cpu_index[cpu] * irqs_per_cpu + irq];
irq_i = &G.st_irq[current][cpu_index[cpu] + 1];
- ic->interrupt = strtoul(cp, &next, 10);
- if (digit) {
- /* Do not count non-numerical IRQs */
- irq_i->irq_nr += ic->interrupt;
+ ic->interrupts = strtoul(cp, &next, 10);
+ /* Count only numerical IRQs */
+ if (isdigit(last_char)) {
+ irq_i->irq_nr += ic->interrupts;
+ //bb_error_msg("G.st_irq[%u][%u].irq_nr + %u = %lld",
+ // current, cpu_index[cpu] + 1, ic->interrupts, irq_i->irq_nr);
}
cp = next;
}
while (irq < irqs_per_cpu) {
/* Number of interrupts per CPU has changed */
ic = &per_cpu_stats[current][irq];
- ic->irq_name[0] = '\0'; /* False interrupt */
+ ic->irq_name[0] = '\0'; /* False interrupt */
irq++;
}
}
char buf[sizeof(long)*3 * 2 + 4]; /* enough for long.long */
unsigned long uptime_sec, decimal;
- fp = fopen_for_read(PROCFS_UPTIME);
- if (!fp)
- return;
+ fp = xfopen_for_read(PROCFS_UPTIME);
if (fgets(buf, sizeof(buf), fp)) {
if (sscanf(buf, "%lu.%lu", &uptime_sec, &decimal) == 2) {
*uptime = (data_t)uptime_sec * G.hz + decimal * G.hz / 100;
if (display_opt(D_IRQ_SUM))
get_irqs_from_stat(G.st_irq[current]);
- if (display_opt(D_IRQ_SUM | D_IRQ_CPU))
+ if (display_opt(D_IRQ_SUM | D_IRQ_CPU)) {
+ int cpu;
+ for (cpu = 1; cpu <= G.cpu_nr; cpu++) {
+ G.st_irq[current][cpu].irq_nr = 0;
+ }
+ /* accumulates .irq_nr */
get_irqs_from_interrupts(PROCFS_INTERRUPTS, G.st_irqcpu,
G.irqcpu_nr, current);
+ }
if (display_opt(D_SOFTIRQS))
get_irqs_from_interrupts(PROCFS_SOFTIRQS,
/* Initialization */
-/* Get number of clock ticks per sec */
-static ALWAYS_INLINE unsigned get_hz(void)
-{
- return sysconf(_SC_CLK_TCK);
-}
-
static void alloc_struct(int cpus)
{
int i;
uts.sysname, uts.release, uts.nodename, cur_date, uts.machine, G.cpu_nr);
}
-/*
- * Get number of processors in /sys
- */
-static int get_sys_cpu_nr(void)
-{
- DIR *dir;
- struct dirent *d;
- struct stat buf;
- char line[MAX_PF_NAME];
- int proc_nr = 0;
-
- dir = opendir(SYSFS_DEVCPU);
- if (!dir)
- return 0; /* /sys not mounted */
-
- /* Get current file entry */
- while ((d = readdir(dir)) != NULL) {
- if (starts_with_cpu(d->d_name) && isdigit(d->d_name[3])) {
- snprintf(line, MAX_PF_NAME, "%s/%s", SYSFS_DEVCPU,
- d->d_name);
- line[MAX_PF_NAME - 1] = '\0';
- /* Get information about file */
- if (stat(line, &buf) < 0)
- continue;
- /* If found 'cpuN', we have one more processor */
- if (S_ISDIR(buf.st_mode))
- proc_nr++;
- }
- }
-
- closedir(dir);
- return proc_nr;
-}
-
-/*
- * Get number of processors in /proc/stat
- * Return value '0' means one CPU and non SMP kernel.
- * Otherwise N means N processor(s) and SMP kernel.
- */
-static int get_proc_cpu_nr(void)
-{
- FILE *fp;
- char line[256];
- int proc_nr = -1;
-
- fp = xfopen_for_read(PROCFS_STAT);
- while (fgets(line, sizeof(line), fp)) {
- if (!starts_with_cpu(line)) {
- if (proc_nr >= 0)
- break; /* we are past "cpuN..." lines */
- continue;
- }
- if (line[3] != ' ') { /* "cpuN" */
- int num_proc;
- if (sscanf(line + 3, "%u", &num_proc) == 1
- && num_proc > proc_nr
- ) {
- proc_nr = num_proc;
- }
- }
- }
-
- fclose(fp);
- return proc_nr + 1;
-}
-
-static int get_cpu_nr(void)
-{
- int n;
-
- /* Try to use /sys, if possible */
- n = get_sys_cpu_nr();
- if (n == 0)
- /* Otherwise use /proc/stat */
- n = get_proc_cpu_nr();
-
- return n;
-}
-
/*
* Get number of interrupts available per processor
*/
unsigned irq;
fp = fopen_for_read(f);
- if (!fp) /* No interrupts file */
+ if (!fp) /* No interrupts file */
return 0;
linelen = INTERRUPTS_LINE + 16 * G.cpu_nr;
//usage: "[-A] [-I SUM|CPU|ALL|SCPU] [-u] [-P num|ALL] [INTERVAL [COUNT]]"
//usage:#define mpstat_full_usage "\n\n"
//usage: "Per-processor statistics\n"
-//usage: "\nOptions:"
//usage: "\n -A Same as -I ALL -u -P ALL"
//usage: "\n -I SUM|CPU|ALL|SCPU Report interrupt statistics"
//usage: "\n -P num|ALL Processor to monitor"
//usage: "\n -u Report CPU utilization"
int mpstat_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
-int mpstat_main(int UNUSED_PARAM argc, char **argv)
+int mpstat_main(int argc UNUSED_PARAM, char **argv)
{
char *opt_irq_fmt;
char *opt_set_cpu;
int i, opt;
enum {
- OPT_ALL = 1 << 0, /* -A */
- OPT_INTS = 1 << 1, /* -I */
- OPT_SETCPU = 1 << 2, /* -P */
- OPT_UTIL = 1 << 3, /* -u */
+ OPT_ALL = 1 << 0, /* -A */
+ OPT_INTS = 1 << 1, /* -I */
+ OPT_SETCPU = 1 << 2, /* -P */
+ OPT_UTIL = 1 << 3, /* -u */
};
/* Dont buffer data if redirected to a pipe */
G.interval = -1;
/* Get number of processors */
- G.cpu_nr = get_cpu_nr();
+ G.cpu_nr = get_cpu_count();
/* Get number of clock ticks per sec */
- G.hz = get_hz();
+ G.hz = bb_clk_tck();
/* Calculate number of interrupts per processor */
G.irqcpu_nr = get_irqcpu_nr(PROCFS_INTERRUPTS, NR_IRQS) + NR_IRQCPU_PREALLOC;
if (*argv) {
/* Get interval */
- G.interval = xatoi_u(*argv);
+ G.interval = xatoi_positive(*argv);
G.count = -1;
argv++;
if (*argv) {
/* Get count value */
if (G.interval == 0)
bb_show_usage();
- G.count = xatoi_u(*argv);
+ G.count = xatoi_positive(*argv);
//if (*++argv)
// bb_show_usage();
}
}
if (opt & OPT_INTS) {
- if (strcmp(opt_irq_fmt, "ALL") == 0)
- G.options |= D_IRQ_SUM + D_IRQ_CPU + D_SOFTIRQS;
- else if (strcmp(opt_irq_fmt, "CPU") == 0)
- G.options |= D_IRQ_CPU;
- else if (strcmp(opt_irq_fmt, "SUM") == 0)
- G.options |= D_IRQ_SUM;
- else if (strcmp(opt_irq_fmt, "SCPU") == 0)
- G.options |= D_SOFTIRQS;
- else
+ static const char v[] = {
+ D_IRQ_CPU, D_IRQ_SUM, D_SOFTIRQS,
+ D_IRQ_SUM + D_IRQ_CPU + D_SOFTIRQS
+ };
+ i = index_in_strings("CPU\0SUM\0SCPU\0ALL\0", opt_irq_fmt);
+ if (i == -1)
bb_show_usage();
+ G.options |= v[i];
}
if ((opt & OPT_UTIL) /* -u? */
memset(G.cpu_bitmap, 0xff, G.cpu_bitmap_len);
} else {
/* Get CPU number */
- unsigned n = xatoi_u(t);
+ unsigned n = xatoi_positive(t);
if (n >= G.cpu_nr)
bb_error_msg_and_die("not that many processors");
n++;