busybox/procps/mpstat.c

968 lines
24 KiB
C
Raw Normal View History

/* 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 source tree.
*/
//config:config MPSTAT
//config: bool "mpstat (10 kb)"
//config: default y
//config: help
//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 */
//#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_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 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 interrupts;
char irq_name[MAX_IRQNAME_LEN];
};
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 stats_irq {
data_t irq_nr;
};
/* Globals. Sort by size and access frequency. */
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;
// 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];
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,
};
/* 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)
{
//bb_error_msg("curr:%lld prev:%lld G.hz:%u", curr, prev, G.hz);
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);
{
/* 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');
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("%"INTRATE_SCRWIDTH_STR".2f",
(double)(p->interrupts - q->interrupts) / 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;
}
}
//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));
}
}
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" */
cp = cpu; /* for "cpu " case */
if (buf[3] != ' ') {
/* "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 counters, save them */
/* Not all fields have to be present */
memset(cp, 0, sizeof(*cp));
sscanf(buf, "%*s"
" %"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 = xfopen_for_read(PROCFS_STAT);
while (fgets(buf, sizeof(buf), fp)) {
//bb_error_msg("/proc/stat:'%s'", buf);
libbb: introduce and use is_prefixed_with() function old new delta is_prefixed_with - 18 +18 complete_username 78 77 -1 man_main 737 735 -2 fsck_device 429 427 -2 unpack_ar_archive 80 76 -4 strip_unsafe_prefix 105 101 -4 singlemount 1054 1050 -4 rtc_adjtime_is_utc 90 86 -4 resolve_mount_spec 88 84 -4 parse_one_line 1029 1025 -4 parse_conf 1460 1456 -4 may_wakeup 83 79 -4 loadkmap_main 219 215 -4 get_irqs_from_stat 103 99 -4 get_header_cpio 913 909 -4 findfs_main 79 75 -4 fbsplash_main 1230 1226 -4 load_crontab 776 771 -5 expand_vars_to_list 1151 1146 -5 date_main 881 876 -5 skip_dev_pfx 30 24 -6 make_device 2199 2193 -6 complete_cmd_dir_file 773 767 -6 run_applet_and_exit 715 708 -7 uudecode_main 321 313 -8 pwdx_main 197 189 -8 execute 568 560 -8 i2cdetect_main 1186 1176 -10 procps_scan 1242 1230 -12 procps_read_smaps 1017 1005 -12 process_module 746 734 -12 patch_main 1903 1891 -12 nfsmount 3572 3560 -12 stack_machine 126 112 -14 process_timer_stats 449 435 -14 match_fstype 111 97 -14 do_ipaddr 1344 1330 -14 open_list_and_close 359 343 -16 get_header_tar 1795 1779 -16 prepend_new_eth_table 340 323 -17 fsck_main 1811 1794 -17 find_iface_state 56 38 -18 dnsd_main 1321 1303 -18 base_device 179 158 -21 find_keyword 104 82 -22 handle_incoming_and_exit 2785 2762 -23 parse_and_put_prompt 774 746 -28 modinfo 347 317 -30 find_action 204 171 -33 update_passwd 1470 1436 -34 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 0/49 up/down: 18/-540) Total: -522 bytes Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
2015-03-13 00:48:34 +08:00
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);
}
/*
* 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;
// 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)
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
) {
int len;
char last_char;
char *cp;
/* 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) - 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->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;
}
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 = 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;
}
}
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)) {
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,
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 */
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(%u CPU)\n",
uts.sysname, uts.release, uts.nodename, cur_date, uts.machine, G.cpu_nr);
}
/*
* 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: "\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 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 */
};
/* 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_count();
/* Get number of clock ticks per sec */
G.hz = bb_clk_tck();
/* 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_positive(*argv);
G.count = -1;
argv++;
if (*argv) {
/* Get count value */
if (G.interval == 0)
bb_show_usage();
G.count = xatoi_positive(*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) {
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? */
|| G.options == 0 /* nothing? (use default then) */
) {
G.options |= D_CPU;
}
if (opt & OPT_SETCPU) {
char *t;
G.p_option = 1;
for (t = strtok_r(opt_set_cpu, ",", &opt_set_cpu); t; t = strtok_r(NULL, ",", &opt_set_cpu)) {
if (strcmp(t, "ALL") == 0) {
/* Select every CPU */
memset(G.cpu_bitmap, 0xff, G.cpu_bitmap_len);
} else {
/* Get CPU number */
unsigned n = xatoi_positive(t);
if (n >= G.cpu_nr)
libbb: reduce the overhead of single parameter bb_error_msg() calls Back in 2007, commit 0c97c9d43707 ("'simple' error message functions by Loic Grenie") introduced bb_simple_perror_msg() to allow for a lower overhead call to bb_perror_msg() when only a string was being printed with no parameters. This saves space for some CPU architectures because it avoids the overhead of a call to a variadic function. However there has never been a simple version of bb_error_msg(), and since 2007 many new calls to bb_perror_msg() have been added that only take a single parameter and so could have been using bb_simple_perror_message(). This changeset introduces 'simple' versions of bb_info_msg(), bb_error_msg(), bb_error_msg_and_die(), bb_herror_msg() and bb_herror_msg_and_die(), and replaces all calls that only take a single parameter, or use something like ("%s", arg), with calls to the corresponding 'simple' version. Since it is likely that single parameter calls to the variadic functions may be accidentally reintroduced in the future a new debugging config option WARN_SIMPLE_MSG has been introduced. This uses some macro magic which will cause any such calls to generate a warning, but this is turned off by default to avoid use of the unpleasant macros in normal circumstances. This is a large changeset due to the number of calls that have been replaced. The only files that contain changes other than simple substitution of function calls are libbb.h, libbb/herror_msg.c, libbb/verror_msg.c and libbb/xfuncs_printf.c. In miscutils/devfsd.c, networking/udhcp/common.h and util-linux/mdev.c additonal macros have been added for logging so that single parameter and multiple parameter logging variants exist. The amount of space saved varies considerably by architecture, and was found to be as follows (for 'defconfig' using GCC 7.4): Arm: -92 bytes MIPS: -52 bytes PPC: -1836 bytes x86_64: -938 bytes Note that for the MIPS architecture only an exception had to be made disabling the 'simple' calls for 'udhcp' (in networking/udhcp/common.h) because it made these files larger on MIPS. Signed-off-by: James Byrne <james.byrne@origamienergy.com> Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
2019-07-02 17:35:03 +08:00
bb_simple_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;
}