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 
this_is_smp(void)112 static ALWAYS_INLINE int this_is_smp(void)
113 {
114 	return (G.total_cpus > 1);
115 }
116 
print_header(void)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 
get_localtime(struct tm * ptm)132 static void get_localtime(struct tm *ptm)
133 {
134 	time_t timer;
135 	time(&timer);
136 	localtime_r(&timer, ptm);
137 }
138 
print_timestamp(void)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 
get_smp_uptime(void)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 */
get_cpu_statistics(stats_cpu_t * sc)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 
get_interval(cputime_t old,cputime_t new)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 */
overflow_safe_sub(cputime_t prev,cputime_t curr)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
overflow_safe_sub(cputime_t prev,cputime_t curr)227 static ALWAYS_INLINE cputime_t overflow_safe_sub(cputime_t prev, cputime_t curr)
228 {
229 	return curr - prev;
230 }
231 #endif
232 
percent_value(cputime_t prev,cputime_t curr,cputime_t itv)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 
print_stats_cpu_struct(stats_cpu_pair_t * stats)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 
cpu_report(stats_cpu_pair_t * stats)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 
print_stats_dev_struct(stats_dev_t * stats_dev,cputime_t itv)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 
print_devstat_header(void)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  */
is_partition(const char * dev)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 
stats_dev_find_or_new(const char * dev_name)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 
stats_dev_free(stats_dev_t * stats_dev)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 
do_disk_statistics(cputime_t itv)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 
dev_report(cputime_t itv)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;
iostat_main(int argc UNUSED_PARAM,char ** argv)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 }
531