iostat: code shrink ~0.5k
[oweals/busybox.git] / procps / iostat.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Report CPU and I/O stats, 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 source tree.
8  */
9
10 //applet:IF_IOSTAT(APPLET(iostat, BB_DIR_BIN, BB_SUID_DROP))
11
12 //kbuild:lib-$(CONFIG_IOSTAT) += iostat.o
13
14 //config:config IOSTAT
15 //config:       bool "iostat"
16 //config:       default y
17 //config:       help
18 //config:         Report CPU and I/O statistics
19
20 #include "libbb.h"
21 #include <sys/utsname.h>  /* Need struct utsname */
22
23 //#define debug(fmt, ...) fprintf(stderr, fmt, ## __VA_ARGS__)
24 #define debug(fmt, ...) ((void)0)
25
26 #define MAX_DEVICE_NAME 12
27
28 #if 1
29 typedef unsigned long long cputime_t;
30 typedef long long icputime_t;
31 # define FMT_DATA "ll"
32 # define CPUTIME_MAX (~0ULL)
33 #else
34 typedef unsigned long cputime_t;
35 typedef long icputime_t;
36 # define FMT_DATA "l"
37 # define CPUTIME_MAX (~0UL)
38 #endif
39
40 enum {
41         STATS_CPU_USER,
42         STATS_CPU_NICE,
43         STATS_CPU_SYSTEM,
44         STATS_CPU_IDLE,
45         STATS_CPU_IOWAIT,
46         STATS_CPU_IRQ,
47         STATS_CPU_SOFTIRQ,
48         STATS_CPU_STEAL,
49         STATS_CPU_GUEST,
50
51         GLOBAL_UPTIME,
52         SMP_UPTIME,
53
54         N_STATS_CPU,
55 };
56
57 typedef struct {
58         cputime_t vector[N_STATS_CPU];
59 } stats_cpu_t;
60
61 typedef struct {
62         stats_cpu_t *prev;
63         stats_cpu_t *curr;
64         cputime_t itv;
65 } stats_cpu_pair_t;
66
67 struct stats_dev {
68         char dname[MAX_DEVICE_NAME];
69         unsigned long long rd_sectors;
70         unsigned long long wr_sectors;
71         unsigned long rd_ops;
72         unsigned long wr_ops;
73 };
74
75 /* Globals. Sort by size and access frequency. */
76 struct globals {
77         smallint show_all;
78         unsigned total_cpus;            /* Number of CPUs */
79         unsigned clk_tck;               /* Number of clock ticks per second */
80         llist_t *dev_list;              /* List of devices entered on the command line */
81         struct stats_dev *saved_stats_dev;
82         struct tm tmtime;
83         struct {
84                 const char *str;
85                 unsigned div;
86         } unit;
87 };
88 #define G (*ptr_to_globals)
89 #define INIT_G() do { \
90         SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
91         G.unit.str = "Blk"; \
92         G.unit.div = 1; \
93 } while (0)
94
95 /* Must match option string! */
96 enum {
97         OPT_c = 1 << 0,
98         OPT_d = 1 << 1,
99         OPT_t = 1 << 2,
100         OPT_z = 1 << 3,
101         OPT_k = 1 << 4,
102         OPT_m = 1 << 5,
103 };
104
105 static ALWAYS_INLINE unsigned get_user_hz(void)
106 {
107         return sysconf(_SC_CLK_TCK);
108 }
109
110 static ALWAYS_INLINE int this_is_smp(void)
111 {
112         return (G.total_cpus > 1);
113 }
114
115 static void print_header(void)
116 {
117         char buf[16];
118         struct utsname uts;
119
120         uname(&uts); /* never fails */
121
122         /* Date representation for the current locale */
123         strftime(buf, sizeof(buf), "%x", &G.tmtime);
124
125         printf("%s %s (%s) \t%s \t_%s_\t(%u CPU)\n\n",
126                         uts.sysname, uts.release, uts.nodename,
127                         buf, uts.machine, G.total_cpus);
128 }
129
130 static void get_localtime(struct tm *ptm)
131 {
132         time_t timer;
133         time(&timer);
134         localtime_r(&timer, ptm);
135 }
136
137 static void print_timestamp(void)
138 {
139         char buf[20];
140         /* %x: date representation for the current locale */
141         /* %X: time representation for the current locale */
142         strftime(buf, sizeof(buf), "%x %X", &G.tmtime);
143         printf("%s\n", buf);
144 }
145
146 static cputime_t get_smp_uptime(void)
147 {
148         FILE *fp;
149         unsigned long sec, dec;
150
151         fp = xfopen_for_read("/proc/uptime");
152
153         if (fscanf(fp, "%lu.%lu", &sec, &dec) != 2)
154                 bb_error_msg_and_die("can't read '%s'", "/proc/uptime");
155
156         fclose(fp);
157
158         return (cputime_t)sec * G.clk_tck + dec * G.clk_tck / 100;
159 }
160
161 /* Fetch CPU statistics from /proc/stat */
162 static void get_cpu_statistics(stats_cpu_t *sc)
163 {
164         FILE *fp;
165         char buf[1024], *ibuf = buf + 4;
166
167         fp = xfopen_for_read("/proc/stat");
168
169         memset(sc, 0, sizeof(*sc));
170
171         while (fgets(buf, sizeof(buf), fp)) {
172                 /* Does the line starts with "cpu "? */
173                 if (!starts_with_cpu(buf) || buf[3] != ' ') {
174                         continue;
175                 }
176                 for (int i = STATS_CPU_USER; i <= STATS_CPU_GUEST; i++) {
177                         ibuf = skip_whitespace(ibuf);
178                         sscanf(ibuf, "%"FMT_DATA"u", &sc->vector[i]);
179                         if (i != STATS_CPU_GUEST) {
180                                 sc->vector[GLOBAL_UPTIME] += sc->vector[i];
181                         }
182                         ibuf = skip_non_whitespace(ibuf);
183                 }
184                 break;
185         }
186
187         if (this_is_smp()) {
188                 sc->vector[SMP_UPTIME] = get_smp_uptime();
189         }
190
191         fclose(fp);
192 }
193
194 static ALWAYS_INLINE cputime_t get_interval(cputime_t old, cputime_t new)
195 {
196         cputime_t itv = new - old;
197
198         return (itv == 0) ? 1 : itv;
199 }
200
201 #if CPUTIME_MAX > 0xffffffff
202 /*
203  * Handle overflow conditions properly for counters which can have
204  * less bits than cputime_t, depending on the kernel version.
205  */
206 /* Surprisingly, on 32bit inlining is a size win */
207 static ALWAYS_INLINE cputime_t overflow_safe_sub(cputime_t prev, cputime_t curr)
208 {
209         cputime_t v = curr - prev;
210
211         if ((icputime_t)v < 0     /* curr < prev - counter overflow? */
212          && prev <= 0xffffffff /* kernel uses 32bit value for the counter? */
213         ) {
214                 /* Add 33th bit set to 1 to curr, compensating for the overflow */
215                 /* double shift defeats "warning: left shift count >= width of type" */
216                 v += ((cputime_t)1 << 16) << 16;
217         }
218         return v;
219 }
220 #else
221 static ALWAYS_INLINE cputime_t overflow_safe_sub(cputime_t prev, cputime_t curr)
222 {
223         return curr - prev;
224 }
225 #endif
226
227 static double percent_value(cputime_t prev, cputime_t curr, cputime_t itv)
228 {
229         return ((double)overflow_safe_sub(prev, curr)) / itv * 100;
230 }
231
232 static void print_stats_cpu_struct(stats_cpu_pair_t *stats)
233 {
234         cputime_t *p = stats->prev->vector;
235         cputime_t *c = stats->curr->vector;
236         printf("         %6.2f  %6.2f  %6.2f  %6.2f  %6.2f  %6.2f\n",
237                 percent_value(p[STATS_CPU_USER]  , c[STATS_CPU_USER]  , stats->itv),
238                 percent_value(p[STATS_CPU_NICE]  , c[STATS_CPU_NICE]  , stats->itv),
239                 percent_value(p[STATS_CPU_SYSTEM] + p[STATS_CPU_SOFTIRQ] + p[STATS_CPU_IRQ],
240                         c[STATS_CPU_SYSTEM] + c[STATS_CPU_SOFTIRQ] + c[STATS_CPU_IRQ], stats->itv),
241                 percent_value(p[STATS_CPU_IOWAIT], c[STATS_CPU_IOWAIT], stats->itv),
242                 percent_value(p[STATS_CPU_STEAL] , c[STATS_CPU_STEAL] , stats->itv),
243                 percent_value(p[STATS_CPU_IDLE]  , c[STATS_CPU_IDLE]  , stats->itv)
244         );
245 }
246
247 static void print_stats_dev_struct(const struct stats_dev *p,
248                 const struct stats_dev *c, cputime_t itv)
249 {
250         if (option_mask32 & OPT_z)
251                 if (p->rd_ops == c->rd_ops && p->wr_ops == c->wr_ops)
252                         return;
253
254         printf("%-13s %8.2f %12.2f %12.2f %10llu %10llu \n", c->dname,
255                 percent_value(p->rd_ops + p->wr_ops ,
256                 /**/              c->rd_ops + c->wr_ops , itv),
257                 percent_value(p->rd_sectors, c->rd_sectors, itv) / G.unit.div,
258                 percent_value(p->wr_sectors, c->wr_sectors, itv) / G.unit.div,
259                 (c->rd_sectors - p->rd_sectors) / G.unit.div,
260                 (c->wr_sectors - p->wr_sectors) / G.unit.div);
261 }
262
263 static void cpu_report(stats_cpu_pair_t *stats)
264 {
265         /* Always print a header */
266         puts("avg-cpu:  %user   %nice %system %iowait  %steal   %idle");
267
268         /* Print current statistics */
269         print_stats_cpu_struct(stats);
270 }
271
272 static void print_devstat_header(void)
273 {
274         printf("Device:%15s%6s%s/s%6s%s/s%6s%s%6s%s\n", "tps",
275                 G.unit.str, "_read", G.unit.str, "_wrtn",
276                 G.unit.str, "_read", G.unit.str, "_wrtn");
277 }
278
279 /*
280  * Is input partition of format [sdaN]?
281  */
282 static int is_partition(const char *dev)
283 {
284         /* Ok, this is naive... */
285         return ((dev[0] - 's') | (dev[1] - 'd') | (dev[2] - 'a')) == 0 && isdigit(dev[3]);
286 }
287
288 static void do_disk_statistics(cputime_t itv)
289 {
290         FILE *fp;
291         int rc;
292         int i = 0;
293         char buf[128];
294         unsigned major, minor;
295         unsigned long wr_ops, dummy; /* %*lu for suppress the conversion wouldn't work */
296         unsigned long long rd_sec_or_wr_ops;
297         unsigned long long rd_sec_or_dummy, wr_sec_or_dummy, wr_sec;
298         struct stats_dev sd;
299
300         fp = xfopen_for_read("/proc/diskstats");
301
302         /* Read and possibly print stats from /proc/diskstats */
303         while (fgets(buf, sizeof(buf), fp)) {
304                 rc = sscanf(buf, "%u %u %s %lu %llu %llu %llu %lu %lu %llu %lu %lu %lu %lu",
305                         &major, &minor, sd.dname, &sd.rd_ops,
306                         &rd_sec_or_dummy, &rd_sec_or_wr_ops, &wr_sec_or_dummy,
307                         &wr_ops, &dummy, &wr_sec, &dummy, &dummy, &dummy, &dummy);
308
309                 switch (rc) {
310                 case 14:
311                         sd.wr_ops = wr_ops;
312                         sd.rd_sectors = rd_sec_or_wr_ops;
313                         sd.wr_sectors = wr_sec;
314                         break;
315                 case 7:
316                         sd.rd_sectors = rd_sec_or_dummy;
317                         sd.wr_ops = (unsigned long)rd_sec_or_wr_ops;
318                         sd.wr_sectors = wr_sec_or_dummy;
319                         break;
320                 default:
321                         break;
322                 }
323
324                 if (!G.dev_list && !is_partition(sd.dname)) {
325                         /* User didn't specify device */
326                         if (!G.show_all && !sd.rd_ops && !sd.wr_ops) {
327                                 /* Don't print unused device */
328                                 continue;
329                         }
330                         print_stats_dev_struct(&G.saved_stats_dev[i], &sd, itv);
331                         G.saved_stats_dev[i] = sd;
332                         i++;
333                 } else {
334                         /* Is device in device list? */
335                         if (llist_find_str(G.dev_list, sd.dname)) {
336                                 /* Print current statistics */
337                                 print_stats_dev_struct(&G.saved_stats_dev[i], &sd, itv);
338                                 G.saved_stats_dev[i] = sd;
339                                 i++;
340                         } else
341                                 continue;
342                 }
343         }
344
345         fclose(fp);
346 }
347
348 static void dev_report(cputime_t itv)
349 {
350         /* Always print a header */
351         print_devstat_header();
352
353         /* Fetch current disk statistics */
354         do_disk_statistics(itv);
355 }
356
357 static unsigned get_number_of_devices(void)
358 {
359         FILE *fp;
360         char buf[128];
361         int rv;
362         unsigned n = 0;
363         unsigned long rd_ops, wr_ops;
364         char dname[MAX_DEVICE_NAME];
365
366         fp = xfopen_for_read("/proc/diskstats");
367
368         while (fgets(buf, sizeof(buf), fp)) {
369                 rv = sscanf(buf, "%*d %*d %s %lu %*u %*u %*u %lu",
370                                 dname, &rd_ops, &wr_ops);
371                 if (rv == 2 || is_partition(dname))
372                         /* A partition */
373                         continue;
374                 if (!rd_ops && !wr_ops) {
375                         /* Unused device */
376                         if (!G.show_all)
377                                 continue;
378                 }
379                 n++;
380         }
381
382         fclose(fp);
383         return n;
384 }
385
386 //usage:#define iostat_trivial_usage
387 //usage:       "[-c] [-d] [-t] [-z] [-k|-m] [ALL|BLOCKDEV...] [INTERVAL [COUNT]]"
388 //usage:#define iostat_full_usage "\n\n"
389 //usage:       "Report CPU and I/O statistics\n"
390 //usage:     "\nOptions:"
391 //usage:     "\n        -c      Show CPU utilization"
392 //usage:     "\n        -d      Show device utilization"
393 //usage:     "\n        -t      Print current time"
394 //usage:     "\n        -z      Omit devices with no activity"
395 //usage:     "\n        -k      Use kb/s"
396 //usage:     "\n        -m      Use Mb/s"
397
398 int iostat_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
399 int iostat_main(int argc UNUSED_PARAM, char **argv)
400 {
401         int opt, dev_num;
402         unsigned interval;
403         int count;
404         stats_cpu_t stats_data[2];
405         smallint current_stats;
406
407         INIT_G();
408
409         memset(&stats_data, 0, sizeof(stats_data));
410
411         /* Get number of clock ticks per sec */
412         G.clk_tck = get_user_hz();
413
414         /* Determine number of CPUs */
415         G.total_cpus = get_cpu_count();
416         if (G.total_cpus == 0)
417                 G.total_cpus = 1;
418
419         /* Parse and process arguments */
420         /* -k and -m are mutually exclusive */
421         opt_complementary = "k--m:m--k";
422         opt = getopt32(argv, "cdtzkm");
423         if (!(opt & (OPT_c + OPT_d)))
424                 /* Default is -cd */
425                 opt |= OPT_c + OPT_d;
426
427         argv += optind;
428
429         /* Store device names into device list */
430         dev_num = 0;
431         while (*argv && !isdigit(*argv[0])) {
432                 if (strcmp(*argv, "ALL") != 0) {
433                         /* If not ALL, save device name */
434                         char *dev_name = skip_dev_pfx(*argv);
435                         if (!llist_find_str(G.dev_list, dev_name)) {
436                                 llist_add_to(&G.dev_list, dev_name);
437                                 dev_num++;
438                         }
439                 } else {
440                         G.show_all = 1;
441                 }
442                 argv++;
443         }
444
445         interval = 0;
446         count = 1;
447         if (*argv) {
448                 /* Get interval */
449                 interval = xatoi_positive(*argv);
450                 count = (interval != 0 ? -1 : 1);
451                 argv++;
452                 if (*argv)
453                         /* Get count value */
454                         count = xatoi_positive(*argv);
455         }
456
457         /* Allocate space for device stats */
458         if (opt & OPT_d) {
459                 G.saved_stats_dev = xzalloc(sizeof(G.saved_stats_dev[0]) *
460                                 (dev_num ? dev_num : get_number_of_devices())
461                 );
462         }
463
464         if (opt & OPT_m) {
465                 G.unit.str = " MB";
466                 G.unit.div = 2048;
467         }
468
469         if (opt & OPT_k) {
470                 G.unit.str = " kB";
471                 G.unit.div = 2;
472         }
473
474         get_localtime(&G.tmtime);
475
476         /* Display header */
477         print_header();
478
479         current_stats = 0;
480         /* Main loop */
481         for (;;) {
482                 stats_cpu_pair_t stats;
483
484                 stats.prev = &stats_data[current_stats ^ 1];
485                 stats.curr = &stats_data[current_stats];
486
487                 /* Fill the time structure */
488                 get_localtime(&G.tmtime);
489
490                 /* Fetch current CPU statistics */
491                 get_cpu_statistics(stats.curr);
492
493                 /* Get interval */
494                 stats.itv = get_interval(
495                         stats.prev->vector[GLOBAL_UPTIME],
496                         stats.curr->vector[GLOBAL_UPTIME]
497                 );
498
499                 if (opt & OPT_t)
500                         print_timestamp();
501
502                 if (opt & OPT_c) {
503                         cpu_report(&stats);
504                         if (opt & OPT_d)
505                                 /* Separate outputs by a newline */
506                                 bb_putchar('\n');
507                 }
508
509                 if (opt & OPT_d) {
510                         if (this_is_smp()) {
511                                 stats.itv = get_interval(
512                                         stats.prev->vector[SMP_UPTIME],
513                                         stats.curr->vector[SMP_UPTIME]
514                                 );
515                         }
516                         dev_report(stats.itv);
517                 }
518
519                 bb_putchar('\n');
520
521                 if (count > 0) {
522                         if (--count == 0)
523                                 break;
524                 }
525
526                 /* Swap stats */
527                 current_stats ^= 1;
528
529                 sleep(interval);
530         }
531
532         if (ENABLE_FEATURE_CLEAN_UP) {
533                 llist_free(G.dev_list, NULL);
534                 free(G.saved_stats_dev);
535                 free(&G);
536         }
537
538         return EXIT_SUCCESS;
539 }