mpstat: fix total par-cpu IRQ counts
[platform/upstream/busybox.git] / procps / mpstat.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Per-processor statistics, based on sysstat version 9.1.2 by Sebastien Godard
4  *
5  * Copyright (C) 2010 Marek Polacek <mmpolacek@gmail.com>
6  *
7  * Licensed under GPLv2, see file License in this tarball for details.
8  */
9
10 //applet:IF_MPSTAT(APPLET(mpstat, _BB_DIR_BIN, _BB_SUID_DROP))
11
12 //kbuild:lib-$(CONFIG_MPSTAT) += mpstat.o
13
14 //config:config MPSTAT
15 //config:       bool "mpstat"
16 //config:       default y
17 //config:       help
18 //config:         Per-processor statistics
19
20 #include "libbb.h"
21 #include <sys/utsname.h>        /* struct utsname */
22
23 //#define debug(fmt, ...) fprintf(stderr, fmt, ## __VA_ARGS__)
24 #define debug(fmt, ...) ((void)0)
25
26 /* Size of /proc/interrupts line, CPU data excluded */
27 #define INTERRUPTS_LINE    64
28 /* Maximum number of interrupts */
29 #define NR_IRQS            256
30 #define NR_IRQCPU_PREALLOC 3
31 #define MAX_IRQNAME_LEN    16
32 #define MAX_PF_NAME        512
33 /* sysstat 9.0.6 uses width 8, but newer code which also prints /proc/softirqs
34  * data needs more: "interrupts" in /proc/softirqs have longer names,
35  * most are up to 8 chars, one (BLOCK_IOPOLL) is even longer.
36  * We are printing headers in the " IRQNAME/s" form, experimentally
37  * anything smaller than 10 chars looks ugly for /proc/softirqs stats.
38  */
39 #define INTRATE_SCRWIDTH   10
40 #define INTRATE_SCRWIDTH_STR "10"
41
42 /* System files */
43 #define SYSFS_DEVCPU      "/sys/devices/system/cpu"
44 #define PROCFS_STAT       "/proc/stat"
45 #define PROCFS_INTERRUPTS "/proc/interrupts"
46 #define PROCFS_SOFTIRQS   "/proc/softirqs"
47 #define PROCFS_UPTIME     "/proc/uptime"
48
49
50 #if 1
51 typedef unsigned long long data_t;
52 typedef long long idata_t;
53 #define FMT_DATA "ll"
54 #define DATA_MAX ULLONG_MAX
55 #else
56 typedef unsigned long data_t;
57 typedef long idata_t;
58 #define FMT_DATA "l"
59 #define DATA_MAX ULONG_MAX
60 #endif
61
62
63 struct stats_irqcpu {
64         unsigned interrupt;
65         char irq_name[MAX_IRQNAME_LEN];
66 };
67
68 struct stats_cpu {
69         data_t cpu_user;
70         data_t cpu_nice;
71         data_t cpu_system;
72         data_t cpu_idle;
73         data_t cpu_iowait;
74         data_t cpu_steal;
75         data_t cpu_irq;
76         data_t cpu_softirq;
77         data_t cpu_guest;
78 };
79
80 struct stats_irq {
81         data_t irq_nr;
82 };
83
84
85 /* Globals. Sort by size and access frequency. */
86 struct globals {
87         int interval;
88         int count;
89         unsigned cpu_nr;                /* Number of CPUs */
90         unsigned irqcpu_nr;             /* Number of interrupts per CPU */
91         unsigned softirqcpu_nr;         /* Number of soft interrupts per CPU */
92         unsigned options;
93         unsigned hz;
94         unsigned cpu_bitmap_len;
95         smallint p_option;
96         smallint header_done;
97         smallint avg_header_done;
98         unsigned char *cpu_bitmap;      /* Bit 0: global, bit 1: 1st proc... */
99         data_t global_uptime[3];
100         data_t per_cpu_uptime[3];
101         struct stats_cpu *st_cpu[3];
102         struct stats_irq *st_irq[3];
103         struct stats_irqcpu *st_irqcpu[3];
104         struct stats_irqcpu *st_softirqcpu[3];
105         struct tm timestamp[3];
106 };
107 #define G (*ptr_to_globals)
108 #define INIT_G() do { \
109         SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
110 } while (0)
111
112 /* The selected interrupts statistics (bits in G.options) */
113 enum {
114         D_CPU      = 1 << 0,
115         D_IRQ_SUM  = 1 << 1,
116         D_IRQ_CPU  = 1 << 2,
117         D_SOFTIRQS = 1 << 3,
118 };
119
120
121 /* Does str start with "cpu"? */
122 static int starts_with_cpu(const char *str)
123 {
124         return !((str[0] - 'c') | (str[1] - 'p') | (str[2] - 'u'));
125 }
126
127 /* Is option on? */
128 static ALWAYS_INLINE int display_opt(int opt)
129 {
130         return (opt & G.options);
131 }
132
133 #if DATA_MAX > 0xffffffff
134 /*
135  * Handle overflow conditions properly for counters which can have
136  * less bits than data_t, depending on the kernel version.
137  */
138 /* Surprisingly, on 32bit inlining is a size win */
139 static ALWAYS_INLINE data_t overflow_safe_sub(data_t prev, data_t curr)
140 {
141         data_t v = curr - prev;
142
143         if ((idata_t)v < 0     /* curr < prev - counter overflow? */
144          && prev <= 0xffffffff /* kernel uses 32bit value for the counter? */
145         ) {
146                 /* Add 33th bit set to 1 to curr, compensating for the overflow */
147                 /* double shift defeats "warning: left shift count >= width of type" */
148                 v += ((data_t)1 << 16) << 16;
149         }
150         return v;
151 }
152 #else
153 static ALWAYS_INLINE data_t overflow_safe_sub(data_t prev, data_t curr)
154 {
155         return curr - prev;
156 }
157 #endif
158
159 static double percent_value(data_t prev, data_t curr, data_t itv)
160 {
161         return ((double)overflow_safe_sub(prev, curr)) / itv * 100;
162 }
163
164 static double hz_value(data_t prev, data_t curr, data_t itv)
165 {
166         //bb_error_msg("curr:%lld prev:%lld G.hz:%u", curr, prev, G.hz);
167         return ((double)overflow_safe_sub(prev, curr)) / itv * G.hz;
168 }
169
170 static ALWAYS_INLINE data_t jiffies_diff(data_t old, data_t new)
171 {
172         data_t diff = new - old;
173         return (diff == 0) ? 1 : diff;
174 }
175
176 static int is_cpu_in_bitmap(unsigned cpu)
177 {
178         return G.cpu_bitmap[cpu >> 3] & (1 << (cpu & 7));
179 }
180
181 static void write_irqcpu_stats(struct stats_irqcpu *per_cpu_stats[],
182                 int total_irqs,
183                 data_t itv,
184                 int prev, int current,
185                 const char *prev_str, const char *current_str)
186 {
187         int j;
188         int offset, cpu;
189         struct stats_irqcpu *p0, *q0;
190
191         /* Check if number of IRQs has changed */
192         if (G.interval != 0) {
193                 for (j = 0; j <= total_irqs; j++) {
194                         p0 = &per_cpu_stats[current][j];
195                         if (p0->irq_name[0] != '\0') {
196                                 q0 = &per_cpu_stats[prev][j];
197                                 if (strcmp(p0->irq_name, q0->irq_name) != 0) {
198                                         /* Strings are different */
199                                         break;
200                                 }
201                         }
202                 }
203         }
204
205         /* Print header */
206         printf("\n%-11s  CPU", prev_str);
207         {
208                 /* A bit complex code to "buy back" space if one header is too wide.
209                  * Here's how it looks like. BLOCK_IOPOLL eating too much,
210                  * and latter headers use smaller width to compensate:
211                  * ...BLOCK/s BLOCK_IOPOLL/s TASKLET/s SCHED/s HRTIMER/s  RCU/s
212                  * ...   2.32      0.00      0.01     17.58      0.14    141.96
213                  */
214                 int expected_len = 0;
215                 int printed_len = 0;
216                 for (j = 0; j < total_irqs; j++) {
217                         p0 = &per_cpu_stats[current][j];
218                         if (p0->irq_name[0] != '\0') {
219                                 int n = (INTRATE_SCRWIDTH-3) - (printed_len - expected_len);
220                                 printed_len += printf(" %*s/s", n > 0 ? n : 0, skip_whitespace(p0->irq_name));
221                                 expected_len += INTRATE_SCRWIDTH;
222                         }
223                 }
224         }
225         bb_putchar('\n');
226
227         for (cpu = 1; cpu <= G.cpu_nr; cpu++) {
228                 /* Check if we want stats about this CPU */
229                 if (!is_cpu_in_bitmap(cpu) && G.p_option) {
230                         continue;
231                 }
232
233                 printf("%-11s %4u", current_str, cpu - 1);
234
235                 for (j = 0; j < total_irqs; j++) {
236                         /* IRQ field set only for proc 0 */
237                         p0 = &per_cpu_stats[current][j];
238
239                         /*
240                          * An empty string for irq name means that
241                          * interrupt is no longer used.
242                          */
243                         if (p0->irq_name[0] != '\0') {
244                                 offset = j;
245                                 q0 = &per_cpu_stats[prev][offset];
246
247                                 /*
248                                  * If we want stats for the time since boot
249                                  * we have p0->irq != q0->irq.
250                                  */
251                                 if (strcmp(p0->irq_name, q0->irq_name) != 0
252                                  && G.interval != 0
253                                 ) {
254                                         if (j) {
255                                                 offset = j - 1;
256                                                 q0 = &per_cpu_stats[prev][offset];
257                                         }
258                                         if (strcmp(p0->irq_name, q0->irq_name) != 0
259                                          && (j + 1 < total_irqs)
260                                         ) {
261                                                 offset = j + 1;
262                                                 q0 = &per_cpu_stats[prev][offset];
263                                         }
264                                 }
265
266                                 if (strcmp(p0->irq_name, q0->irq_name) == 0
267                                  || G.interval == 0
268                                 ) {
269                                         struct stats_irqcpu *p, *q;
270                                         p = &per_cpu_stats[current][(cpu - 1) * total_irqs + j];
271                                         q = &per_cpu_stats[prev][(cpu - 1) * total_irqs + offset];
272                                         printf("%"INTRATE_SCRWIDTH_STR".2f",
273                                                 (double)(p->interrupt - q->interrupt) / itv * G.hz);
274                                 } else {
275                                         printf("        N/A");
276                                 }
277                         }
278                 }
279                 bb_putchar('\n');
280         }
281 }
282
283 static data_t get_per_cpu_interval(const struct stats_cpu *scc,
284                 const struct stats_cpu *scp)
285 {
286         return ((scc->cpu_user + scc->cpu_nice +
287                  scc->cpu_system + scc->cpu_iowait +
288                  scc->cpu_idle + scc->cpu_steal +
289                  scc->cpu_irq + scc->cpu_softirq) -
290                 (scp->cpu_user + scp->cpu_nice +
291                  scp->cpu_system + scp->cpu_iowait +
292                  scp->cpu_idle + scp->cpu_steal +
293                  scp->cpu_irq + scp->cpu_softirq));
294 }
295
296 static void print_stats_cpu_struct(const struct stats_cpu *p,
297                 const struct stats_cpu *c,
298                 data_t itv)
299 {
300         printf(" %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f\n",
301                 percent_value(p->cpu_user - p->cpu_guest,
302                 /**/                          c->cpu_user - c->cpu_guest, itv),
303                 percent_value(p->cpu_nice   , c->cpu_nice   , itv),
304                 percent_value(p->cpu_system , c->cpu_system , itv),
305                 percent_value(p->cpu_iowait , c->cpu_iowait , itv),
306                 percent_value(p->cpu_irq    , c->cpu_irq    , itv),
307                 percent_value(p->cpu_softirq, c->cpu_softirq, itv),
308                 percent_value(p->cpu_steal  , c->cpu_steal  , itv),
309                 percent_value(p->cpu_guest  , c->cpu_guest  , itv),
310                 percent_value(p->cpu_idle   , c->cpu_idle   , itv)
311         );
312 }
313
314 static void write_stats_core(int prev, int current,
315                 const char *prev_str, const char *current_str)
316 {
317         struct stats_cpu *scc, *scp;
318         data_t itv, global_itv;
319         int cpu;
320
321         /* Compute time interval */
322         itv = global_itv = jiffies_diff(G.global_uptime[prev], G.global_uptime[current]);
323
324         /* Reduce interval to one CPU */
325         if (G.cpu_nr > 1)
326                 itv = jiffies_diff(G.per_cpu_uptime[prev], G.per_cpu_uptime[current]);
327
328         /* Print CPU stats */
329         if (display_opt(D_CPU)) {
330
331                 /* This is done exactly once */
332                 if (!G.header_done) {
333                         printf("\n%-11s  CPU    %%usr   %%nice    %%sys %%iowait    %%irq   %%soft  %%steal  %%guest   %%idle\n",
334                                 prev_str
335                         );
336                         G.header_done = 1;
337                 }
338
339                 for (cpu = 0; cpu <= G.cpu_nr; cpu++) {
340                         data_t per_cpu_itv;
341
342                         /* Print stats about this particular CPU? */
343                         if (!is_cpu_in_bitmap(cpu))
344                                 continue;
345
346                         scc = &G.st_cpu[current][cpu];
347                         scp = &G.st_cpu[prev][cpu];
348                         per_cpu_itv = global_itv;
349
350                         printf((cpu ? "%-11s %4u" : "%-11s  all"), current_str, cpu - 1);
351                         if (cpu) {
352                                 double idle;
353                                 /*
354                                  * If the CPU is offline, then it isn't in /proc/stat,
355                                  * so all values are 0.
356                                  * NB: Guest time is already included in user time.
357                                  */
358                                 if ((scc->cpu_user | scc->cpu_nice | scc->cpu_system |
359                                      scc->cpu_iowait | scc->cpu_idle | scc->cpu_steal |
360                                      scc->cpu_irq | scc->cpu_softirq) == 0
361                                 ) {
362                                         /*
363                                          * Set current struct fields to values from prev.
364                                          * iteration. Then their values won't jump from
365                                          * zero, when the CPU comes back online.
366                                          */
367                                         *scc = *scp;
368                                         idle = 0.0;
369                                         goto print_zeros;
370                                 }
371                                 /* Compute interval again for current proc */
372                                 per_cpu_itv = get_per_cpu_interval(scc, scp);
373                                 if (per_cpu_itv == 0) {
374                                         /*
375                                          * If the CPU is tickless then there is no change in CPU values
376                                          * but the sum of values is not zero.
377                                          */
378                                         idle = 100.0;
379  print_zeros:
380                                         printf(" %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f\n",
381                                                 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, idle);
382                                         continue;
383                                 }
384                         }
385                         print_stats_cpu_struct(scp, scc, per_cpu_itv);
386                 }
387         }
388
389         /* Print total number of IRQs per CPU */
390         if (display_opt(D_IRQ_SUM)) {
391
392                 /* Print average header, this is done exactly once */
393                 if (!G.avg_header_done) {
394                         printf("\n%-11s  CPU    intr/s\n", prev_str);
395                         G.avg_header_done = 1;
396                 }
397
398                 for (cpu = 0; cpu <= G.cpu_nr; cpu++) {
399                         data_t per_cpu_itv;
400
401                         /* Print stats about this CPU? */
402                         if (!is_cpu_in_bitmap(cpu))
403                                 continue;
404
405                         per_cpu_itv = itv;
406                         printf((cpu ? "%-11s %4u" : "%-11s  all"), current_str, cpu - 1);
407                         if (cpu) {
408                                 scc = &G.st_cpu[current][cpu];
409                                 scp = &G.st_cpu[prev][cpu];
410                                 /* Compute interval again for current proc */
411                                 per_cpu_itv = get_per_cpu_interval(scc, scp);
412                                 if (per_cpu_itv == 0) {
413                                         printf(" %9.2f\n", 0.0);
414                                         continue;
415                                 }
416                         }
417                         //bb_error_msg("G.st_irq[%u][%u].irq_nr:%lld - G.st_irq[%u][%u].irq_nr:%lld",
418                         // current, cpu, G.st_irq[prev][cpu].irq_nr, prev, cpu, G.st_irq[current][cpu].irq_nr);
419                         printf(" %9.2f\n", hz_value(G.st_irq[prev][cpu].irq_nr, G.st_irq[current][cpu].irq_nr, per_cpu_itv));
420                 }
421         }
422
423         if (display_opt(D_IRQ_CPU)) {
424                 write_irqcpu_stats(G.st_irqcpu, G.irqcpu_nr,
425                                 itv,
426                                 prev, current,
427                                 prev_str, current_str
428                 );
429         }
430
431         if (display_opt(D_SOFTIRQS)) {
432                 write_irqcpu_stats(G.st_softirqcpu, G.softirqcpu_nr,
433                                 itv,
434                                 prev, current,
435                                 prev_str, current_str
436                 );
437         }
438 }
439
440 /*
441  * Print the statistics
442  */
443 static void write_stats(int current)
444 {
445         char prev_time[16];
446         char curr_time[16];
447
448         strftime(prev_time, sizeof(prev_time), "%X", &G.timestamp[!current]);
449         strftime(curr_time, sizeof(curr_time), "%X", &G.timestamp[current]);
450
451         write_stats_core(!current, current, prev_time, curr_time);
452 }
453
454 static void write_stats_avg(int current)
455 {
456         write_stats_core(2, current, "Average:", "Average:");
457 }
458
459 /*
460  * Read CPU statistics
461  */
462 static void get_cpu_statistics(struct stats_cpu *cpu, data_t *up, data_t *up0)
463 {
464         FILE *fp;
465         char buf[1024];
466
467         fp = xfopen_for_read(PROCFS_STAT);
468
469         while (fgets(buf, sizeof(buf), fp)) {
470                 data_t sum;
471                 unsigned cpu_number;
472                 struct stats_cpu *cp;
473
474                 if (!starts_with_cpu(buf))
475                         continue; /* not "cpu" */
476
477                 cp = cpu; /* for "cpu " case */
478                 if (buf[3] != ' ') {
479                         /* "cpuN " */
480                         if (G.cpu_nr == 0
481                          || sscanf(buf + 3, "%u ", &cpu_number) != 1
482                          || cpu_number >= G.cpu_nr
483                         ) {
484                                 continue;
485                         }
486                         cp = &cpu[cpu_number + 1];
487                 }
488
489                 /* Read the counters, save them */
490                 /* Not all fields have to be present */
491                 memset(cp, 0, sizeof(*cp));
492                 sscanf(buf, "%*s"
493                         " %"FMT_DATA"u %"FMT_DATA"u %"FMT_DATA"u"
494                         " %"FMT_DATA"u %"FMT_DATA"u %"FMT_DATA"u"
495                         " %"FMT_DATA"u %"FMT_DATA"u %"FMT_DATA"u",
496                         &cp->cpu_user, &cp->cpu_nice, &cp->cpu_system,
497                         &cp->cpu_idle, &cp->cpu_iowait, &cp->cpu_irq,
498                         &cp->cpu_softirq, &cp->cpu_steal, &cp->cpu_guest
499                 );
500                 /*
501                  * Compute uptime in jiffies (1/HZ), it'll be the sum of
502                  * individual CPU's uptimes.
503                  * NB: We have to omit cpu_guest, because cpu_user includes it.
504                  */
505                 sum = cp->cpu_user + cp->cpu_nice + cp->cpu_system +
506                         cp->cpu_idle + cp->cpu_iowait + cp->cpu_irq +
507                         cp->cpu_softirq + cp->cpu_steal;
508
509                 if (buf[3] == ' ') {
510                         /* "cpu " */
511                         *up = sum;
512                 } else {
513                         /* "cpuN " */
514                         if (cpu_number == 0 && *up0 != 0) {
515                                 /* Compute uptime of single CPU */
516                                 *up0 = sum;
517                         }
518                 }
519         }
520         fclose(fp);
521 }
522
523 /*
524  * Read IRQs from /proc/stat
525  */
526 static void get_irqs_from_stat(struct stats_irq *irq)
527 {
528         FILE *fp;
529         unsigned cpu;
530         char buf[1024];
531
532         for (cpu = 1; cpu <= G.cpu_nr; cpu++) {
533                 irq->irq_nr = 0;
534         }
535
536         fp = fopen_for_read(PROCFS_STAT);
537         if (!fp)
538                 return;
539
540         while (fgets(buf, sizeof(buf), fp)) {
541                 //bb_error_msg("/proc/stat:'%s'", buf);
542                 if (strncmp(buf, "intr ", 5) == 0) {
543                         /* Read total number of IRQs since system boot */
544                         sscanf(buf + 5, "%"FMT_DATA"u", &irq->irq_nr);
545                 }
546         }
547
548         fclose(fp);
549 }
550
551 /*
552  * Read stats from /proc/interrupts or /proc/softirqs
553  */
554 static void get_irqs_from_interrupts(const char *fname,
555                 struct stats_irqcpu *per_cpu_stats[],
556                 int irqs_per_cpu, int current)
557 {
558         FILE *fp;
559         struct stats_irq *irq_i;
560         struct stats_irqcpu *ic;
561         char *buf;
562         unsigned buflen;
563         unsigned cpu;
564         unsigned irq;
565         int cpu_index[G.cpu_nr];
566         int iindex;
567
568 // Moved to get_irqs_from_stat(), which is called once, not twice,
569 // unlike get_irqs_from_interrupts().
570 // Otherwise reading of /proc/softirqs
571 // was resetting counts to 0 after we painstakingly collected them from
572 // /proc/interrupts. Which resulted in:
573 // 01:32:47 PM  CPU    intr/s
574 // 01:32:47 PM  all    591.47
575 // 01:32:47 PM    0      0.00 <= ???
576 // 01:32:47 PM    1      0.00 <= ???
577 //      for (cpu = 1; cpu <= G.cpu_nr; cpu++) {
578 //              G.st_irq[current][cpu].irq_nr = 0;
579 //              //bb_error_msg("G.st_irq[%u][%u].irq_nr=0", current, cpu);
580 //      }
581
582         fp = fopen_for_read(fname);
583         if (!fp)
584                 return;
585
586         buflen = INTERRUPTS_LINE + 16 * G.cpu_nr;
587         buf = xmalloc(buflen);
588
589         /* Parse header and determine, which CPUs are online */
590         iindex = 0;
591         while (fgets(buf, buflen, fp)) {
592                 char *cp, *next;
593                 next = buf;
594                 while ((cp = strstr(next, "CPU")) != NULL
595                  && iindex < G.cpu_nr
596                 ) {
597                         cpu = strtoul(cp + 3, &next, 10);
598                         cpu_index[iindex++] = cpu;
599                 }
600                 if (iindex) /* We found header */
601                         break;
602         }
603
604         irq = 0;
605         while (fgets(buf, buflen, fp)
606          && irq < irqs_per_cpu
607         ) {
608                 int len;
609                 char last_char;
610                 char *cp;
611
612                 /* Skip over "IRQNAME:" */
613                 cp = strchr(buf, ':');
614                 if (!cp)
615                         continue;
616                 last_char = cp[-1];
617
618                 ic = &per_cpu_stats[current][irq];
619                 len = cp - buf;
620                 if (len >= sizeof(ic->irq_name)) {
621                         len = sizeof(ic->irq_name) - 1;
622                 }
623                 safe_strncpy(ic->irq_name, buf, len + 1);
624                 //bb_error_msg("%s: irq%d:'%s' buf:'%s'", fname, irq, ic->irq_name, buf);
625                 cp++;
626
627                 for (cpu = 0; cpu < iindex; cpu++) {
628                         char *next;
629                         ic = &per_cpu_stats[current][cpu_index[cpu] * irqs_per_cpu + irq];
630                         irq_i = &G.st_irq[current][cpu_index[cpu] + 1];
631                         ic->interrupt = strtoul(cp, &next, 10);
632                         /* Count only numerical IRQs */
633                         if (isdigit(last_char)) {
634                                 irq_i->irq_nr += ic->interrupt;
635                                 //bb_error_msg("G.st_irq[%u][%u].irq_nr + %u = %lld",
636                                 // current, cpu_index[cpu] + 1, ic->interrupt, irq_i->irq_nr);
637                         }
638                         cp = next;
639                 }
640                 irq++;
641         }
642         fclose(fp);
643         free(buf);
644
645         while (irq < irqs_per_cpu) {
646                 /* Number of interrupts per CPU has changed */
647                 ic = &per_cpu_stats[current][irq];
648                 ic->irq_name[0] = '\0'; /* False interrupt */
649                 irq++;
650         }
651 }
652
653 static void get_uptime(data_t *uptime)
654 {
655         FILE *fp;
656         char buf[sizeof(long)*3 * 2 + 4]; /* enough for long.long */
657         unsigned long uptime_sec, decimal;
658
659         fp = fopen_for_read(PROCFS_UPTIME);
660         if (!fp)
661                 return;
662         if (fgets(buf, sizeof(buf), fp)) {
663                 if (sscanf(buf, "%lu.%lu", &uptime_sec, &decimal) == 2) {
664                         *uptime = (data_t)uptime_sec * G.hz + decimal * G.hz / 100;
665                 }
666         }
667
668         fclose(fp);
669 }
670
671 static void get_localtime(struct tm *tm)
672 {
673         time_t timer;
674         time(&timer);
675         localtime_r(&timer, tm);
676 }
677
678 static void alarm_handler(int sig UNUSED_PARAM)
679 {
680         signal(SIGALRM, alarm_handler);
681         alarm(G.interval);
682 }
683
684 static void main_loop(void)
685 {
686         unsigned current;
687         unsigned cpus;
688
689         /* Read the stats */
690         if (G.cpu_nr > 1) {
691                 G.per_cpu_uptime[0] = 0;
692                 get_uptime(&G.per_cpu_uptime[0]);
693         }
694
695         get_cpu_statistics(G.st_cpu[0], &G.global_uptime[0], &G.per_cpu_uptime[0]);
696
697         if (display_opt(D_IRQ_SUM))
698                 get_irqs_from_stat(G.st_irq[0]);
699
700         if (display_opt(D_IRQ_SUM | D_IRQ_CPU))
701                 get_irqs_from_interrupts(PROCFS_INTERRUPTS, G.st_irqcpu,
702                                         G.irqcpu_nr, 0);
703
704         if (display_opt(D_SOFTIRQS))
705                 get_irqs_from_interrupts(PROCFS_SOFTIRQS, G.st_softirqcpu,
706                                         G.softirqcpu_nr, 0);
707
708         if (G.interval == 0) {
709                 /* Display since boot time */
710                 cpus = G.cpu_nr + 1;
711                 G.timestamp[1] = G.timestamp[0];
712                 memset(G.st_cpu[1], 0, sizeof(G.st_cpu[1][0]) * cpus);
713                 memset(G.st_irq[1], 0, sizeof(G.st_irq[1][0]) * cpus);
714                 memset(G.st_irqcpu[1], 0, sizeof(G.st_irqcpu[1][0]) * cpus * G.irqcpu_nr);
715                 memset(G.st_softirqcpu[1], 0, sizeof(G.st_softirqcpu[1][0]) * cpus * G.softirqcpu_nr);
716
717                 write_stats(0);
718
719                 /* And we're done */
720                 return;
721         }
722
723         /* Set a handler for SIGALRM */
724         alarm_handler(0);
725
726         /* Save the stats we already have. We need them to compute the average */
727         G.timestamp[2] = G.timestamp[0];
728         G.global_uptime[2] = G.global_uptime[0];
729         G.per_cpu_uptime[2] = G.per_cpu_uptime[0];
730         cpus = G.cpu_nr + 1;
731         memcpy(G.st_cpu[2], G.st_cpu[0], sizeof(G.st_cpu[0][0]) * cpus);
732         memcpy(G.st_irq[2], G.st_irq[0], sizeof(G.st_irq[0][0]) * cpus);
733         memcpy(G.st_irqcpu[2], G.st_irqcpu[0], sizeof(G.st_irqcpu[0][0]) * cpus * G.irqcpu_nr);
734         if (display_opt(D_SOFTIRQS)) {
735                 memcpy(G.st_softirqcpu[2], G.st_softirqcpu[0],
736                         sizeof(G.st_softirqcpu[0][0]) * cpus * G.softirqcpu_nr);
737         }
738
739         current = 1;
740         while (1) {
741                 /* Suspend until a signal is received */
742                 pause();
743
744                 /* Set structures to 0 to distinguish off/online CPUs */
745                 memset(&G.st_cpu[current][/*cpu:*/ 1], 0, sizeof(G.st_cpu[0][0]) * G.cpu_nr);
746
747                 get_localtime(&G.timestamp[current]);
748
749                 /* Read stats */
750                 if (G.cpu_nr > 1) {
751                         G.per_cpu_uptime[current] = 0;
752                         get_uptime(&G.per_cpu_uptime[current]);
753                 }
754                 get_cpu_statistics(G.st_cpu[current], &G.global_uptime[current], &G.per_cpu_uptime[current]);
755
756                 if (display_opt(D_IRQ_SUM))
757                         get_irqs_from_stat(G.st_irq[current]);
758
759                 if (display_opt(D_IRQ_SUM | D_IRQ_CPU))
760                         get_irqs_from_interrupts(PROCFS_INTERRUPTS, G.st_irqcpu,
761                                         G.irqcpu_nr, current);
762
763                 if (display_opt(D_SOFTIRQS))
764                         get_irqs_from_interrupts(PROCFS_SOFTIRQS,
765                                         G.st_softirqcpu,
766                                         G.softirqcpu_nr, current);
767
768                 write_stats(current);
769
770                 if (G.count > 0) {
771                         if (--G.count == 0)
772                                 break;
773                 }
774
775                 current ^= 1;
776         }
777
778         /* Print average statistics */
779         write_stats_avg(current);
780 }
781
782 /* Initialization */
783
784 /* Get number of clock ticks per sec */
785 static ALWAYS_INLINE unsigned get_hz(void)
786 {
787         return sysconf(_SC_CLK_TCK);
788 }
789
790 static void alloc_struct(int cpus)
791 {
792         int i;
793         for (i = 0; i < 3; i++) {
794                 G.st_cpu[i] = xzalloc(sizeof(G.st_cpu[i][0]) * cpus);
795                 G.st_irq[i] = xzalloc(sizeof(G.st_irq[i][0]) * cpus);
796                 G.st_irqcpu[i] = xzalloc(sizeof(G.st_irqcpu[i][0]) * cpus * G.irqcpu_nr);
797                 G.st_softirqcpu[i] = xzalloc(sizeof(G.st_softirqcpu[i][0]) * cpus * G.softirqcpu_nr);
798         }
799         G.cpu_bitmap_len = (cpus >> 3) + 1;
800         G.cpu_bitmap = xzalloc(G.cpu_bitmap_len);
801 }
802
803 static void print_header(struct tm *t)
804 {
805         char cur_date[16];
806         struct utsname uts;
807
808         /* Get system name, release number and hostname */
809         uname(&uts);
810
811         strftime(cur_date, sizeof(cur_date), "%x", t);
812
813         printf("%s %s (%s)\t%s\t_%s_\t(%u CPU)\n",
814                 uts.sysname, uts.release, uts.nodename, cur_date, uts.machine, G.cpu_nr);
815 }
816
817 /*
818  * Get number of processors in /sys
819  */
820 static int get_sys_cpu_nr(void)
821 {
822         DIR *dir;
823         struct dirent *d;
824         struct stat buf;
825         char line[MAX_PF_NAME];
826         int proc_nr = 0;
827
828         dir = opendir(SYSFS_DEVCPU);
829         if (!dir)
830                 return 0;       /* /sys not mounted */
831
832         /* Get current file entry */
833         while ((d = readdir(dir)) != NULL) {
834                 if (starts_with_cpu(d->d_name) && isdigit(d->d_name[3])) {
835                         snprintf(line, MAX_PF_NAME, "%s/%s", SYSFS_DEVCPU,
836                                  d->d_name);
837                         line[MAX_PF_NAME - 1] = '\0';
838                         /* Get information about file */
839                         if (stat(line, &buf) < 0)
840                                 continue;
841                         /* If found 'cpuN', we have one more processor */
842                         if (S_ISDIR(buf.st_mode))
843                                 proc_nr++;
844                 }
845         }
846
847         closedir(dir);
848         return proc_nr;
849 }
850
851 /*
852  * Get number of processors in /proc/stat
853  * Return value '0' means one CPU and non SMP kernel.
854  * Otherwise N means N processor(s) and SMP kernel.
855  */
856 static int get_proc_cpu_nr(void)
857 {
858         FILE *fp;
859         char line[256];
860         int proc_nr = -1;
861
862         fp = xfopen_for_read(PROCFS_STAT);
863         while (fgets(line, sizeof(line), fp)) {
864                 if (!starts_with_cpu(line)) {
865                         if (proc_nr >= 0)
866                                 break; /* we are past "cpuN..." lines */
867                         continue;
868                 }
869                 if (line[3] != ' ') { /* "cpuN" */
870                         int num_proc;
871                         if (sscanf(line + 3, "%u", &num_proc) == 1
872                          && num_proc > proc_nr
873                         ) {
874                                 proc_nr = num_proc;
875                         }
876                 }
877         }
878
879         fclose(fp);
880         return proc_nr + 1;
881 }
882
883 static int get_cpu_nr(void)
884 {
885         int n;
886
887         /* Try to use /sys, if possible */
888         n = get_sys_cpu_nr();
889         if (n == 0)
890                 /* Otherwise use /proc/stat */
891                 n = get_proc_cpu_nr();
892
893         return n;
894 }
895
896 /*
897  * Get number of interrupts available per processor
898  */
899 static int get_irqcpu_nr(const char *f, int max_irqs)
900 {
901         FILE *fp;
902         char *line;
903         unsigned linelen;
904         unsigned irq;
905
906         fp = fopen_for_read(f);
907         if (!fp)                /* No interrupts file */
908                 return 0;
909
910         linelen = INTERRUPTS_LINE + 16 * G.cpu_nr;
911         line = xmalloc(linelen);
912
913         irq = 0;
914         while (fgets(line, linelen, fp)
915          && irq < max_irqs
916         ) {
917                 int p = strcspn(line, ":");
918                 if ((p > 0) && (p < 16))
919                         irq++;
920         }
921
922         fclose(fp);
923         free(line);
924
925         return irq;
926 }
927
928 //usage:#define mpstat_trivial_usage
929 //usage:       "[-A] [-I SUM|CPU|ALL|SCPU] [-u] [-P num|ALL] [INTERVAL [COUNT]]"
930 //usage:#define mpstat_full_usage "\n\n"
931 //usage:       "Per-processor statistics\n"
932 //usage:     "\nOptions:"
933 //usage:     "\n        -A                      Same as -I ALL -u -P ALL"
934 //usage:     "\n        -I SUM|CPU|ALL|SCPU     Report interrupt statistics"
935 //usage:     "\n        -P num|ALL              Processor to monitor"
936 //usage:     "\n        -u                      Report CPU utilization"
937
938 int mpstat_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
939 int mpstat_main(int UNUSED_PARAM argc, char **argv)
940 {
941         char *opt_irq_fmt;
942         char *opt_set_cpu;
943         int i, opt;
944         enum {
945                 OPT_ALL    = 1 << 0,    /* -A */
946                 OPT_INTS   = 1 << 1,    /* -I */
947                 OPT_SETCPU = 1 << 2,    /* -P */
948                 OPT_UTIL   = 1 << 3,    /* -u */
949         };
950
951         /* Dont buffer data if redirected to a pipe */
952         setbuf(stdout, NULL);
953
954         INIT_G();
955
956         G.interval = -1;
957
958         /* Get number of processors */
959         G.cpu_nr = get_cpu_nr();
960
961         /* Get number of clock ticks per sec */
962         G.hz = get_hz();
963
964         /* Calculate number of interrupts per processor */
965         G.irqcpu_nr = get_irqcpu_nr(PROCFS_INTERRUPTS, NR_IRQS) + NR_IRQCPU_PREALLOC;
966
967         /* Calculate number of soft interrupts per processor */
968         G.softirqcpu_nr = get_irqcpu_nr(PROCFS_SOFTIRQS, NR_IRQS) + NR_IRQCPU_PREALLOC;
969
970         /* Allocate space for structures. + 1 for global structure. */
971         alloc_struct(G.cpu_nr + 1);
972
973         /* Parse and process arguments */
974         opt = getopt32(argv, "AI:P:u", &opt_irq_fmt, &opt_set_cpu);
975         argv += optind;
976
977         if (*argv) {
978                 /* Get interval */
979                 G.interval = xatoi_u(*argv);
980                 G.count = -1;
981                 argv++;
982                 if (*argv) {
983                         /* Get count value */
984                         if (G.interval == 0)
985                                 bb_show_usage();
986                         G.count = xatoi_u(*argv);
987                         //if (*++argv)
988                         //      bb_show_usage();
989                 }
990         }
991         if (G.interval < 0)
992                 G.interval = 0;
993
994         if (opt & OPT_ALL) {
995                 G.p_option = 1;
996                 G.options |= D_CPU + D_IRQ_SUM + D_IRQ_CPU + D_SOFTIRQS;
997                 /* Select every CPU */
998                 memset(G.cpu_bitmap, 0xff, G.cpu_bitmap_len);
999         }
1000
1001         if (opt & OPT_INTS) {
1002                 static const char v[] = {
1003                         D_IRQ_CPU, D_IRQ_SUM, D_SOFTIRQS,
1004                         D_IRQ_SUM + D_IRQ_CPU + D_SOFTIRQS
1005                 };
1006                 i = index_in_strings("CPU\0SUM\0SCPU\0ALL\0", opt_irq_fmt);
1007                 if (i == -1)
1008                         bb_show_usage();
1009                 G.options |= v[i];
1010         }
1011
1012         if ((opt & OPT_UTIL) /* -u? */
1013          || G.options == 0  /* nothing? (use default then) */
1014         ) {
1015                 G.options |= D_CPU;
1016         }
1017
1018         if (opt & OPT_SETCPU) {
1019                 char *t;
1020                 G.p_option = 1;
1021
1022                 for (t = strtok(opt_set_cpu, ","); t; t = strtok(NULL, ",")) {
1023                         if (strcmp(t, "ALL") == 0) {
1024                                 /* Select every CPU */
1025                                 memset(G.cpu_bitmap, 0xff, G.cpu_bitmap_len);
1026                         } else {
1027                                 /* Get CPU number */
1028                                 unsigned n = xatoi_u(t);
1029                                 if (n >= G.cpu_nr)
1030                                         bb_error_msg_and_die("not that many processors");
1031                                 n++;
1032                                 G.cpu_bitmap[n >> 3] |= 1 << (n & 7);
1033                         }
1034                 }
1035         }
1036
1037         if (!G.p_option)
1038                 /* Display global stats */
1039                 G.cpu_bitmap[0] = 1;
1040
1041         /* Get time */
1042         get_localtime(&G.timestamp[0]);
1043
1044         /* Display header */
1045         print_header(&G.timestamp[0]);
1046
1047         /* The main loop */
1048         main_loop();
1049
1050         if (ENABLE_FEATURE_CLEAN_UP) {
1051                 /* Clean up */
1052                 for (i = 0; i < 3; i++) {
1053                         free(G.st_cpu[i]);
1054                         free(G.st_irq[i]);
1055                         free(G.st_irqcpu[i]);
1056                         free(G.st_softirqcpu[i]);
1057                 }
1058                 free(G.cpu_bitmap);
1059                 free(&G);
1060         }
1061
1062         return EXIT_SUCCESS;
1063 }