| /* vi: set sw=4 ts=4: */ | 
 | /* | 
 |  * Report CPU and I/O stats, based on sysstat version 9.1.2 by Sebastien Godard | 
 |  * | 
 |  * Copyright (C) 2010 Marek Polacek <mmpolacek@gmail.com> | 
 |  * | 
 |  * Licensed under GPLv2, see file LICENSE in this source tree. | 
 |  */ | 
 |  | 
 | //config:config IOSTAT | 
 | //config:	bool "iostat" | 
 | //config:	default y | 
 | //config:	help | 
 | //config:	  Report CPU and I/O statistics | 
 |  | 
 | //applet:IF_IOSTAT(APPLET(iostat, BB_DIR_BIN, BB_SUID_DROP)) | 
 |  | 
 | //kbuild:lib-$(CONFIG_IOSTAT) += iostat.o | 
 |  | 
 | #include "libbb.h" | 
 | #include <sys/utsname.h>  /* struct utsname */ | 
 |  | 
 | //#define debug(fmt, ...) fprintf(stderr, fmt, ## __VA_ARGS__) | 
 | #define debug(fmt, ...) ((void)0) | 
 |  | 
 | #define MAX_DEVICE_NAME 12 | 
 | #define MAX_DEVICE_NAME_STR "12" | 
 |  | 
 | #if 1 | 
 | typedef unsigned long long cputime_t; | 
 | typedef long long icputime_t; | 
 | # define FMT_DATA "ll" | 
 | # define CPUTIME_MAX (~0ULL) | 
 | #else | 
 | typedef unsigned long cputime_t; | 
 | typedef long icputime_t; | 
 | # define FMT_DATA "l" | 
 | # define CPUTIME_MAX (~0UL) | 
 | #endif | 
 |  | 
 | enum { | 
 | 	STATS_CPU_USER, | 
 | 	STATS_CPU_NICE, | 
 | 	STATS_CPU_SYSTEM, | 
 | 	STATS_CPU_IDLE, | 
 | 	STATS_CPU_IOWAIT, | 
 | 	STATS_CPU_IRQ, | 
 | 	STATS_CPU_SOFTIRQ, | 
 | 	STATS_CPU_STEAL, | 
 | 	STATS_CPU_GUEST, | 
 |  | 
 | 	GLOBAL_UPTIME, | 
 | 	SMP_UPTIME, | 
 |  | 
 | 	N_STATS_CPU, | 
 | }; | 
 |  | 
 | typedef struct { | 
 | 	cputime_t vector[N_STATS_CPU]; | 
 | } stats_cpu_t; | 
 |  | 
 | typedef struct { | 
 | 	stats_cpu_t *prev; | 
 | 	stats_cpu_t *curr; | 
 | 	cputime_t itv; | 
 | } stats_cpu_pair_t; | 
 |  | 
 | typedef struct { | 
 | 	unsigned long long rd_sectors; | 
 | 	unsigned long long wr_sectors; | 
 | 	unsigned long rd_ops; | 
 | 	unsigned long wr_ops; | 
 | } stats_dev_data_t; | 
 |  | 
 | typedef struct stats_dev { | 
 | 	struct stats_dev *next; | 
 | 	char dname[MAX_DEVICE_NAME + 1]; | 
 | 	stats_dev_data_t prev_data; | 
 | 	stats_dev_data_t curr_data; | 
 | } stats_dev_t; | 
 |  | 
 | /* Globals. Sort by size and access frequency. */ | 
 | struct globals { | 
 | 	smallint show_all; | 
 | 	unsigned total_cpus;            /* Number of CPUs */ | 
 | 	unsigned clk_tck;               /* Number of clock ticks per second */ | 
 | 	llist_t *dev_name_list;         /* List of devices entered on the command line */ | 
 | 	stats_dev_t *stats_dev_list; | 
 | 	struct tm tmtime; | 
 | 	struct { | 
 | 		const char *str; | 
 | 		unsigned div; | 
 | 	} unit; | 
 | }; | 
 | #define G (*ptr_to_globals) | 
 | #define INIT_G() do { \ | 
 | 	SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ | 
 | 	G.unit.str = "Blk"; \ | 
 | 	G.unit.div = 1; \ | 
 | } while (0) | 
 |  | 
 | /* Must match option string! */ | 
 | enum { | 
 | 	OPT_c = 1 << 0, | 
 | 	OPT_d = 1 << 1, | 
 | 	OPT_t = 1 << 2, | 
 | 	OPT_z = 1 << 3, | 
 | 	OPT_k = 1 << 4, | 
 | 	OPT_m = 1 << 5, | 
 | }; | 
 |  | 
 | static ALWAYS_INLINE unsigned get_user_hz(void) | 
 | { | 
 | 	return sysconf(_SC_CLK_TCK); | 
 | } | 
 |  | 
 | static ALWAYS_INLINE int this_is_smp(void) | 
 | { | 
 | 	return (G.total_cpus > 1); | 
 | } | 
 |  | 
 | static void print_header(void) | 
 | { | 
 | 	char buf[32]; | 
 | 	struct utsname uts; | 
 |  | 
 | 	uname(&uts); /* never fails */ | 
 |  | 
 | 	/* Date representation for the current locale */ | 
 | 	strftime(buf, sizeof(buf), "%x", &G.tmtime); | 
 |  | 
 | 	printf("%s %s (%s) \t%s \t_%s_\t(%u CPU)\n\n", | 
 | 			uts.sysname, uts.release, uts.nodename, | 
 | 			buf, uts.machine, G.total_cpus); | 
 | } | 
 |  | 
 | static void get_localtime(struct tm *ptm) | 
 | { | 
 | 	time_t timer; | 
 | 	time(&timer); | 
 | 	localtime_r(&timer, ptm); | 
 | } | 
 |  | 
 | static void print_timestamp(void) | 
 | { | 
 | 	char buf[64]; | 
 | 	/* %x: date representation for the current locale */ | 
 | 	/* %X: time representation for the current locale */ | 
 | 	strftime(buf, sizeof(buf), "%x %X", &G.tmtime); | 
 | 	printf("%s\n", buf); | 
 | } | 
 |  | 
 | static cputime_t get_smp_uptime(void) | 
 | { | 
 | 	FILE *fp; | 
 | 	unsigned long sec, dec; | 
 |  | 
 | 	fp = xfopen_for_read("/proc/uptime"); | 
 |  | 
 | 	if (fscanf(fp, "%lu.%lu", &sec, &dec) != 2) | 
 | 		bb_error_msg_and_die("can't read '%s'", "/proc/uptime"); | 
 |  | 
 | 	fclose(fp); | 
 |  | 
 | 	return (cputime_t)sec * G.clk_tck + dec * G.clk_tck / 100; | 
 | } | 
 |  | 
 | /* Fetch CPU statistics from /proc/stat */ | 
 | static void get_cpu_statistics(stats_cpu_t *sc) | 
 | { | 
 | 	FILE *fp; | 
 | 	char buf[1024]; | 
 |  | 
 | 	fp = xfopen_for_read("/proc/stat"); | 
 |  | 
 | 	memset(sc, 0, sizeof(*sc)); | 
 |  | 
 | 	while (fgets(buf, sizeof(buf), fp)) { | 
 | 		int i; | 
 | 		char *ibuf; | 
 |  | 
 | 		/* Does the line start with "cpu "? */ | 
 | 		if (!starts_with_cpu(buf) || buf[3] != ' ') { | 
 | 			continue; | 
 | 		} | 
 | 		ibuf = buf + 4; | 
 | 		for (i = STATS_CPU_USER; i <= STATS_CPU_GUEST; i++) { | 
 | 			ibuf = skip_whitespace(ibuf); | 
 | 			sscanf(ibuf, "%"FMT_DATA"u", &sc->vector[i]); | 
 | 			if (i != STATS_CPU_GUEST) { | 
 | 				sc->vector[GLOBAL_UPTIME] += sc->vector[i]; | 
 | 			} | 
 | 			ibuf = skip_non_whitespace(ibuf); | 
 | 		} | 
 | 		break; | 
 | 	} | 
 |  | 
 | 	if (this_is_smp()) { | 
 | 		sc->vector[SMP_UPTIME] = get_smp_uptime(); | 
 | 	} | 
 |  | 
 | 	fclose(fp); | 
 | } | 
 |  | 
 | static ALWAYS_INLINE cputime_t get_interval(cputime_t old, cputime_t new) | 
 | { | 
 | 	cputime_t itv = new - old; | 
 |  | 
 | 	return (itv == 0) ? 1 : itv; | 
 | } | 
 |  | 
 | #if CPUTIME_MAX > 0xffffffff | 
 | /* | 
 |  * Handle overflow conditions properly for counters which can have | 
 |  * less bits than cputime_t, depending on the kernel version. | 
 |  */ | 
 | /* Surprisingly, on 32bit inlining is a size win */ | 
 | static ALWAYS_INLINE cputime_t overflow_safe_sub(cputime_t prev, cputime_t curr) | 
 | { | 
 | 	cputime_t v = curr - prev; | 
 |  | 
 | 	if ((icputime_t)v < 0     /* curr < prev - counter overflow? */ | 
 | 	 && prev <= 0xffffffff /* kernel uses 32bit value for the counter? */ | 
 | 	) { | 
 | 		/* Add 33th bit set to 1 to curr, compensating for the overflow */ | 
 | 		/* double shift defeats "warning: left shift count >= width of type" */ | 
 | 		v += ((cputime_t)1 << 16) << 16; | 
 | 	} | 
 | 	return v; | 
 | } | 
 | #else | 
 | static ALWAYS_INLINE cputime_t overflow_safe_sub(cputime_t prev, cputime_t curr) | 
 | { | 
 | 	return curr - prev; | 
 | } | 
 | #endif | 
 |  | 
 | static double percent_value(cputime_t prev, cputime_t curr, cputime_t itv) | 
 | { | 
 | 	return ((double)overflow_safe_sub(prev, curr)) / itv * 100; | 
 | } | 
 |  | 
 | static void print_stats_cpu_struct(stats_cpu_pair_t *stats) | 
 | { | 
 | 	cputime_t *p = stats->prev->vector; | 
 | 	cputime_t *c = stats->curr->vector; | 
 | 	printf("        %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f\n", | 
 | 		percent_value(p[STATS_CPU_USER]  , c[STATS_CPU_USER]  , stats->itv), | 
 | 		percent_value(p[STATS_CPU_NICE]  , c[STATS_CPU_NICE]  , stats->itv), | 
 | 		percent_value(p[STATS_CPU_SYSTEM] + p[STATS_CPU_SOFTIRQ] + p[STATS_CPU_IRQ], | 
 | 			c[STATS_CPU_SYSTEM] + c[STATS_CPU_SOFTIRQ] + c[STATS_CPU_IRQ], stats->itv), | 
 | 		percent_value(p[STATS_CPU_IOWAIT], c[STATS_CPU_IOWAIT], stats->itv), | 
 | 		percent_value(p[STATS_CPU_STEAL] , c[STATS_CPU_STEAL] , stats->itv), | 
 | 		percent_value(p[STATS_CPU_IDLE]  , c[STATS_CPU_IDLE]  , stats->itv) | 
 | 	); | 
 | } | 
 |  | 
 | static void cpu_report(stats_cpu_pair_t *stats) | 
 | { | 
 | 	/* Always print a header */ | 
 | 	puts("avg-cpu:  %user   %nice %system %iowait  %steal   %idle"); | 
 |  | 
 | 	/* Print current statistics */ | 
 | 	print_stats_cpu_struct(stats); | 
 | } | 
 |  | 
 | static void print_stats_dev_struct(stats_dev_t *stats_dev, cputime_t itv) | 
 | { | 
 | 	stats_dev_data_t *p = &stats_dev->prev_data; | 
 | 	stats_dev_data_t *c = &stats_dev->curr_data; | 
 | 	if (option_mask32 & OPT_z) | 
 | 		if (p->rd_ops == c->rd_ops && p->wr_ops == c->wr_ops) | 
 | 			return; | 
 |  | 
 | 	printf("%-13s %8.2f %12.2f %12.2f %10llu %10llu\n", | 
 | 		stats_dev->dname, | 
 | 		percent_value(p->rd_ops + p->wr_ops, c->rd_ops + c->wr_ops, itv), | 
 | 		percent_value(p->rd_sectors, c->rd_sectors, itv) / G.unit.div, | 
 | 		percent_value(p->wr_sectors, c->wr_sectors, itv) / G.unit.div, | 
 | 		(c->rd_sectors - p->rd_sectors) / G.unit.div, | 
 | 		(c->wr_sectors - p->wr_sectors) / G.unit.div | 
 | 	); | 
 | } | 
 |  | 
 | static void print_devstat_header(void) | 
 | { | 
 | 	printf("Device:%15s%6s%s/s%6s%s/s%6s%s%6s%s\n", | 
 | 		"tps", | 
 | 		G.unit.str, "_read", G.unit.str, "_wrtn", | 
 | 		G.unit.str, "_read", G.unit.str, "_wrtn" | 
 | 	); | 
 | } | 
 |  | 
 | /* | 
 |  * Is input partition of format [sdaN]? | 
 |  */ | 
 | static int is_partition(const char *dev) | 
 | { | 
 | 	/* Ok, this is naive... */ | 
 | 	return ((dev[0] - 's') | (dev[1] - 'd') | (dev[2] - 'a')) == 0 && isdigit(dev[3]); | 
 | } | 
 |  | 
 | static stats_dev_t *stats_dev_find_or_new(const char *dev_name) | 
 | { | 
 | 	stats_dev_t **curr = &G.stats_dev_list; | 
 |  | 
 | 	while (*curr != NULL) { | 
 | 		if (strcmp((*curr)->dname, dev_name) == 0) | 
 | 			return *curr; | 
 | 		curr = &(*curr)->next; | 
 | 	} | 
 |  | 
 | 	*curr = xzalloc(sizeof(stats_dev_t)); | 
 | 	strncpy((*curr)->dname, dev_name, MAX_DEVICE_NAME); | 
 | 	return *curr; | 
 | } | 
 |  | 
 | static void stats_dev_free(stats_dev_t *stats_dev) | 
 | { | 
 | 	if (stats_dev) { | 
 | 		stats_dev_free(stats_dev->next); | 
 | 		free(stats_dev); | 
 | 	} | 
 | } | 
 |  | 
 | static void do_disk_statistics(cputime_t itv) | 
 | { | 
 | 	char buf[128]; | 
 | 	char dev_name[MAX_DEVICE_NAME + 1]; | 
 | 	unsigned long long rd_sec_or_dummy; | 
 | 	unsigned long long wr_sec_or_dummy; | 
 | 	stats_dev_data_t *curr_data; | 
 | 	stats_dev_t *stats_dev; | 
 | 	FILE *fp; | 
 | 	int rc; | 
 |  | 
 | 	fp = xfopen_for_read("/proc/diskstats"); | 
 | 	/* Read and possibly print stats from /proc/diskstats */ | 
 | 	while (fgets(buf, sizeof(buf), fp)) { | 
 | 		sscanf(buf, "%*s %*s %"MAX_DEVICE_NAME_STR"s", dev_name); | 
 | 		if (G.dev_name_list) { | 
 | 			/* Is device name in list? */ | 
 | 			if (!llist_find_str(G.dev_name_list, dev_name)) | 
 | 				continue; | 
 | 		} else if (is_partition(dev_name)) { | 
 | 			continue; | 
 | 		} | 
 |  | 
 | 		stats_dev = stats_dev_find_or_new(dev_name); | 
 | 		curr_data = &stats_dev->curr_data; | 
 |  | 
 | 		rc = sscanf(buf, "%*s %*s %*s %lu %llu %llu %llu %lu %*s %llu", | 
 | 			&curr_data->rd_ops, | 
 | 			&rd_sec_or_dummy, | 
 | 			&curr_data->rd_sectors, | 
 | 			&wr_sec_or_dummy, | 
 | 			&curr_data->wr_ops, | 
 | 			&curr_data->wr_sectors); | 
 | 		if (rc != 6) { | 
 | 			curr_data->rd_sectors = rd_sec_or_dummy; | 
 | 			curr_data->wr_sectors = wr_sec_or_dummy; | 
 | 			//curr_data->rd_ops = ; | 
 | 			curr_data->wr_ops = (unsigned long)curr_data->rd_sectors; | 
 | 		} | 
 |  | 
 | 		if (!G.dev_name_list /* User didn't specify device */ | 
 | 		 && !G.show_all | 
 | 		 && curr_data->rd_ops == 0 | 
 | 		 && curr_data->wr_ops == 0 | 
 | 		) { | 
 | 			/* Don't print unused device */ | 
 | 			continue; | 
 | 		} | 
 |  | 
 | 		/* Print current statistics */ | 
 | 		print_stats_dev_struct(stats_dev, itv); | 
 | 		stats_dev->prev_data = *curr_data; | 
 | 	} | 
 |  | 
 | 	fclose(fp); | 
 | } | 
 |  | 
 | static void dev_report(cputime_t itv) | 
 | { | 
 | 	/* Always print a header */ | 
 | 	print_devstat_header(); | 
 |  | 
 | 	/* Fetch current disk statistics */ | 
 | 	do_disk_statistics(itv); | 
 | } | 
 |  | 
 | //usage:#define iostat_trivial_usage | 
 | //usage:       "[-c] [-d] [-t] [-z] [-k|-m] [ALL|BLOCKDEV...] [INTERVAL [COUNT]]" | 
 | //usage:#define iostat_full_usage "\n\n" | 
 | //usage:       "Report CPU and I/O statistics\n" | 
 | //usage:     "\n	-c	Show CPU utilization" | 
 | //usage:     "\n	-d	Show device utilization" | 
 | //usage:     "\n	-t	Print current time" | 
 | //usage:     "\n	-z	Omit devices with no activity" | 
 | //usage:     "\n	-k	Use kb/s" | 
 | //usage:     "\n	-m	Use Mb/s" | 
 |  | 
 | int iostat_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | 
 | int iostat_main(int argc UNUSED_PARAM, char **argv) | 
 | { | 
 | 	int opt; | 
 | 	unsigned interval; | 
 | 	int count; | 
 | 	stats_cpu_t stats_data[2]; | 
 | 	smallint current_stats; | 
 |  | 
 | 	INIT_G(); | 
 |  | 
 | 	memset(&stats_data, 0, sizeof(stats_data)); | 
 |  | 
 | 	/* Get number of clock ticks per sec */ | 
 | 	G.clk_tck = get_user_hz(); | 
 |  | 
 | 	/* Determine number of CPUs */ | 
 | 	G.total_cpus = get_cpu_count(); | 
 | 	if (G.total_cpus == 0) | 
 | 		G.total_cpus = 1; | 
 |  | 
 | 	/* Parse and process arguments */ | 
 | 	/* -k and -m are mutually exclusive */ | 
 | 	opt_complementary = "k--m:m--k"; | 
 | 	opt = getopt32(argv, "cdtzkm"); | 
 | 	if (!(opt & (OPT_c + OPT_d))) | 
 | 		/* Default is -cd */ | 
 | 		opt |= OPT_c + OPT_d; | 
 |  | 
 | 	argv += optind; | 
 |  | 
 | 	/* Store device names into device list */ | 
 | 	while (*argv && !isdigit(*argv[0])) { | 
 | 		if (strcmp(*argv, "ALL") != 0) { | 
 | 			/* If not ALL, save device name */ | 
 | 			char *dev_name = skip_dev_pfx(*argv); | 
 | 			if (!llist_find_str(G.dev_name_list, dev_name)) { | 
 | 				llist_add_to(&G.dev_name_list, dev_name); | 
 | 			} | 
 | 		} else { | 
 | 			G.show_all = 1; | 
 | 		} | 
 | 		argv++; | 
 | 	} | 
 |  | 
 | 	interval = 0; | 
 | 	count = 1; | 
 | 	if (*argv) { | 
 | 		/* Get interval */ | 
 | 		interval = xatoi_positive(*argv); | 
 | 		count = (interval != 0 ? -1 : 1); | 
 | 		argv++; | 
 | 		if (*argv) | 
 | 			/* Get count value */ | 
 | 			count = xatoi_positive(*argv); | 
 | 	} | 
 |  | 
 | 	if (opt & OPT_m) { | 
 | 		G.unit.str = " MB"; | 
 | 		G.unit.div = 2048; | 
 | 	} | 
 |  | 
 | 	if (opt & OPT_k) { | 
 | 		G.unit.str = " kB"; | 
 | 		G.unit.div = 2; | 
 | 	} | 
 |  | 
 | 	get_localtime(&G.tmtime); | 
 |  | 
 | 	/* Display header */ | 
 | 	print_header(); | 
 |  | 
 | 	current_stats = 0; | 
 | 	/* Main loop */ | 
 | 	for (;;) { | 
 | 		stats_cpu_pair_t stats; | 
 |  | 
 | 		stats.prev = &stats_data[current_stats ^ 1]; | 
 | 		stats.curr = &stats_data[current_stats]; | 
 |  | 
 | 		/* Fill the time structure */ | 
 | 		get_localtime(&G.tmtime); | 
 |  | 
 | 		/* Fetch current CPU statistics */ | 
 | 		get_cpu_statistics(stats.curr); | 
 |  | 
 | 		/* Get interval */ | 
 | 		stats.itv = get_interval( | 
 | 			stats.prev->vector[GLOBAL_UPTIME], | 
 | 			stats.curr->vector[GLOBAL_UPTIME] | 
 | 		); | 
 |  | 
 | 		if (opt & OPT_t) | 
 | 			print_timestamp(); | 
 |  | 
 | 		if (opt & OPT_c) { | 
 | 			cpu_report(&stats); | 
 | 			if (opt & OPT_d) | 
 | 				/* Separate outputs by a newline */ | 
 | 				bb_putchar('\n'); | 
 | 		} | 
 |  | 
 | 		if (opt & OPT_d) { | 
 | 			if (this_is_smp()) { | 
 | 				stats.itv = get_interval( | 
 | 					stats.prev->vector[SMP_UPTIME], | 
 | 					stats.curr->vector[SMP_UPTIME] | 
 | 				); | 
 | 			} | 
 | 			dev_report(stats.itv); | 
 | 		} | 
 |  | 
 | 		bb_putchar('\n'); | 
 |  | 
 | 		if (count > 0) { | 
 | 			if (--count == 0) | 
 | 				break; | 
 | 		} | 
 |  | 
 | 		/* Swap stats */ | 
 | 		current_stats ^= 1; | 
 |  | 
 | 		sleep(interval); | 
 | 	} | 
 |  | 
 | 	if (ENABLE_FEATURE_CLEAN_UP) { | 
 | 		llist_free(G.dev_name_list, NULL); | 
 | 		stats_dev_free(G.stats_dev_list); | 
 | 		free(&G); | 
 | 	} | 
 |  | 
 | 	return EXIT_SUCCESS; | 
 | } |