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