diff options
-rw-r--r-- | procps/iostat.c | 615 |
1 files changed, 615 insertions, 0 deletions
diff --git a/procps/iostat.c b/procps/iostat.c new file mode 100644 index 000000000..573419e1c --- /dev/null +++ b/procps/iostat.c @@ -0,0 +1,615 @@ +/* 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 tarball for details. + */ + +//applet:IF_IOSTAT(APPLET(iostat, _BB_DIR_BIN, _BB_SUID_DROP)) + +//kbuild:lib-$(CONFIG_IOSTAT) += iostat.o + +//config:config IOSTAT +//config: bool "iostat" +//config: default y +//config: help +//config: Report CPU and I/O statistics + +#include "libbb.h" +#include <sys/utsname.h> /* Need struct utsname */ + +#define debug(fmt, ...) fprintf(stderr, fmt, ## __VA_ARGS__) +//#define debug(fmt, ...) ((void)0) + +#define MAX_DEVICE_NAME 12 +#define CURRENT 0 +#define LAST 1 + +#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 + +struct stats_cpu { + cputime_t cpu_user; + cputime_t cpu_nice; + cputime_t cpu_system; + cputime_t cpu_idle; + cputime_t cpu_iowait; + cputime_t cpu_steal; + cputime_t cpu_irq; + cputime_t cpu_softirq; + cputime_t cpu_guest; +}; + +struct stats_dev { + char dname[MAX_DEVICE_NAME]; + unsigned long long rd_sectors; + unsigned long long wr_sectors; + unsigned long rd_ops; + unsigned long wr_ops; +}; + +/* List of devices entered on the command line */ +struct device_list { + char dname[MAX_DEVICE_NAME]; +}; + +/* Globals. Sort by size and access frequency. */ +struct globals { + smallint show_all; + unsigned devlist_i; /* Index to the list of devices */ + unsigned total_cpus; /* Number of CPUs */ + unsigned clk_tck; /* Number of clock ticks per second */ + struct device_list *dlist; + struct stats_dev *saved_stats_dev; + struct tm tmtime; +}; +#define G (*ptr_to_globals) +#define INIT_G() do { \ + SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ +} 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[16]; + struct utsname uts; + + if (uname(&uts) < 0) + bb_perror_msg_and_die("uname"); + + strftime(buf, sizeof(buf), "%x", &G.tmtime); + + printf("%s %s (%s) \t%s \t_%s_\t(%d CPU)\n\n", + uts.sysname, uts.release, uts.nodename, + buf, uts.machine, G.total_cpus); +} + +static int get_number_of_cpus(void) +{ +#ifdef _SC_NPROCESSORS_CONF + return sysconf(_SC_NPROCESSORS_CONF); +#else + char buf[128]; + int n = 0; + FILE *fp; + + fp = xfopen_for_read("/proc/cpuinfo"); + + while (fgets(buf, sizeof(buf), fp)) + if (strncmp(buf, "processor\t:", 11) == 0) + n++; + + fclose(fp); + return n; +#endif +} + +static void get_localtime(struct tm *ptm) +{ + time_t timer; + time(&timer); + localtime_r(&timer, ptm); +} + +static void print_timestamp(void) +{ + char buf[20]; + strftime(buf, sizeof(buf), "%x %X", &G.tmtime); + printf("%s\n", buf); +} + +/* Does str start with "cpu"? */ +static int starts_with_cpu(const char *str) +{ + return ((str[0] - 'c') | (str[1] - 'p') | (str[2] - 'u')) == 0; +} + +/* Fetch CPU statistics from /proc/stat */ +static void get_cpu_statistics(struct stats_cpu *sc) +{ + FILE *fp; + char buf[1024]; + + fp = xfopen_for_read("/proc/stat"); + + memset(sc, 0, sizeof(*sc)); + + while (fgets(buf, sizeof(buf), fp)) { + /* Does the line starts with "cpu "? */ + if (starts_with_cpu(buf) && buf[3] == ' ') { + sscanf(buf + 4 + 1, + "%"FMT_DATA"u %"FMT_DATA"u %"FMT_DATA"u %"FMT_DATA"u %" + FMT_DATA"u %"FMT_DATA"u %"FMT_DATA"u %"FMT_DATA"u %"FMT_DATA"u", + &sc->cpu_user, &sc->cpu_nice, &sc->cpu_system, + &sc->cpu_idle, &sc->cpu_iowait, &sc->cpu_irq, + &sc->cpu_softirq, &sc->cpu_steal, &sc->cpu_guest); + } + } + + fclose(fp); +} + +static cputime_t get_smp_uptime(void) +{ + FILE *fp; + char buf[sizeof(long)*3 * 2 + 4]; + unsigned long sec, dec; + + fp = xfopen_for_read("/proc/uptime"); + + if (fgets(buf, sizeof(buf), fp)) + if (sscanf(buf, "%lu.%lu", &sec, &dec) != 2) + bb_error_msg_and_die("can't read /proc/uptime"); + + fclose(fp); + + return (cputime_t)sec * G.clk_tck + dec * G.clk_tck / 100; +} + +/* + * Obtain current uptime in jiffies. + * Uptime is sum of individual CPUs' uptimes. + */ +static cputime_t get_uptime(const struct stats_cpu *sc) +{ + /* NB: Don't include cpu_guest, it is already in cpu_user */ + return sc->cpu_user + sc->cpu_nice + sc->cpu_system + sc->cpu_idle + + + sc->cpu_iowait + sc->cpu_irq + sc->cpu_steal + sc->cpu_softirq; +} + +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(const struct stats_cpu *p, + const struct stats_cpu *c, cputime_t itv) +{ + printf(" %6.2f %6.2f %6.2f %6.2f %6.2f %6.2f\n", + percent_value(p->cpu_user , c->cpu_user , itv), + percent_value(p->cpu_nice , c->cpu_nice , itv), + percent_value(p->cpu_system + p->cpu_softirq + p->cpu_irq, + c->cpu_system + c->cpu_softirq + c->cpu_irq, itv), + percent_value(p->cpu_iowait , c->cpu_iowait , itv), + percent_value(p->cpu_steal , c->cpu_steal , itv), + percent_value(p->cpu_idle , c->cpu_idle , itv) + ); +} + +static void print_stats_dev_struct(const struct stats_dev *p, + const struct stats_dev *c, cputime_t itv) +{ + int unit = 1; + + if (option_mask32 & OPT_k) + unit = 2; + else if (option_mask32 & OPT_m) + unit = 2048; + + if (option_mask32 & OPT_z) + if (p->rd_ops == c->rd_ops && p->wr_ops == c->wr_ops) + return; + + printf("%-13s", c->dname); + printf(" %8.2f %12.2f %12.2f %10llu %10llu \n", + percent_value(p->rd_ops + p->wr_ops , + /**/ c->rd_ops + c->wr_ops , itv), + percent_value(p->rd_sectors, c->rd_sectors, itv) / unit, + percent_value(p->wr_sectors, c->wr_sectors, itv) / unit, + (c->rd_sectors - p->rd_sectors) / unit, + (c->wr_sectors - p->wr_sectors) / unit); +} + +static void cpu_report(const struct stats_cpu *last, + const struct stats_cpu *cur, + cputime_t itv) +{ + /* Always print a header */ + puts("avg-cpu: %user %nice %system %iowait %steal %idle"); + + /* Print current statistics */ + print_stats_cpu_struct(last, cur, itv); +} + +static void print_devstat_header(void) +{ + printf("Device: tps"); + + if (option_mask32 & OPT_m) + puts(" MB_read/s MB_wrtn/s MB_read MB_wrtn"); + else if (option_mask32 & OPT_k) + puts(" kB_read/s kB_wrtn/s kB_read kB_wrtn"); + else + puts(" Blk_read/s Blk_wrtn/s Blk_read Blk_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]); +} + +/* + * Return number of numbers on cmdline. + * Reasonable values are only 0 (no interval/count specified), + * 1 (interval specified) and 2 (both interval and count specified) + */ +static int numbers_on_cmdline(int argc, char *argv[]) +{ + int sum = 0; + + if (isdigit(argv[argc-1][0])) + sum++; + if (argc > 2 && isdigit(argv[argc-2][0])) + sum++; + + return sum; +} + +static int is_dev_in_dlist(const char *dev) +{ + int i; + + /* Go through the device list */ + for (i = 0; i < G.devlist_i; i++) + if (strcmp(G.dlist[i].dname, dev) == 0) + /* Found a match */ + return 1; + + /* No match found */ + return 0; +} + +static void do_disk_statistics(cputime_t itv) +{ + FILE *fp; + int rc; + int i = 0; + char buf[128]; + unsigned major, minor; + unsigned long wr_ops, dummy; /* %*lu for suppres the conversion wouldn't work */ + unsigned long long rd_sec_or_wr_ops; + unsigned long long rd_sec_or_dummy, wr_sec_or_dummy, wr_sec; + struct stats_dev sd; + + fp = xfopen_for_read("/proc/diskstats"); + + /* Read and possibly print stats from /proc/diskstats */ + while (fgets(buf, sizeof(buf), fp)) { + rc = sscanf(buf, "%u %u %s %lu %llu %llu %llu %lu %lu %llu %lu %lu %lu %lu", + &major, &minor, sd.dname, &sd.rd_ops, + &rd_sec_or_dummy, &rd_sec_or_wr_ops, &wr_sec_or_dummy, + &wr_ops, &dummy, &wr_sec, &dummy, &dummy, &dummy, &dummy); + + switch (rc) { + case 14: + sd.wr_ops = wr_ops; + sd.rd_sectors = rd_sec_or_wr_ops; + sd.wr_sectors = wr_sec; + break; + case 7: + sd.rd_sectors = rd_sec_or_dummy; + sd.wr_ops = (unsigned long)rd_sec_or_wr_ops; + sd.wr_sectors = wr_sec_or_dummy; + break; + default: + break; + } + + if (!G.devlist_i && !is_partition(sd.dname)) { + /* User didn't specify device */ + if (!G.show_all && !sd.rd_ops && !sd.wr_ops) { + /* Don't print unused device */ + continue; + } + print_stats_dev_struct(&G.saved_stats_dev[i], &sd, itv); + G.saved_stats_dev[i] = sd; + i++; + } else { + /* Is device in device list? */ + if (is_dev_in_dlist(sd.dname)) { + /* Print current statistics */ + print_stats_dev_struct(&G.saved_stats_dev[i], &sd, itv); + G.saved_stats_dev[i] = sd; + i++; + } else + continue; + } + } +} + +static void dev_report(cputime_t itv) +{ + /* Always print a header */ + print_devstat_header(); + + /* Fetch current disk statistics */ + do_disk_statistics(itv); +} + +static void save_to_devlist(const char *dname) +{ + int i; + struct device_list *tmp = G.dlist; + + if (strncmp(dname, "/dev/", 5) == 0) + /* We'll ignore prefix '/dev/' */ + dname += 5; + + /* Go through the list */ + for (i = 0; i < G.devlist_i; i++, tmp++) + if (strcmp(tmp->dname, dname) == 0) + /* Already in the list */ + return; + + /* Add device name to the list */ + strncpy(tmp->dname, dname, MAX_DEVICE_NAME - 1); + + /* Update device list index */ + G.devlist_i++; +} + +static unsigned get_number_of_devices(void) +{ + FILE *fp; + char buf[128]; + int rv; + unsigned n = 0; + unsigned long rd_ops, wr_ops; + char dname[MAX_DEVICE_NAME]; + + fp = xfopen_for_read("/proc/diskstats"); + + while (fgets(buf, sizeof(buf), fp)) { + rv = sscanf(buf, "%*d %*d %s %lu %*u %*u %*u %lu", + dname, &rd_ops, &wr_ops); + if (rv == 2 || is_partition(dname)) + /* A partition */ + continue; + if (!rd_ops && !wr_ops) { + /* Unused device */ + if (!G.show_all) + continue; + } + n++; + } + + fclose(fp); + return n; +} + +static int number_of_ALL_on_cmdline(char **argv) +{ + int alls = 0; + + /* Iterate over cmd line arguments, count "ALL" */ + while (*argv) + if (strcmp(*argv++, "ALL") == 0) + alls++; + + return alls; +} + +//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: "\nOptions:" +//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, char **argv) +{ + int opt, dev_num; + unsigned interval = 0; + int count = 0; + cputime_t global_uptime[2] = { 0 }; + cputime_t smp_uptime[2] = { 0 }; + cputime_t itv; + struct stats_cpu stats_cur, stats_last; + + INIT_G(); + + memset(&stats_last, 0, sizeof(stats_last)); + + /* Get number of clock ticks per sec */ + G.clk_tck = get_user_hz(); + + /* Determine number of CPUs */ + G.total_cpus = get_number_of_cpus(); + + /* 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; + argc -= optind; + + dev_num = argc - numbers_on_cmdline(argc, argv); + /* We don't want to allocate space for 'ALL' */ + dev_num -= number_of_ALL_on_cmdline(argv); + if (dev_num > 0) + /* Make space for device list */ + G.dlist = xzalloc(sizeof(G.dlist[0]) * dev_num); + + /* Store device names into device list */ + while (*argv && !isdigit(*argv[0])) { + if (strcmp(*argv, "ALL") != 0) + /* If not ALL, save device name */ + save_to_devlist(*argv); + else + G.show_all = 1; + argv++; + } + + if (*argv) { + /* Get interval */ + interval = xatoi_u(*argv); + count = interval ? -1 : 1; + argv++; + if (*argv) + /* Get count value */ + count = xatoi_u(*argv); + } + + /* Allocate space for device stats */ + if (opt & OPT_d) { + G.saved_stats_dev = xzalloc(sizeof(G.saved_stats_dev[0]) * + (dev_num ? dev_num : get_number_of_devices()) + ); + } + + /* Display header */ + print_header(); + + /* Main loop */ + for (;;) { + /* Fill the time structure */ + get_localtime(&G.tmtime); + + /* Fetch current CPU statistics */ + get_cpu_statistics(&stats_cur); + + /* Fetch current uptime */ + global_uptime[CURRENT] = get_uptime(&stats_cur); + + /* Get interval */ + itv = get_interval(global_uptime[LAST], global_uptime[CURRENT]); + + if (opt & OPT_t) + print_timestamp(); + + if (opt & OPT_c) { + cpu_report(&stats_last, &stats_cur, itv); + if (opt & OPT_d) + /* Separate outputs by a newline */ + bb_putchar('\n'); + } + + if (opt & OPT_d) { + if (this_is_smp()) { + smp_uptime[CURRENT] = get_smp_uptime(); + itv = get_interval(smp_uptime[LAST], smp_uptime[CURRENT]); + smp_uptime[LAST] = smp_uptime[CURRENT]; + } + dev_report(itv); + } + + if (count > 0) { + if (--count == 0) + break; + } + + /* Backup current stats */ + global_uptime[LAST] = global_uptime[CURRENT]; + stats_last = stats_cur; + + bb_putchar('\n'); + sleep(interval); + } + + bb_putchar('\n'); + + if (ENABLE_FEATURE_CLEAN_UP) { + free(&G); + free(G.dlist); + free(G.saved_stats_dev); + } + + return EXIT_SUCCESS; +} |