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