diff options
author | Marek Polacek <mmpolacek@gmail.com> | 2010-07-21 10:29:07 +0200 |
---|---|---|
committer | Denys Vlasenko <vda.linux@googlemail.com> | 2010-07-21 10:29:07 +0200 |
commit | 23e8c08fa2e1faab335e7ff625272f57d1dde469 (patch) | |
tree | 9613ccce34eac66989c1bcea7b6c29f8bae862ed /procps | |
parent | ba085c6351b49d7c71f3635a0f4709e1927fdc51 (diff) | |
download | busybox-23e8c08fa2e1faab335e7ff625272f57d1dde469.tar.gz |
mpstat: new applet. ~5.5k
Signed-off-by: Marek Polacek <mmpolacek@gmail.com>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
Diffstat (limited to 'procps')
-rw-r--r-- | procps/mpstat.c | 1024 |
1 files changed, 1024 insertions, 0 deletions
diff --git a/procps/mpstat.c b/procps/mpstat.c new file mode 100644 index 000000000..0f633c713 --- /dev/null +++ b/procps/mpstat.c @@ -0,0 +1,1024 @@ +/* vi: set sw=4 ts=4: */ +/* + * Per-processor statistics, based on sysstat version 9.1.2 by Sebastien Godard + * + * Copyright (C) 2010 Marek Polacek <mmpolacek@gmail.com> + * + * Licensed under GPLv2, see file License in this tarball for details. + */ + +//applet:IF_MPSTAT(APPLET(mpstat, _BB_DIR_BIN, _BB_SUID_DROP)) + +//kbuild:lib-$(CONFIG_MPSTAT) += mpstat.o + +//config:config MPSTAT +//config: bool "mpstat" +//config: default y +//config: help +//config: Per-processor statistics + +#include "libbb.h" +#include <sys/utsname.h> /* struct utsname */ + +//#define debug(fmt, ...) fprintf(stderr, fmt, ## __VA_ARGS__) +#define debug(fmt, ...) ((void)0) + +/* Size of /proc/interrupts line, CPU data excluded */ +#define INTERRUPTS_LINE 64 +/* Maximum number of interrupts */ +#define NR_IRQS 256 +#define NR_IRQCPU_PREALLOC 3 +#define MAX_IRQ_LEN 16 +#define MAX_PF_NAME 512 + +/* System files */ +#define SYSFS_DEVCPU "/sys/devices/system/cpu" +#define PROCFS_STAT "/proc/stat" +#define PROCFS_INTERRUPTS "/proc/interrupts" +#define PROCFS_SOFTIRQS "/proc/softirqs" +#define PROCFS_UPTIME "/proc/uptime" + + +#if 1 +typedef unsigned long long data_t; +typedef long long idata_t; +#define FMT_DATA "ll" +#define DATA_MAX ULLONG_MAX +#else +typedef unsigned long data_t; +typedef long idata_t; +#define FMT_DATA "l" +#define DATA_MAX ULONG_MAX +#endif + + +struct stats_irqcpu { + unsigned interrupt; + char irq_name[MAX_IRQ_LEN]; +}; + +/* Structure for CPU statistics */ +struct stats_cpu { + data_t cpu_user; + data_t cpu_nice; + data_t cpu_system; + data_t cpu_idle; + data_t cpu_iowait; + data_t cpu_steal; + data_t cpu_irq; + data_t cpu_softirq; + data_t cpu_guest; +}; + +/* Struct for interrupts statistics */ +struct stats_irq { + data_t irq_nr; +}; + + +/* Globals. Try to sort by size. */ +struct globals { + int interval; + int count; + unsigned cpu_nr; /* Number of CPUs */ + unsigned irqcpu_nr; /* Number of interrupts per CPU */ + unsigned softirqcpu_nr; /* Number of soft interrupts per CPU */ + unsigned options; + unsigned hz; + unsigned cpu_bitmap_len; + smallint p_option; + 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]; + struct stats_cpu *st_cpu[3]; + struct stats_irq *st_irq[3]; + struct stats_irqcpu *st_irqcpu[3]; + struct stats_irqcpu *st_softirqcpu[3]; + struct tm timestamp[3]; +}; +#define G (*ptr_to_globals) +#define INIT_G() do { \ + SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ +} while (0) + +/* The selected interrupts statistics (bits in G.options) */ +enum { + D_CPU = 1 << 0, + D_IRQ_SUM = 1 << 1, + D_IRQ_CPU = 1 << 2, + D_SOFTIRQS = 1 << 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) +{ + return (opt & G.options); +} + +#if DATA_MAX > 0xffffffff +/* + * Handle overflow conditions properly for counters which can have + * less bits than data_t, depending on the kernel version. + */ +/* Surprisingly, on 32bit inlining is a size win */ +static ALWAYS_INLINE data_t overflow_safe_sub(data_t prev, data_t curr) +{ + data_t v = curr - prev; + + if ((idata_t)v < 0 /* curr < prev - counter overflow? */ + && prev <= 0xffffffff /* kernel uses 32bit value for the counter? */ + ) { + /* Add 33th bit set to 1 to curr, compensating for the overflow */ + /* double shift defeats "warning: left shift count >= width of type" */ + v += ((data_t)1 << 16) << 16; + } + return v; +} +#else +static ALWAYS_INLINE data_t overflow_safe_sub(data_t prev, data_t curr) +{ + return curr - prev; +} +#endif + +static double percent_value(data_t prev, data_t curr, data_t itv) +{ + return ((double)overflow_safe_sub(prev, curr)) / itv * 100; +} + +static double hz_value(data_t prev, data_t curr, data_t itv) +{ + return ((double)overflow_safe_sub(prev, curr)) / itv * G.hz; +} + +static ALWAYS_INLINE data_t jiffies_diff(data_t old, data_t new) +{ + data_t diff = new - old; + return (diff == 0) ? 1 : diff; +} + +static int is_cpu_in_bitmap(unsigned cpu) +{ + return G.cpu_bitmap[cpu >> 3] & (1 << (cpu & 7)); +} + +static void write_irqcpu_stats(struct stats_irqcpu *per_cpu_stats[], + int total_irqs, + data_t itv, + int prev, int current, + const char *prev_str, const char *current_str) +{ + int j; + int offset, cpu; + struct stats_irqcpu *p0, *q0; + + /* Check if number of IRQs has changed */ + if (G.interval != 0) { + for (j = 0; j <= total_irqs; j++) { + p0 = &per_cpu_stats[current][j]; + if (p0->irq_name[0] != '\0') { + q0 = &per_cpu_stats[prev][j]; + if (strcmp(p0->irq_name, q0->irq_name) != 0) { + /* Strings are different */ + break; + } + } + } + } + + /* 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); + } + bb_putchar('\n'); + + for (cpu = 1; cpu <= G.cpu_nr; cpu++) { + /* Check if we want stats about this CPU */ + if (!is_cpu_in_bitmap(cpu) && G.p_option) { + continue; + } + + printf("%-11s %4u", current_str, cpu - 1); + + for (j = 0; j < total_irqs; j++) { + /* IRQ field set only for proc 0 */ + p0 = &per_cpu_stats[current][j]; + + /* + * An empty string for irq name means that + * interrupt is no longer used. + */ + if (p0->irq_name[0] != '\0') { + offset = j; + q0 = &per_cpu_stats[prev][offset]; + + /* + * If we want stats for the time since boot + * we have p0->irq != q0->irq. + */ + if (strcmp(p0->irq_name, q0->irq_name) != 0 + && G.interval != 0 + ) { + if (j) { + offset = j - 1; + q0 = &per_cpu_stats[prev][offset]; + } + if (strcmp(p0->irq_name, q0->irq_name) != 0 + && (j + 1 < total_irqs) + ) { + offset = j + 1; + q0 = &per_cpu_stats[prev][offset]; + } + } + + if (strcmp(p0->irq_name, q0->irq_name) == 0 + || G.interval == 0 + ) { + 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); + } else { + printf(" N/A"); + } + } + } + bb_putchar('\n'); + } +} + +static data_t get_per_cpu_interval(const struct stats_cpu *scc, + const struct stats_cpu *scp) +{ + return ((scc->cpu_user + scc->cpu_nice + + scc->cpu_system + scc->cpu_iowait + + scc->cpu_idle + scc->cpu_steal + + scc->cpu_irq + scc->cpu_softirq) - + (scp->cpu_user + scp->cpu_nice + + scp->cpu_system + scp->cpu_iowait + + scp->cpu_idle + scp->cpu_steal + + scp->cpu_irq + scp->cpu_softirq)); +} + +static void print_stats_cpu_struct(const struct stats_cpu *p, + const struct stats_cpu *c, + data_t itv) +{ + printf(" %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f\n", + percent_value(p->cpu_user - p->cpu_guest, + /**/ c->cpu_user - c->cpu_guest, itv), + percent_value(p->cpu_nice , c->cpu_nice , itv), + percent_value(p->cpu_system , c->cpu_system , itv), + percent_value(p->cpu_iowait , c->cpu_iowait , itv), + percent_value(p->cpu_irq , c->cpu_irq , itv), + percent_value(p->cpu_softirq, c->cpu_softirq, itv), + percent_value(p->cpu_steal , c->cpu_steal , itv), + percent_value(p->cpu_guest , c->cpu_guest , itv), + percent_value(p->cpu_idle , c->cpu_idle , itv) + ); +} + +static void write_stats_core(int prev, int current, + const char *prev_str, const char *current_str) +{ + struct stats_cpu *scc, *scp; + data_t itv, global_itv; + int cpu; + + /* Compute time interval */ + itv = global_itv = jiffies_diff(G.global_uptime[prev], G.global_uptime[current]); + + /* Reduce interval to one CPU */ + if (G.cpu_nr > 1) + itv = jiffies_diff(G.per_cpu_uptime[prev], G.per_cpu_uptime[current]); + + /* Print CPU stats */ + if (display_opt(D_CPU)) { + + /* 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; + } + + for (cpu = 0; cpu <= G.cpu_nr; cpu++) { + data_t per_cpu_itv; + + /* Print stats about this particular CPU? */ + if (!is_cpu_in_bitmap(cpu)) + continue; + + scc = &G.st_cpu[current][cpu]; + scp = &G.st_cpu[prev][cpu]; + per_cpu_itv = global_itv; + + printf((cpu ? "%-11s %4u" : "%-11s all"), current_str, cpu - 1); + if (cpu) { + double idle; + /* + * If the CPU is offline, then it isn't in /proc/stat, + * so all values are 0. + * NB: Guest time is already included in user time. + */ + if ((scc->cpu_user | scc->cpu_nice | scc->cpu_system | + scc->cpu_iowait | scc->cpu_idle | scc->cpu_steal | + scc->cpu_irq | scc->cpu_softirq) == 0 + ) { + /* + * Set current struct fields to values from prev. + * iteration. Then their values won't jump from + * zero, when the CPU comes back online. + */ + *scc = *scp; + idle = 0.0; + goto print_zeros; + } + /* Compute interval again for current proc */ + per_cpu_itv = get_per_cpu_interval(scc, scp); + if (per_cpu_itv == 0) { + /* + * If the CPU is tickless then there is no change in CPU values + * but the sum of values is not zero. + */ + idle = 100.0; + print_zeros: + printf(" %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f\n", + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, idle); + continue; + } + } + print_stats_cpu_struct(scp, scc, 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) { + printf("\n%-11s CPU intr/s\n", prev_str); + G.avg_header_done = 1; + } + + for (cpu = 0; cpu <= G.cpu_nr; cpu++) { + data_t per_cpu_itv; + + /* Print stats about this CPU? */ + if (!is_cpu_in_bitmap(cpu)) + continue; + + per_cpu_itv = itv; + printf((cpu ? "%-11s %4u" : "%-11s all"), current_str, cpu - 1); + if (cpu) { + scc = &G.st_cpu[current][cpu]; + scp = &G.st_cpu[prev][cpu]; + /* Compute interval again for current proc */ + per_cpu_itv = get_per_cpu_interval(scc, scp); + if (per_cpu_itv == 0) { + printf(" %9.2f\n", 0.0); + continue; + } + } + printf(" %9.2f\n", hz_value(G.st_irq[prev][cpu].irq_nr, G.st_irq[current][cpu].irq_nr, per_cpu_itv)); + } + } + + if (display_opt(D_IRQ_CPU)) { + write_irqcpu_stats(G.st_irqcpu, G.irqcpu_nr, + itv, + prev, current, + prev_str, current_str + ); + } + + if (display_opt(D_SOFTIRQS)) { + write_irqcpu_stats(G.st_softirqcpu, G.softirqcpu_nr, + itv, + prev, current, + prev_str, current_str + ); + } +} + +/* + * Print the statistics + */ +static void write_stats(int current) +{ + char prev_time[16]; + char curr_time[16]; + + strftime(prev_time, sizeof(prev_time), "%X", &G.timestamp[!current]); + strftime(curr_time, sizeof(curr_time), "%X", &G.timestamp[current]); + + write_stats_core(!current, current, prev_time, curr_time); +} + +static void write_stats_avg(int current) +{ + write_stats_core(2, current, "Average:", "Average:"); +} + +/* + * Read CPU statistics + */ +static void get_cpu_statistics(struct stats_cpu *cpu, data_t *up, data_t *up0) +{ + FILE *fp; + char buf[1024]; + + fp = xfopen_for_read(PROCFS_STAT); + + while (fgets(buf, sizeof(buf), fp)) { + data_t sum; + unsigned cpu_number; + struct stats_cpu *cp; + + if (!starts_with_cpu(buf)) + continue; /* not "cpu" */ + if (buf[3] == ' ') { + /* "cpu " */ + cp = cpu; + } else { + /* "cpuN" */ + if (G.cpu_nr == 0 + || sscanf(buf + 3, "%u ", &cpu_number) != 1 + || cpu_number >= G.cpu_nr + ) { + continue; + } + cp = &cpu[cpu_number + 1]; + } + + /* Read the jiffies, save them */ + /* Not all fields have to be present */ + memset(cp, 0, sizeof(*cp)); + sscanf(skip_non_whitespace(buf + 3), + " %"FMT_DATA"u %"FMT_DATA"u %"FMT_DATA"u" + " %"FMT_DATA"u %"FMT_DATA"u %"FMT_DATA"u" + " %"FMT_DATA"u %"FMT_DATA"u %"FMT_DATA"u", + &cp->cpu_user, &cp->cpu_nice, &cp->cpu_system, + &cp->cpu_idle, &cp->cpu_iowait, &cp->cpu_irq, + &cp->cpu_softirq, &cp->cpu_steal, + &cp->cpu_guest + ); + /* + * Compute uptime in jiffies (1/HZ), it'll be the sum of + * individual CPU's uptimes. + * NB: We have to omit cpu_guest, because cpu_user includes it. + */ + sum = cp->cpu_user + cp->cpu_nice + cp->cpu_system + + cp->cpu_idle + cp->cpu_iowait + cp->cpu_irq + + cp->cpu_softirq + cp->cpu_steal; + + if (buf[3] == ' ') { + /* "cpu " */ + *up = sum; + } else { + /* "cpuN" */ + if (cpu_number == 0 && *up0 != 0) { + /* Compute uptime of single CPU */ + *up0 = sum; + } + } + } + fclose(fp); +} + +/* + * Read IRQs from /proc/stat + */ +static void get_irqs_from_stat(struct stats_irq *irq) +{ + FILE *fp; + char buf[1024]; + + fp = fopen_for_read(PROCFS_STAT); + if (!fp) + return; + + while (fgets(buf, sizeof(buf), fp)) { + if (strncmp(buf, "intr ", 5) == 0) + /* Read total number of IRQs since system boot */ + sscanf(buf + 5, "%"FMT_DATA"u", &irq->irq_nr); + } + + fclose(fp); +} + +/* + * Read stats from /proc/interrupts or /proc/softirqs + */ +static void get_irqs_from_interrupts(const char *fname, + struct stats_irqcpu *per_cpu_stats[], + int irqs_per_cpu, int current) +{ + FILE *fp; + struct stats_irq *irq_i; + struct stats_irqcpu *ic; + char *buf; + unsigned buflen; + unsigned cpu; + 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; + } + + fp = fopen_for_read(fname); + if (!fp) + return; + + buflen = INTERRUPTS_LINE + 16 * G.cpu_nr; + buf = xmalloc(buflen); + + /* Parse header and determine, which CPUs are online */ + iindex = 0; + while (fgets(buf, buflen, fp)) { + char *cp, *next; + next = buf; + while ((cp = strstr(next, "CPU")) != NULL + && iindex < G.cpu_nr + ) { + cpu = strtoul(cp + 3, &next, 10); + cpu_index[iindex++] = cpu; + } + if (iindex) /* We found header */ + break; + } + + irq = 0; + while (fgets(buf, buflen, fp) + && irq < irqs_per_cpu + ) { + char *cp; + /* Skip over "<irq>:" */ + cp = strchr(buf, ':'); + if (!cp) + continue; + + ic = &per_cpu_stats[current][irq]; + len = cp - buf; + if (len > sizeof(ic->irq_name)) { + len = sizeof(ic->irq_name); + } + safe_strncpy(ic->irq_name, buf, len); + digit = isdigit(buf[len - 1]); + 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; + } + cp = next; + } + irq++; + } + fclose(fp); + free(buf); + + 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 */ + irq++; + } +} + +static void get_uptime(data_t *uptime) +{ + FILE *fp; + 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; + 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; + } + } + + fclose(fp); +} + +static void get_localtime(struct tm *tm) +{ + time_t timer; + time(&timer); + localtime_r(&timer, tm); +} + +static void alarm_handler(int sig UNUSED_PARAM) +{ + signal(SIGALRM, alarm_handler); + alarm(G.interval); +} + +static void main_loop(void) +{ + unsigned current; + unsigned cpus; + + /* Read the stats */ + if (G.cpu_nr > 1) { + G.per_cpu_uptime[0] = 0; + get_uptime(&G.per_cpu_uptime[0]); + } + + get_cpu_statistics(G.st_cpu[0], &G.global_uptime[0], &G.per_cpu_uptime[0]); + + if (display_opt(D_IRQ_SUM)) + get_irqs_from_stat(G.st_irq[0]); + + if (display_opt(D_IRQ_SUM | D_IRQ_CPU)) + get_irqs_from_interrupts(PROCFS_INTERRUPTS, G.st_irqcpu, + G.irqcpu_nr, 0); + + if (display_opt(D_SOFTIRQS)) + get_irqs_from_interrupts(PROCFS_SOFTIRQS, G.st_softirqcpu, + G.softirqcpu_nr, 0); + + if (G.interval == 0) { + /* Display since boot time */ + cpus = G.cpu_nr + 1; + G.timestamp[1] = G.timestamp[0]; + memset(G.st_cpu[1], 0, sizeof(G.st_cpu[1][0]) * cpus); + memset(G.st_irq[1], 0, sizeof(G.st_irq[1][0]) * cpus); + memset(G.st_irqcpu[1], 0, sizeof(G.st_irqcpu[1][0]) * cpus * G.irqcpu_nr); + memset(G.st_softirqcpu[1], 0, sizeof(G.st_softirqcpu[1][0]) * cpus * G.softirqcpu_nr); + + write_stats(0); + + /* And we're done */ + return; + } + + /* Set a handler for SIGALRM */ + alarm_handler(0); + + /* Save the stats we already have. We need them to compute the average */ + G.timestamp[2] = G.timestamp[0]; + G.global_uptime[2] = G.global_uptime[0]; + G.per_cpu_uptime[2] = G.per_cpu_uptime[0]; + cpus = G.cpu_nr + 1; + memcpy(G.st_cpu[2], G.st_cpu[0], sizeof(G.st_cpu[0][0]) * cpus); + memcpy(G.st_irq[2], G.st_irq[0], sizeof(G.st_irq[0][0]) * cpus); + memcpy(G.st_irqcpu[2], G.st_irqcpu[0], sizeof(G.st_irqcpu[0][0]) * cpus * G.irqcpu_nr); + if (display_opt(D_SOFTIRQS)) { + memcpy(G.st_softirqcpu[2], G.st_softirqcpu[0], + sizeof(G.st_softirqcpu[0][0]) * cpus * G.softirqcpu_nr); + } + + current = 1; + while (1) { + /* Suspend until a signal is received */ + pause(); + + /* Set structures to 0 to distinguish off/online CPUs */ + memset(&G.st_cpu[current][/*cpu:*/ 1], 0, sizeof(G.st_cpu[0][0]) * G.cpu_nr); + + get_localtime(&G.timestamp[current]); + + /* Read stats */ + if (G.cpu_nr > 1) { + G.per_cpu_uptime[current] = 0; + get_uptime(&G.per_cpu_uptime[current]); + } + get_cpu_statistics(G.st_cpu[current], &G.global_uptime[current], &G.per_cpu_uptime[current]); + + if (display_opt(D_IRQ_SUM)) + get_irqs_from_stat(G.st_irq[current]); + + if (display_opt(D_IRQ_SUM | D_IRQ_CPU)) + get_irqs_from_interrupts(PROCFS_INTERRUPTS, G.st_irqcpu, + G.irqcpu_nr, current); + + if (display_opt(D_SOFTIRQS)) + get_irqs_from_interrupts(PROCFS_SOFTIRQS, + G.st_softirqcpu, + G.softirqcpu_nr, current); + + write_stats(current); + + if (G.count > 0) { + if (--G.count == 0) + break; + } + + current ^= 1; + } + + /* Print average statistics */ + write_stats_avg(current); +} + +/* 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; + for (i = 0; i < 3; i++) { + G.st_cpu[i] = xzalloc(sizeof(G.st_cpu[i][0]) * cpus); + G.st_irq[i] = xzalloc(sizeof(G.st_irq[i][0]) * cpus); + G.st_irqcpu[i] = xzalloc(sizeof(G.st_irqcpu[i][0]) * cpus * G.irqcpu_nr); + G.st_softirqcpu[i] = xzalloc(sizeof(G.st_softirqcpu[i][0]) * cpus * G.softirqcpu_nr); + } + G.cpu_bitmap_len = (cpus >> 3) + 1; + G.cpu_bitmap = xzalloc(G.cpu_bitmap_len); +} + +static void print_header(struct tm *t) +{ + char cur_date[16]; + struct utsname uts; + + /* Get system name, release number and hostname */ + uname(&uts); + + strftime(cur_date, sizeof(cur_date), "%x", t); + + printf("%s %s (%s) \t%s \t_%s_\t(%d CPU)\n", + 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 + */ +static int get_irqcpu_nr(const char *f, int max_irqs) +{ + FILE *fp; + char *line; + unsigned linelen; + unsigned irq; + + fp = fopen_for_read(f); + if (!fp) /* No interrupts file */ + return 0; + + linelen = INTERRUPTS_LINE + 16 * G.cpu_nr; + line = xmalloc(linelen); + + irq = 0; + while (fgets(line, linelen, fp) + && irq < max_irqs + ) { + int p = strcspn(line, ":"); + if ((p > 0) && (p < 16)) + irq++; + } + + fclose(fp); + free(line); + + return irq; +} + +//usage:#define mpstat_trivial_usage +//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) +{ + 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 */ + }; + + /* Dont buffer data if redirected to a pipe */ + setbuf(stdout, NULL); + + INIT_G(); + + G.interval = -1; + + /* Get number of processors */ + G.cpu_nr = get_cpu_nr(); + + /* Get number of clock ticks per sec */ + G.hz = get_hz(); + + /* Calculate number of interrupts per processor */ + G.irqcpu_nr = get_irqcpu_nr(PROCFS_INTERRUPTS, NR_IRQS) + NR_IRQCPU_PREALLOC; + + /* Calculate number of soft interrupts per processor */ + G.softirqcpu_nr = get_irqcpu_nr(PROCFS_SOFTIRQS, NR_IRQS) + NR_IRQCPU_PREALLOC; + + /* Allocate space for structures. + 1 for global structure. */ + alloc_struct(G.cpu_nr + 1); + + /* Parse and process arguments */ + opt = getopt32(argv, "AI:P:u", &opt_irq_fmt, &opt_set_cpu); + argv += optind; + + if (*argv) { + /* Get interval */ + G.interval = xatoi_u(*argv); + G.count = -1; + argv++; + if (*argv) { + /* Get count value */ + if (G.interval == 0) + bb_show_usage(); + G.count = xatoi_u(*argv); + //if (*++argv) + // bb_show_usage(); + } + } + if (G.interval < 0) + G.interval = 0; + + if (opt & OPT_ALL) { + G.p_option = 1; + G.options |= D_CPU + D_IRQ_SUM + D_IRQ_CPU + D_SOFTIRQS; + /* Select every CPU */ + memset(G.cpu_bitmap, 0xff, G.cpu_bitmap_len); + } + + 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 + bb_show_usage(); + } + + if ((opt & OPT_UTIL) /* -u? */ + || G.options == 0 /* nothing? (use default then) */ + ) { + G.options |= D_CPU; + } + + if (opt & OPT_SETCPU) { + char *t; + G.p_option = 1; + + for (t = strtok(opt_set_cpu, ","); t; t = strtok(NULL, ",")) { + if (strcmp(t, "ALL") == 0) { + /* Select every CPU */ + memset(G.cpu_bitmap, 0xff, G.cpu_bitmap_len); + } else { + /* Get CPU number */ + unsigned n = xatoi_u(t); + if (n >= G.cpu_nr) + bb_error_msg_and_die("not that many processors"); + n++; + G.cpu_bitmap[n >> 3] |= 1 << (n & 7); + } + } + } + + if (!G.p_option) + /* Display global stats */ + G.cpu_bitmap[0] = 1; + + /* Get time */ + get_localtime(&G.timestamp[0]); + + /* Display header */ + print_header(&G.timestamp[0]); + + /* The main loop */ + main_loop(); + + if (ENABLE_FEATURE_CLEAN_UP) { + /* Clean up */ + for (i = 0; i < 3; i++) { + free(G.st_cpu[i]); + free(G.st_irq[i]); + free(G.st_irqcpu[i]); + free(G.st_softirqcpu[i]); + } + free(G.cpu_bitmap); + free(&G); + } + + return EXIT_SUCCESS; +} |