/* * iostat: report CPU and I/O statistics * (C) 1998-2022 by Sebastien GODARD (sysstat orange.fr) * *************************************************************************** * This program is free software; you can redistribute it and/or modify it * * under the terms of the GNU General Public License as published by the * * Free Software Foundation; either version 2 of the License, or (at your * * option) any later version. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without the implied warranty of MERCHANTABILITY * * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * * for more details. * * * * You should have received a copy of the GNU General Public License along * * with this program; if not, write to the Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA * *************************************************************************** */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "version.h" #include "iostat.h" #include "ioconf.h" #include "rd_stats.h" #include "count.h" #include /* For setlocale() */ #ifdef USE_NLS #include #define _(string) gettext(string) #else #define _(string) (string) #endif #ifdef USE_SCCSID #define SCCSID "@(#)sysstat-" VERSION ": " __FILE__ " compiled " __DATE__ " " __TIME__ char *sccsid(void) { return (SCCSID); } #endif #ifdef TEST extern int __env; #endif struct stats_cpu *st_cpu[2]; unsigned long long uptime_cs[2] = {0, 0}; unsigned long long tot_jiffies[2] = {0, 0}; struct io_device *dev_list = NULL; /* Number of decimal places */ int dplaces_nr = -1; int group_nr = 0; /* Nb of device groups */ int cpu_nr = 0; /* Nb of processors on the machine */ int flags = 0; /* Flag for common options and system state */ long interval = 0; char timestamp[TIMESTAMP_LEN]; char alt_dir[MAX_FILE_LEN]; struct sigaction alrm_act, int_act; int sigint_caught = 0; /* *************************************************************************** * Print usage and exit. * * IN: * @progname Name of sysstat command. *************************************************************************** */ void usage(char *progname) { fprintf(stderr, _("Usage: %s [ options ] [ [ ] ]\n"), progname); #ifdef DEBUG fprintf(stderr, _("Options are:\n" "[ -c ] [ -d ] [ -h ] [ -k | -m ] [ -N ] [ -s ] [ -t ] [ -V ] [ -x ] [ -y ] [ -z ]\n" "[ { -f | +f } ] [ -j { ID | LABEL | PATH | UUID | ... } ]\n" "[ --compact ] [ --dec={ 0 | 1 | 2 } ] [ --human ] [ --pretty ] [ -o JSON ]\n" "[ [ -H ] -g ] [ -p [ [,...] | ALL ] ]\n" "[ [...] | ALL ] [ --debuginfo ]\n")); #else fprintf(stderr, _("Options are:\n" "[ -c ] [ -d ] [ -h ] [ -k | -m ] [ -N ] [ -s ] [ -t ] [ -V ] [ -x ] [ -y ] [ -z ]\n" "[ { -f | +f } ] [ -j { ID | LABEL | PATH | UUID | ... } ]\n" "[ --compact ] [ --dec={ 0 | 1 | 2 } ] [ --human ] [ --pretty ] [ -o JSON ]\n" "[ [ -H ] -g ] [ -p [ [,...] | ALL ] ]\n" "[ [...] | ALL ]\n")); #endif exit(1); } /* *************************************************************************** * Set disk output unit. Unit will be kB/s unless POSIXLY_CORRECT * environment variable has been set, in which case the output will be * expressed in blocks/s. *************************************************************************** */ void set_disk_output_unit(void) { if (DISPLAY_KILOBYTES(flags) || DISPLAY_MEGABYTES(flags)) return; /* Check POSIXLY_CORRECT environment variable */ if (__getenv(ENV_POSIXLY_CORRECT) == NULL) { /* Variable not set: Unit is kB/s and not blocks/s */ flags |= I_D_KILOBYTES; } } /* *************************************************************************** * Get device mapper name (e.g. "dm-0") from its registered name (e.g. * "virtualhd-home"). * * IN: * @name Registered name of the device (e.g. "virtualhd-home"). * * RETURNS: * Name of the device mapper name (e.g. "dm-0"). *************************************************************************** */ char *get_dm_name_from_registered_name(char *registered_name) { int n; char filen[PATH_MAX]; char target[PATH_MAX]; /* * The registered device name is a symlink pointing at its device mapper name * in the /dev/mapper directory. */ n = snprintf(filen, sizeof(filen), "%s/%s", DEVMAP_DIR, registered_name); if ((n >= sizeof(filen)) || access(filen, F_OK)) { return (NULL); } /* Read symlink */ n = readlink(filen, target, PATH_MAX); if ((n <= 0) || (n >= PATH_MAX)) return (NULL); target[n] = '\0'; /* ... and get device mapper name it points at */ return basename(target); } /* *************************************************************************** * SIGALRM signal handler. No need to reset the handler here. * * IN: * @sig Signal number. *************************************************************************** */ void alarm_handler(int sig) { alarm(interval); } /* *************************************************************************** * SIGINT signal handler. * * IN: * @sig Signal number. ************************************************************************** */ void int_handler(int sig) { sigint_caught = 1; } /* *************************************************************************** * Initialize stats common structures. *************************************************************************** */ void init_stats(void) { int i; /* Allocate structures for CPUs "all" and 0 */ for (i = 0; i < 2; i++) { if ((st_cpu[i] = (struct stats_cpu *) malloc(STATS_CPU_SIZE * 2)) == NULL) { perror("malloc"); exit(4); } memset(st_cpu[i], 0, STATS_CPU_SIZE * 2); } } /* *************************************************************************** * Set every device entry to nonexistent status. * * IN: * @dlist Pointer on the start of the linked list. *************************************************************************** */ void set_devices_nonexistent(struct io_device *dlist) { while (dlist != NULL) { dlist->exist = FALSE; dlist = dlist->next; } } /* *************************************************************************** * Get device major and minor numbers. * * IN: * @filename Name of the device ("sda", "/dev/sdb1"...) * * OUT: * @major Major number of the device. * @minor Minor number of the device. * * RETURNS: * 0 on success, and -1 otherwise. *************************************************************************** */ int get_major_minor_nr(char filename[], int *major, int *minor) { struct stat statbuf; char *bang; char dfile[MAX_PF_NAME]; snprintf(dfile, sizeof(dfile), "%s%s", filename[0] == '/' ? "" : SLASH_DEV, filename); dfile[sizeof(dfile) - 1] = '\0'; while ((bang = strchr(dfile, '!'))) { /* * Some devices may have had a slash replaced with a bang character (eg. cciss!c0d0...) * Restore their original names so that they can be found in /dev directory. */ *bang = '/'; } if (__stat(dfile, &statbuf) < 0) return -1; *major = __major(statbuf.st_rdev); *minor = __minor(statbuf.st_rdev); return 0; } /* *************************************************************************** * Check if a device is present in the list, and add it if requested. * Also look for its type (device or partition) and save it. * * IN: * @dlist Address of pointer on the start of the linked list. * @name Device name. * @dtype T_PART_DEV (=2) if the device and all its partitions should * also be read (option -p used), T_GROUP (=3) if it's a group * name, and 0 otherwise. * @major Major number of the device (set to UKWN_MAJ_NR by caller if * unknown: In this case, major and minor numbers will be * determined here). * @minor Minor number of the device. * * RETURNS: * Pointer on the io_device structure in the list where the device is located * (whether it was already in the list or if it has been added). * NULL if the device name is too long or if the device doesn't exist and we * don't want to add it. *************************************************************************** */ struct io_device *add_list_device(struct io_device **dlist, char *name, int dtype, int major, int minor) { struct io_device *d, *ds; int i, rc = 0, maj_nr, min_nr; char *dm_name; if (strnlen(name, MAX_NAME_LEN) == MAX_NAME_LEN) /* Device name is too long */ return NULL; while (*dlist != NULL) { d = *dlist; if ((i = strcmp(d->name, name)) == 0) { /* Device found in list */ if ((dtype == T_PART_DEV) && (d->dev_tp == T_DEV)) { d->dev_tp = dtype; } d->exist = TRUE; return d; } if (!GROUP_DEFINED(flags) && !DISPLAY_EVERYTHING(flags) && (i > 0)) /* * If no group defined and we don't use /proc/diskstats, * insert current device in alphabetical order. * NB: Using /proc/diskstats ("iostat -p ALL") is a bit better than * using alphabetical order because sda10 comes after sda9... */ break; dlist = &(d->next); } /* Device not found */ ds = *dlist; /* Add device to the list */ if ((*dlist = (struct io_device *) malloc(sizeof(struct io_device))) == NULL) { perror("malloc"); exit(4); } memset(*dlist, 0, sizeof(struct io_device)); d = *dlist; for (i = 0; i < 2; i++) { if ((d->dev_stats[i] = (struct io_stats *) malloc(sizeof(struct io_stats))) == NULL) { perror("malloc"); exit(4); } memset(d->dev_stats[i], 0, sizeof(struct io_stats)); } if (DISPLAY_DEVMAP_NAME(flags)) { /* * Save device mapper name (e.g. "dm-0") instead of * its registered name (e.g. "virtualhd-home") * This is because we won't read stats for a file named "virtualhd-home" but * for a file named "dm-0" (we will display "virtualhd-home" anyway at the end * because option -N has been used). */ dm_name = get_dm_name_from_registered_name(name); if (!dm_name) { dm_name = name; } strncpy(d->name, dm_name, sizeof(d->name) - 1); } else { strncpy(d->name, name, sizeof(d->name)); } d->name[MAX_NAME_LEN - 1] = '\0'; d->exist = TRUE; d->next = ds; if (dtype == T_GROUP) { d->dev_tp = dtype; } else { if (!alt_dir[0] || USE_ALL_DIR(flags)) { rc = is_device(SLASH_SYS, name, ACCEPT_VIRTUAL_DEVICES); } if (alt_dir[0] && (!USE_ALL_DIR(flags) || (USE_ALL_DIR(flags) && !rc))) { rc = is_device(alt_dir, name, ACCEPT_VIRTUAL_DEVICES); } if (rc) { d->dev_tp = (dtype == T_PART_DEV ? T_PART_DEV : T_DEV); } else { /* This is a partition (T_PART) */ d->dev_tp = T_PART; } /* Save major and minor numbers */ if (major != UKWN_MAJ_NR) { d->major = major; d->minor = minor; } else { /* Look for device major and minor numbers */ if (get_major_minor_nr(d->name, &maj_nr, &min_nr) == 0) { d->major = maj_nr; d->minor = min_nr; } } } return d; } /* *************************************************************************** * Read sysfs stat for current block device or partition. * * IN: * @filename File name where stats will be read. * @ios Structure where stats will be saved. * * OUT: * @ios Structure where stats have been saved. * * RETURNS: * 0 on success, -1 otherwise. *************************************************************************** */ int read_sysfs_file_stat_work(char *filename, struct io_stats *ios) { FILE *fp; struct io_stats sdev; int i; unsigned int ios_pgr, tot_ticks, rq_ticks, wr_ticks, dc_ticks, fl_ticks; unsigned long rd_ios, rd_merges_or_rd_sec, wr_ios, wr_merges; unsigned long rd_sec_or_wr_ios, wr_sec, rd_ticks_or_wr_sec; unsigned long dc_ios, dc_merges, dc_sec, fl_ios; /* Try to read given stat file */ if ((fp = fopen(filename, "r")) == NULL) return -1; i = fscanf(fp, "%lu %lu %lu %lu %lu %lu %lu %u %u %u %u %lu %lu %lu %u %lu %u", &rd_ios, &rd_merges_or_rd_sec, &rd_sec_or_wr_ios, &rd_ticks_or_wr_sec, &wr_ios, &wr_merges, &wr_sec, &wr_ticks, &ios_pgr, &tot_ticks, &rq_ticks, &dc_ios, &dc_merges, &dc_sec, &dc_ticks, &fl_ios, &fl_ticks); memset(&sdev, 0, sizeof(struct io_stats)); if (i >= 11) { /* Device or partition */ sdev.rd_ios = rd_ios; sdev.rd_merges = rd_merges_or_rd_sec; sdev.rd_sectors = rd_sec_or_wr_ios; sdev.rd_ticks = (unsigned int) rd_ticks_or_wr_sec; sdev.wr_ios = wr_ios; sdev.wr_merges = wr_merges; sdev.wr_sectors = wr_sec; sdev.wr_ticks = wr_ticks; sdev.ios_pgr = ios_pgr; sdev.tot_ticks = tot_ticks; sdev.rq_ticks = rq_ticks; if (i >= 15) { /* Discard I/O */ sdev.dc_ios = dc_ios; sdev.dc_merges = dc_merges; sdev.dc_sectors = dc_sec; sdev.dc_ticks = dc_ticks; } if (i >= 17) { /* Flush I/O */ sdev.fl_ios = fl_ios; sdev.fl_ticks = fl_ticks; } } else if (i == 4) { /* Partition without extended statistics */ sdev.rd_ios = rd_ios; sdev.rd_sectors = rd_merges_or_rd_sec; sdev.wr_ios = rd_sec_or_wr_ios; sdev.wr_sectors = rd_ticks_or_wr_sec; } *ios = sdev; fclose(fp); return 0; } /* *************************************************************************** * Read sysfs stat for current whole device using /sys or an alternate * location. * * IN: * @devname Device name for which stats have to be read. * @ios Structure where stats will be saved. * * OUT: * @ios Structure where stats have been saved. * * RETURNS: * 0 on success, -1 otherwise. *************************************************************************** */ int read_sysfs_file_stat(char *devname, struct io_stats *ios) { int rc = 0; char dfile[MAX_PF_NAME]; if (!alt_dir[0] || USE_ALL_DIR(flags)) { /* Read stats for current whole device using /sys/block/ directory */ snprintf(dfile, sizeof(dfile), "%s/%s/%s/%s", SLASH_SYS, __BLOCK, devname, S_STAT); dfile[sizeof(dfile) - 1] = '\0'; rc = read_sysfs_file_stat_work(dfile, ios); } if (alt_dir[0] && (!USE_ALL_DIR(flags) || (USE_ALL_DIR(flags) && (rc < 0)))) { /* Read stats for current whole device using an alternate /sys directory */ snprintf(dfile, sizeof(dfile), "%s/%s/%s/%s", alt_dir, __BLOCK, devname, S_STAT); dfile[sizeof(dfile) - 1] = '\0'; rc = read_sysfs_file_stat_work(dfile, ios); } return rc; } /* *************************************************************************** * Read sysfs stats for all the partitions of a whole device. Devices are * saved in the linked list. * * IN: * @curr Index in array for current sample statistics. * @dname Whole device name. * @sysdev sysfs location. * * RETURNS: * 0 on success, -1 otherwise. *************************************************************************** */ int read_sysfs_device_part_stat_work(int curr, char *dname, char *sysdev) { DIR *dir; struct dirent *drd; struct io_stats sdev; struct io_device *d; char dfile[MAX_PF_NAME], filename[MAX_PF_NAME + 512]; snprintf(dfile, sizeof(dfile), "%s/%s/%s", sysdev, __BLOCK, dname); dfile[sizeof(dfile) - 1] = '\0'; /* Open current device directory in /sys/block */ if ((dir = __opendir(dfile)) == NULL) return -1; /* Get current entry */ while ((drd = __readdir(dir)) != NULL) { if (!strcmp(drd->d_name, ".") || !strcmp(drd->d_name, "..")) continue; snprintf(filename, sizeof(filename), "%s/%s/%s", dfile, drd->d_name, S_STAT); filename[sizeof(filename) - 1] = '\0'; /* Read current partition stats */ if (read_sysfs_file_stat_work(filename, &sdev) < 0) continue; d = add_list_device(&dev_list, drd->d_name, 0, UKWN_MAJ_NR, 0); if (d != NULL) { *(d->dev_stats[curr]) = sdev; } } /* Close device directory */ __closedir(dir); return 0; } /* *************************************************************************** * Read sysfs stats for all the partitions of a whole device. * Stats are from /sys or an alternate directory. * * IN: * @curr Index in array for current sample statistics. * @dname Whole device name. * * RETURNS: * 0 on success, -1 otherwise. *************************************************************************** */ int read_sysfs_device_part_stat(int curr, char *dname) { int rc = 0; if (!alt_dir[0] || USE_ALL_DIR(flags)) { /* Read partition stats from /sys */ rc = read_sysfs_device_part_stat_work(curr, dname, SLASH_SYS); } if (alt_dir[0] && (!USE_ALL_DIR(flags) || (USE_ALL_DIR(flags) && (rc < 0)))) { /* Read partition stats from an alternate /sys directory */ rc = read_sysfs_device_part_stat_work(curr, dname, alt_dir); } return rc; } /* *************************************************************************** * Read sysfs stats for every whole device. Devices are saved in the linked * list. * * IN: * @curr Index in array for current sample statistics. * @sysblock __sys/block directory location. * * RETURNS: * 0 on success, -1 otherwise. *************************************************************************** */ int read_sysfs_all_devices_stat_work(int curr, char *sysblock) { DIR *dir; struct dirent *drd; struct io_stats sdev; struct io_device *d; char dfile[MAX_PF_NAME]; /* Open __sys/block directory */ if ((dir = __opendir(sysblock)) == NULL) return -1; /* Get current entry */ while ((drd = __readdir(dir)) != NULL) { if (!strcmp(drd->d_name, ".") || !strcmp(drd->d_name, "..")) continue; snprintf(dfile, sizeof(dfile), "%s/%s/%s", sysblock, drd->d_name, S_STAT); dfile[sizeof(dfile) - 1] = '\0'; /* Read current whole device stats */ if (read_sysfs_file_stat_work(dfile, &sdev) < 0) continue; d = add_list_device(&dev_list, drd->d_name, 0, UKWN_MAJ_NR, 0); if (d != NULL) { *(d->dev_stats[curr]) = sdev; } } /* Close device directory */ __closedir(dir); return 0; } /* *************************************************************************** * Read sysfs stats for every whole device from /sys or an alternate * location. * * IN: * @curr Index in array for current sample statistics. * * RETURNS: * 0 on success, -1 otherwise. *************************************************************************** */ int read_sysfs_all_devices_stat(int curr) { int rc = 0; char sysblock[MAX_PF_NAME]; if (!alt_dir[0] || USE_ALL_DIR(flags)) { /* Read all whole devices from /sys */ rc = read_sysfs_all_devices_stat_work(curr, SYSFS_BLOCK); } if (alt_dir[0]) { snprintf(sysblock, sizeof(sysblock), "%s/%s", alt_dir, __BLOCK); sysblock[sizeof(sysblock) - 1] = '\0'; /* Read stats from an alternate sys location */ rc = read_sysfs_all_devices_stat_work(curr, sysblock); } return rc; } /* *************************************************************************** * Read sysfs stats for a partition using __sys/dev/block/M:m/ directory. * * IN: * @curr Index in array for current sample statistics. * @d Device structure. * @sysdev sysfs directory. * * RETURNS: * 0 on success, and -1 otherwise. *************************************************************************** */ int read_sysfs_part_stat_work(int curr, struct io_device *d, char *sysdev) { char dfile[MAX_PF_NAME]; /* Read stats for device */ snprintf(dfile, sizeof(dfile), "%s/%s/%d:%d/%s", sysdev, __DEV_BLOCK, d->major, d->minor, S_STAT); dfile[sizeof(dfile) - 1] = '\0'; return read_sysfs_file_stat_work(dfile, d->dev_stats[curr]); } /* *************************************************************************** * Read sysfs stats for a partition using /sys/dev/block/M:m/ directory or * an alternate directory. * * IN: * @curr Index in array for current sample statistics. * @d Device structure. * * RETURNS: * 0 on success, and -1 otherwise. *************************************************************************** */ int read_sysfs_part_stat(int curr, struct io_device *d) { int rc = 0; if (!alt_dir[0] || USE_ALL_DIR(flags)) { /* Read partition stats from /sys */ rc = read_sysfs_part_stat_work(curr, d, SLASH_SYS); } if (alt_dir[0] && (!USE_ALL_DIR(flags) || (USE_ALL_DIR(flags) && (rc < 0)))) { /* Read partition stats from an alternate /sys directory */ rc = read_sysfs_part_stat_work(curr, d, alt_dir); } return rc; } /* *************************************************************************** * Read stats from the sysfs filesystem for the devices entered on the * command line. * * IN: * @curr Index in array for current sample statistics. *************************************************************************** */ void read_sysfs_dlist_stat(int curr) { struct io_device *dlist; for (dlist = dev_list; dlist != NULL; dlist = dlist->next) { if (dlist->exist) /* Device stats already read */ continue; else if (dlist->dev_tp == T_PART) { /* * This is a partition. * Read its stats using /sys/dev/block/M:n/ directory. */ if (read_sysfs_part_stat(curr, dlist) == 0) { dlist->exist = TRUE; } } else if ((dlist->dev_tp == T_PART_DEV) || (dlist->dev_tp == T_DEV)) { /* Read stats for current whole device using /sys/block/ directory */ if (read_sysfs_file_stat(dlist->name, dlist->dev_stats[curr]) == 0) { dlist->exist = TRUE; } if (dlist->dev_tp == T_PART_DEV) { /* Also read all its partitions now */ read_sysfs_device_part_stat(curr, dlist->name); } } } /* Read all whole devices stats if requested ("iostat ALL ...") */ if (DISPLAY_ALL_DEVICES(flags)) { read_sysfs_all_devices_stat(curr); } } /* *************************************************************************** * Read stats from the diskstats file. Only used when "-p ALL" has been * entered on the command line. * * IN: * @curr Index in array for current sample statistics. * @diskstats Path to diskstats file (e.g. "/proc/diskstats"). *************************************************************************** */ void read_diskstats_stat_work(int curr, char *diskstats) { FILE *fp; char line[256], dev_name[MAX_NAME_LEN]; struct io_device *d; struct io_stats sdev; int i; unsigned int ios_pgr, tot_ticks, rq_ticks, wr_ticks, dc_ticks, fl_ticks; unsigned long rd_ios, rd_merges_or_rd_sec, rd_ticks_or_wr_sec, wr_ios; unsigned long wr_merges, rd_sec_or_wr_ios, wr_sec; unsigned long dc_ios, dc_merges, dc_sec, fl_ios; unsigned int major, minor; if ((fp = fopen(diskstats, "r")) == NULL) return; while (fgets(line, sizeof(line), fp) != NULL) { memset(&sdev, 0, sizeof(struct io_stats)); /* major minor name rio rmerge rsect ruse wio wmerge wsect wuse running use aveq dcio dcmerge dcsect dcuse flio fltm */ i = sscanf(line, "%u %u %s %lu %lu %lu %lu %lu %lu %lu %u %u %u %u %lu %lu %lu %u %lu %u", &major, &minor, dev_name, &rd_ios, &rd_merges_or_rd_sec, &rd_sec_or_wr_ios, &rd_ticks_or_wr_sec, &wr_ios, &wr_merges, &wr_sec, &wr_ticks, &ios_pgr, &tot_ticks, &rq_ticks, &dc_ios, &dc_merges, &dc_sec, &dc_ticks, &fl_ios, &fl_ticks); if (i >= 14) { sdev.rd_ios = rd_ios; sdev.rd_merges = rd_merges_or_rd_sec; sdev.rd_sectors = rd_sec_or_wr_ios; sdev.rd_ticks = (unsigned int) rd_ticks_or_wr_sec; sdev.wr_ios = wr_ios; sdev.wr_merges = wr_merges; sdev.wr_sectors = wr_sec; sdev.wr_ticks = wr_ticks; sdev.ios_pgr = ios_pgr; sdev.tot_ticks = tot_ticks; sdev.rq_ticks = rq_ticks; if (i >= 18) { /* Discard I/O */ sdev.dc_ios = dc_ios; sdev.dc_merges = dc_merges; sdev.dc_sectors = dc_sec; sdev.dc_ticks = dc_ticks; } if (i >= 20) { /* Flush I/O */ sdev.fl_ios = fl_ios; sdev.fl_ticks = fl_ticks; } } else if (i == 7) { /* Partition without extended statistics */ if (DISPLAY_EXTENDED(flags)) continue; sdev.rd_ios = rd_ios; sdev.rd_sectors = rd_merges_or_rd_sec; sdev.wr_ios = rd_sec_or_wr_ios; sdev.wr_sectors = rd_ticks_or_wr_sec; } else /* Unknown entry: Ignore it */ continue; d = add_list_device(&dev_list, dev_name, 0, major, minor); if (d != NULL) { *d->dev_stats[curr] = sdev; } } fclose(fp); } /* *************************************************************************** * Read stats from /proc/diskstats or an alternate diskstats file. * Only used when "-p ALL" has been entered on the command line. * * IN: * @curr Index in array for current sample statistics. *************************************************************************** */ void read_diskstats_stat(int curr) { char diskstats[MAX_PF_NAME]; if (!alt_dir[0] || USE_ALL_DIR(flags)) { /* Read stats from /proc/diskstats */ read_diskstats_stat_work(curr, DISKSTATS); } if (alt_dir[0]) { snprintf(diskstats, sizeof(diskstats), "%s/%s", alt_dir, __DISKSTATS); diskstats[sizeof(diskstats) - 1] = '\0'; /* Read stats from an alternate diskstats file */ read_diskstats_stat_work(curr, diskstats); } } /* *************************************************************************** * Add current device statistics to corresponding group. * * IN: * @curr Index in array for current sample statistics. * @iodev_nr Number of devices and partitions. *************************************************************************** */ void compute_device_groups_stats(int curr, struct io_device *d, struct io_device *g) { if (!DISPLAY_UNFILTERED(flags)) { if (!d->dev_stats[curr]->rd_ios && !d->dev_stats[curr]->wr_ios && !d->dev_stats[curr]->dc_ios && !d->dev_stats[curr]->fl_ios) return; } g->dev_stats[curr]->rd_ios += d->dev_stats[curr]->rd_ios; g->dev_stats[curr]->rd_merges += d->dev_stats[curr]->rd_merges; g->dev_stats[curr]->rd_sectors += d->dev_stats[curr]->rd_sectors; g->dev_stats[curr]->rd_ticks += d->dev_stats[curr]->rd_ticks; g->dev_stats[curr]->wr_ios += d->dev_stats[curr]->wr_ios; g->dev_stats[curr]->wr_merges += d->dev_stats[curr]->wr_merges; g->dev_stats[curr]->wr_sectors += d->dev_stats[curr]->wr_sectors; g->dev_stats[curr]->wr_ticks += d->dev_stats[curr]->wr_ticks; g->dev_stats[curr]->dc_ios += d->dev_stats[curr]->dc_ios; g->dev_stats[curr]->dc_merges += d->dev_stats[curr]->dc_merges; g->dev_stats[curr]->dc_sectors += d->dev_stats[curr]->dc_sectors; g->dev_stats[curr]->dc_ticks += d->dev_stats[curr]->dc_ticks; g->dev_stats[curr]->fl_ios += d->dev_stats[curr]->fl_ios; g->dev_stats[curr]->fl_ticks += d->dev_stats[curr]->fl_ticks; g->dev_stats[curr]->ios_pgr += d->dev_stats[curr]->ios_pgr; g->dev_stats[curr]->tot_ticks += d->dev_stats[curr]->tot_ticks; g->dev_stats[curr]->rq_ticks += d->dev_stats[curr]->rq_ticks; } /* *************************************************************************** * Write current sample's timestamp, either in plain or JSON format. * * IN: * @tab Number of tabs to print. * @rectime Current date and time. *************************************************************************** */ void write_sample_timestamp(int tab, struct tm *rectime) { if (DISPLAY_ISO(flags)) { strftime(timestamp, sizeof(timestamp), "%FT%T%z", rectime); } else { strftime(timestamp, sizeof(timestamp), "%x %X", rectime); } if (DISPLAY_JSON_OUTPUT(flags)) { xprintf(tab, "\"timestamp\": \"%s\",", timestamp); } else { printf("%s\n", timestamp); } } /* *************************************************************************** * Display CPU utilization in plain format. * * IN: * @curr Index in array for current sample statistics. * @deltot_jiffies * Number of jiffies spent on the interval by all processors. *************************************************************************** */ void write_plain_cpu_stat(int curr, unsigned long long deltot_jiffies) { printf("avg-cpu: %%user %%nice %%system %%iowait %%steal %%idle\n"); printf(" "); cprintf_pc(DISPLAY_UNIT(flags), 6, 7, 2, ll_sp_value(st_cpu[!curr]->cpu_user, st_cpu[curr]->cpu_user, deltot_jiffies), ll_sp_value(st_cpu[!curr]->cpu_nice, st_cpu[curr]->cpu_nice, deltot_jiffies), /* * Time spent in system mode also includes time spent servicing * hard and soft interrupts. */ ll_sp_value(st_cpu[!curr]->cpu_sys + st_cpu[!curr]->cpu_softirq + st_cpu[!curr]->cpu_hardirq, st_cpu[curr]->cpu_sys + st_cpu[curr]->cpu_softirq + st_cpu[curr]->cpu_hardirq, deltot_jiffies), ll_sp_value(st_cpu[!curr]->cpu_iowait, st_cpu[curr]->cpu_iowait, deltot_jiffies), ll_sp_value(st_cpu[!curr]->cpu_steal, st_cpu[curr]->cpu_steal, deltot_jiffies), (st_cpu[curr]->cpu_idle < st_cpu[!curr]->cpu_idle) ? 0.0 : ll_sp_value(st_cpu[!curr]->cpu_idle, st_cpu[curr]->cpu_idle, deltot_jiffies)); printf("\n\n"); } /* *************************************************************************** * Display CPU utilization in JSON format. * * IN: * @tab Number of tabs to print. * @curr Index in array for current sample statistics. * @deltot_jiffies * Number of jiffies spent on the interval by all processors. *************************************************************************** */ void write_json_cpu_stat(int tab, int curr, unsigned long long deltot_jiffies) { xprintf0(tab, "\"avg-cpu\": {\"user\": %.2f, \"nice\": %.2f, \"system\": %.2f," " \"iowait\": %.2f, \"steal\": %.2f, \"idle\": %.2f}", ll_sp_value(st_cpu[!curr]->cpu_user, st_cpu[curr]->cpu_user, deltot_jiffies), ll_sp_value(st_cpu[!curr]->cpu_nice, st_cpu[curr]->cpu_nice, deltot_jiffies), /* * Time spent in system mode also includes time spent servicing * hard and soft interrupts. */ ll_sp_value(st_cpu[!curr]->cpu_sys + st_cpu[!curr]->cpu_softirq + st_cpu[!curr]->cpu_hardirq, st_cpu[curr]->cpu_sys + st_cpu[curr]->cpu_softirq + st_cpu[curr]->cpu_hardirq, deltot_jiffies), ll_sp_value(st_cpu[!curr]->cpu_iowait, st_cpu[curr]->cpu_iowait, deltot_jiffies), ll_sp_value(st_cpu[!curr]->cpu_steal, st_cpu[curr]->cpu_steal, deltot_jiffies), (st_cpu[curr]->cpu_idle < st_cpu[!curr]->cpu_idle) ? 0.0 : ll_sp_value(st_cpu[!curr]->cpu_idle, st_cpu[curr]->cpu_idle, deltot_jiffies)); } /* *************************************************************************** * Display CPU utilization in plain or JSON format. * * IN: * @curr Index in array for current sample statistics. * @tab Number of tabs to print (JSON format only). *************************************************************************** */ void write_cpu_stat(int curr, int tab) { unsigned long long deltot_jiffies; /* Total number of jiffies spent on the interval */ deltot_jiffies = get_interval(tot_jiffies[!curr], tot_jiffies[curr]); #ifdef DEBUG if (DISPLAY_DEBUG(flags)) { /* Debug output */ fprintf(stderr, "deltot_jiffies=%llu st_cpu[curr]{ cpu_user=%llu cpu_nice=%llu " "cpu_sys=%llu cpu_idle=%llu cpu_iowait=%llu cpu_steal=%llu " "cpu_hardirq=%llu cpu_softirq=%llu cpu_guest=%llu " "cpu_guest_nice=%llu }\n", deltot_jiffies, st_cpu[curr]->cpu_user, st_cpu[curr]->cpu_nice, st_cpu[curr]->cpu_sys, st_cpu[curr]->cpu_idle, st_cpu[curr]->cpu_iowait, st_cpu[curr]->cpu_steal, st_cpu[curr]->cpu_hardirq, st_cpu[curr]->cpu_softirq, st_cpu[curr]->cpu_guest, st_cpu[curr]->cpu_guest_nice); } #endif if (DISPLAY_JSON_OUTPUT(flags)) { write_json_cpu_stat(tab, curr, deltot_jiffies); } else { write_plain_cpu_stat(curr, deltot_jiffies); } } /* *************************************************************************** * Display disk stats header in plain or JSON format. * * OUT: * @fctr Conversion factor. * @tab Number of tabs to print (JSON format only). * @hpart Indicate which part of the report should be displayed in * human mode. A value of 0 indicates that output should not be * broken in several parts. *************************************************************************** */ void write_disk_stat_header(int *fctr, int *tab, int hpart) { char *units, *spc; if (DISPLAY_KILOBYTES(flags)) { *fctr = 2; units = "kB"; spc = " "; } else if (DISPLAY_MEGABYTES(flags)) { *fctr = 2048; units = "MB"; spc = " "; } else if (DISPLAY_EXTENDED(flags)) { units = "sec"; spc = ""; } else { units = "Blk"; spc = ""; } if (DISPLAY_JSON_OUTPUT(flags)) { xprintf((*tab)++, "\"disk\": ["); return; } if (!DISPLAY_PRETTY(flags)) { printf("Device "); } if (DISPLAY_EXTENDED(flags)) { /* Extended stats */ if (DISPLAY_SHORT_OUTPUT(flags)) { printf(" tps %s%s/s rqm/s await areq-sz aqu-sz %%util", spc, units); } else { if ((hpart == 1) || !hpart) { printf(" r/s %sr%s/s rrqm/s %%rrqm r_await rareq-sz", spc, units); } if ((hpart == 2) || !hpart) { printf(" w/s %sw%s/s wrqm/s %%wrqm w_await wareq-sz", spc, units); } if ((hpart == 3) || !hpart) { printf(" d/s %sd%s/s drqm/s %%drqm d_await dareq-sz", spc, units); } if ((hpart == 4) || !hpart) { printf(" f/s f_await aqu-sz %%util"); } } } else { /* Basic stats */ if (DISPLAY_SHORT_OUTPUT(flags)) { printf(" tps %s%s_read/s %s%s_w+d/s %s%s_read %s%s_w+d", spc, units, spc, units, spc, units, spc, units); } else { printf(" tps %s%s_read/s %s%s_wrtn/s %s%s_dscd/s %s%s_read %s%s_wrtn %s%s_dscd", spc, units, spc, units, spc, units, spc, units, spc, units, spc, units); } } if (DISPLAY_PRETTY(flags)) { printf(" Device"); } printf("\n"); } /* *************************************************************************** * Display extended stats, read from /proc/{diskstats,partitions} or /sys, * in plain format. * * IN: * @itv Interval of time. * @fctr Conversion factor. * @hpart Indicate which part of the report should be displayed in * human mode. A value of 0 indicates that output should not be * broken in several parts. * @d Structure containing device description. * @ioi Current sample statistics. * @ioj Previous sample statistics. * @devname Current device name. * @xds Extended stats for current device. * @xios Additional extended statistics for current device. *************************************************************************** */ void write_plain_ext_stat(unsigned long long itv, int fctr, int hpart, struct io_device *d, struct io_stats *ioi, struct io_stats *ioj, char *devname, struct ext_disk_stats *xds, struct ext_io_stats *xios) { int n; /* If this is a group with no devices, skip it */ if (d->dev_tp == T_GROUP) return; if (!DISPLAY_PRETTY(flags)) { cprintf_in(IS_STR, "%-13s", devname, 0); } /* Compute number of devices in group */ if (d->dev_tp > T_GROUP) { n = d->dev_tp - T_GROUP; } else { n = 1; } if (DISPLAY_SHORT_OUTPUT(flags)) { /* tps */ /* Origin (unmerged) flush operations are counted as writes */ cprintf_f(NO_UNIT, 1, 8, 2, S_VALUE(ioj->rd_ios + ioj->wr_ios + ioj->dc_ios, ioi->rd_ios + ioi->wr_ios + ioi->dc_ios, itv)); /* kB/s */ if (!DISPLAY_UNIT(flags)) { xios->sectors /= fctr; } cprintf_f(DISPLAY_UNIT(flags) ? UNIT_SECTOR : NO_UNIT, 1, 9, 2, xios->sectors); /* rqm/s */ cprintf_f(NO_UNIT, 1, 8, 2, S_VALUE(ioj->rd_merges + ioj->wr_merges + ioj->dc_merges, ioi->rd_merges + ioi->wr_merges + ioi->dc_merges, itv)); /* await */ cprintf_f(NO_UNIT, 1, 7, 2, xds->await); /* areq-sz (in kB, not sectors) */ cprintf_f(DISPLAY_UNIT(flags) ? UNIT_KILOBYTE : NO_UNIT, 1, 8, 2, xds->arqsz / 2); /* aqu-sz */ cprintf_f(NO_UNIT, 1, 7, 2, S_VALUE(ioj->rq_ticks, ioi->rq_ticks, itv) / 1000.0); /* * %util * Again: Ticks in milliseconds. */ cprintf_pc(DISPLAY_UNIT(flags), 1, 6, 2, xds->util / 10.0 / (double) n); } else { if ((hpart == 1) || !hpart) { /* r/s */ cprintf_f(NO_UNIT, 1, 7, 2, S_VALUE(ioj->rd_ios, ioi->rd_ios, itv)); /* rkB/s */ if (!DISPLAY_UNIT(flags)) { xios->rsectors /= fctr; } cprintf_f(DISPLAY_UNIT(flags) ? UNIT_SECTOR : NO_UNIT, 1, 9, 2, xios->rsectors); /* rrqm/s */ cprintf_f(NO_UNIT, 1, 8, 2, S_VALUE(ioj->rd_merges, ioi->rd_merges, itv)); /* %rrqm */ cprintf_pc(DISPLAY_UNIT(flags), 1, 6, 2, xios->rrqm_pc); /* r_await */ cprintf_f(NO_UNIT, 1, 7, 2, xios->r_await); /* rareq-sz (in kB, not sectors) */ cprintf_f(DISPLAY_UNIT(flags) ? UNIT_KILOBYTE : NO_UNIT, 1, 8, 2, xios->rarqsz / 2); } if ((hpart == 2) || !hpart) { /* w/s */ cprintf_f(NO_UNIT, 1, 7, 2, S_VALUE(ioj->wr_ios, ioi->wr_ios, itv)); /* wkB/s */ if (!DISPLAY_UNIT(flags)) { xios->wsectors /= fctr; } cprintf_f(DISPLAY_UNIT(flags) ? UNIT_SECTOR : NO_UNIT, 1, 9, 2, xios->wsectors); /* wrqm/s */ cprintf_f(NO_UNIT, 1, 8, 2, S_VALUE(ioj->wr_merges, ioi->wr_merges, itv)); /* %wrqm */ cprintf_pc(DISPLAY_UNIT(flags), 1, 6, 2, xios->wrqm_pc); /* w_await */ cprintf_f(NO_UNIT, 1, 7, 2, xios->w_await); /* wareq-sz (in kB, not sectors) */ cprintf_f(DISPLAY_UNIT(flags) ? UNIT_KILOBYTE : NO_UNIT, 1, 8, 2, xios->warqsz / 2); } if ((hpart == 3) || !hpart) { /* d/s */ cprintf_f(NO_UNIT, 1, 7, 2, S_VALUE(ioj->dc_ios, ioi->dc_ios, itv)); /* dkB/s */ if (!DISPLAY_UNIT(flags)) { xios->dsectors /= fctr; } cprintf_f(DISPLAY_UNIT(flags) ? UNIT_SECTOR : NO_UNIT, 1, 9, 2, xios->dsectors); /* drqm/s */ cprintf_f(NO_UNIT, 1, 8, 2, S_VALUE(ioj->dc_merges, ioi->dc_merges, itv)); /* %drqm */ cprintf_pc(DISPLAY_UNIT(flags), 1, 6, 2, xios->drqm_pc); /* d_await */ cprintf_f(NO_UNIT, 1, 7, 2, xios->d_await); /* dareq-sz (in kB, not sectors) */ cprintf_f(DISPLAY_UNIT(flags) ? UNIT_KILOBYTE : NO_UNIT, 1, 8, 2, xios->darqsz / 2); } if ((hpart == 4) || !hpart) { /* f/s */ cprintf_f(NO_UNIT, 1, 7, 2, S_VALUE(ioj->fl_ios, ioi->fl_ios, itv)); /* f_await */ cprintf_f(NO_UNIT, 1, 7, 2, xios->f_await); /* aqu-sz */ cprintf_f(NO_UNIT, 1, 7, 2, S_VALUE(ioj->rq_ticks, ioi->rq_ticks, itv) / 1000.0); /* * %util * Again: Ticks in milliseconds. */ if (d->dev_tp > T_GROUP) { n = d->dev_tp - T_GROUP; } else { n = 1; } cprintf_pc(DISPLAY_UNIT(flags), 1, 6, 2, xds->util / 10.0 / (double) n); } } if (DISPLAY_PRETTY(flags)) { cprintf_in(IS_STR, " %s", devname, 0); } printf("\n"); } /* *************************************************************************** * Display extended stats, read from /proc/{diskstats,partitions} or /sys, * in JSON format. * * IN: * @tab Number of tabs to print. * @itv Interval of time. * @fctr Conversion factor. * @d Structure containing the device description. * @ioi Current sample statistics. * @ioj Previous sample statistics. * @devname Current device name. * @xds Extended stats for current device. * @xios Additional extended statistics for current device. *************************************************************************** */ void write_json_ext_stat(int tab, unsigned long long itv, int fctr, struct io_device *d, struct io_stats *ioi, struct io_stats *ioj, char *devname, struct ext_disk_stats *xds, struct ext_io_stats *xios) { int n; char line[256]; /* If this is a group with no devices, skip it */ if (d->dev_tp == T_GROUP) return; xprintf0(tab, "{\"disk_device\": \"%s\", ", devname); if (DISPLAY_SHORT_OUTPUT(flags)) { printf("\"tps\": %.2f, \"", /* Origin (unmerged) flush operations are counted as writes */ S_VALUE(ioj->rd_ios + ioj->wr_ios + ioj->dc_ios, ioi->rd_ios + ioi->wr_ios + ioi->dc_ios, itv)); if (DISPLAY_MEGABYTES(flags)) { printf("MB/s"); } else if (DISPLAY_KILOBYTES(flags)) { printf("kB/s"); } else { printf("sec/s"); } printf("\": %.2f, \"rqm/s\": %.2f, \"await\": %.2f, " "\"areq-sz\": %.2f, \"aqu-sz\": %.2f, ", xios->sectors /= fctr, S_VALUE(ioj->rd_merges + ioj->wr_merges + ioj->dc_merges, ioi->rd_merges + ioi->wr_merges + ioi->dc_merges, itv), xds->await, xds->arqsz / 2, S_VALUE(ioj->rq_ticks, ioi->rq_ticks, itv) / 1000.0); } else { printf("\"r/s\": %.2f, \"w/s\": %.2f, \"d/s\": %.2f, \"f/s\": %.2f, ", S_VALUE(ioj->rd_ios, ioi->rd_ios, itv), S_VALUE(ioj->wr_ios, ioi->wr_ios, itv), S_VALUE(ioj->dc_ios, ioi->dc_ios, itv), S_VALUE(ioj->fl_ios, ioi->fl_ios, itv)); if (DISPLAY_MEGABYTES(flags)) { sprintf(line, "\"rMB/s\": %%.2f, \"wMB/s\": %%.2f, \"dMB/s\": %%.2f, "); } else if (DISPLAY_KILOBYTES(flags)) { sprintf(line, "\"rkB/s\": %%.2f, \"wkB/s\": %%.2f, \"dkB/s\": %%.2f, "); } else { sprintf(line, "\"rsec/s\": %%.2f, \"wsec/s\": %%.2f, \"dsec/s\": %%.2f, "); } printf(line, xios->rsectors /= fctr, xios->wsectors /= fctr, xios->dsectors /= fctr); printf("\"rrqm/s\": %.2f, \"wrqm/s\": %.2f, \"drqm/s\": %.2f, " "\"rrqm\": %.2f, \"wrqm\": %.2f, \"drqm\": %.2f, " "\"r_await\": %.2f, \"w_await\": %.2f, \"d_await\": %.2f, \"f_await\": %.2f, " "\"rareq-sz\": %.2f, \"wareq-sz\": %.2f, \"dareq-sz\": %.2f, " "\"aqu-sz\": %.2f, ", S_VALUE(ioj->rd_merges, ioi->rd_merges, itv), S_VALUE(ioj->wr_merges, ioi->wr_merges, itv), S_VALUE(ioj->dc_merges, ioi->dc_merges, itv), xios->rrqm_pc, xios->wrqm_pc, xios->drqm_pc, xios->r_await, xios->w_await, xios->d_await, xios->f_await, xios->rarqsz / 2, xios->warqsz / 2, xios->darqsz / 2, S_VALUE(ioj->rq_ticks, ioi->rq_ticks, itv) / 1000.0); } if (d->dev_tp > T_GROUP) { n = d->dev_tp - T_GROUP; } else { n = 1; } printf("\"util\": %.2f}", xds->util / 10.0 / (double) n); } /* *************************************************************************** * Display extended stats, read from /proc/{diskstats,partitions} or /sys, * in plain or JSON format. * * IN: * @itv Interval of time. * @fctr Conversion factor. * @hpart Indicate which part of the report should be displayed in * human mode. A value of 0 indicates that output should not be * broken in several parts. * @d Structure containing device description. * @ioi Current sample statistics. * @ioj Previous sample statistics. * @tab Number of tabs to print (JSON output only). * @dname Name to be used for display for current device. *************************************************************************** */ void write_ext_stat(unsigned long long itv, int fctr, int hpart, struct io_device *d, struct io_stats *ioi, struct io_stats *ioj, int tab, char *dname) { struct stats_disk sdc, sdp; struct ext_disk_stats xds; struct ext_io_stats xios; memset(&xds, 0, sizeof(struct ext_disk_stats)); memset(&xios, 0, sizeof(struct ext_io_stats)); /* * Counters overflows are possible, but don't need to be handled in * a special way: The difference is still properly calculated if the * result is of the same type as the two values. * Exception is field rq_ticks which is incremented by the number of * I/O in progress times the number of milliseconds spent doing I/O. * But the number of I/O in progress (field ios_pgr) happens to be * sometimes negative... */ if ((hpart == 4) || !hpart || DISPLAY_SHORT_OUTPUT(flags)) { /* Origin (unmerged) flush operations are counted as writes */ sdc.nr_ios = ioi->rd_ios + ioi->wr_ios + ioi->dc_ios; sdp.nr_ios = ioj->rd_ios + ioj->wr_ios + ioj->dc_ios; sdc.tot_ticks = ioi->tot_ticks; sdp.tot_ticks = ioj->tot_ticks; sdc.rd_ticks = ioi->rd_ticks; sdp.rd_ticks = ioj->rd_ticks; sdc.wr_ticks = ioi->wr_ticks; sdp.wr_ticks = ioj->wr_ticks; sdc.dc_ticks = ioi->dc_ticks; sdp.dc_ticks = ioj->dc_ticks; sdc.rd_sect = ioi->rd_sectors; sdp.rd_sect = ioj->rd_sectors; sdc.wr_sect = ioi->wr_sectors; sdp.wr_sect = ioj->wr_sectors; sdc.dc_sect = ioi->dc_sectors; sdp.dc_sect = ioj->dc_sectors; compute_ext_disk_stats(&sdc, &sdp, itv, &xds); } /* rkB/s wkB/s dkB/s */ xios.rsectors = S_VALUE(ioj->rd_sectors, ioi->rd_sectors, itv); xios.wsectors = S_VALUE(ioj->wr_sectors, ioi->wr_sectors, itv); xios.dsectors = S_VALUE(ioj->dc_sectors, ioi->dc_sectors, itv); if (DISPLAY_SHORT_OUTPUT(flags)) { xios.sectors = xios.rsectors + xios.wsectors + xios.dsectors; } else { if ((hpart == 1) || !hpart) { /* %rrqm */ xios.rrqm_pc = (ioi->rd_merges - ioj->rd_merges) + (ioi->rd_ios - ioj->rd_ios) ? (double) ((ioi->rd_merges - ioj->rd_merges)) / ((ioi->rd_merges - ioj->rd_merges) + (ioi->rd_ios - ioj->rd_ios)) * 100 : 0.0; /* r_await */ xios.r_await = (ioi->rd_ios - ioj->rd_ios) ? (ioi->rd_ticks - ioj->rd_ticks) / ((double) (ioi->rd_ios - ioj->rd_ios)) : 0.0; /* rareq-sz (still in sectors, not kB) */ xios.rarqsz = (ioi->rd_ios - ioj->rd_ios) ? (ioi->rd_sectors - ioj->rd_sectors) / ((double) (ioi->rd_ios - ioj->rd_ios)) : 0.0; } if ((hpart == 2) || !hpart) { /* %wrqm */ xios.wrqm_pc = (ioi->wr_merges - ioj->wr_merges) + (ioi->wr_ios - ioj->wr_ios) ? (double) ((ioi->wr_merges - ioj->wr_merges)) / ((ioi->wr_merges - ioj->wr_merges) + (ioi->wr_ios - ioj->wr_ios)) * 100 : 0.0; /* w_await */ xios.w_await = (ioi->wr_ios - ioj->wr_ios) ? (ioi->wr_ticks - ioj->wr_ticks) / ((double) (ioi->wr_ios - ioj->wr_ios)) : 0.0; /* wareq-sz (still in sectors, not kB) */ xios.warqsz = (ioi->wr_ios - ioj->wr_ios) ? (ioi->wr_sectors - ioj->wr_sectors) / ((double) (ioi->wr_ios - ioj->wr_ios)) : 0.0; } if ((hpart == 3) || !hpart) { /* %drqm */ xios.drqm_pc = (ioi->dc_merges - ioj->dc_merges) + (ioi->dc_ios - ioj->dc_ios) ? (double) ((ioi->dc_merges - ioj->dc_merges)) / ((ioi->dc_merges - ioj->dc_merges) + (ioi->dc_ios - ioj->dc_ios)) * 100 : 0.0; /* d_await */ xios.d_await = (ioi->dc_ios - ioj->dc_ios) ? (ioi->dc_ticks - ioj->dc_ticks) / ((double) (ioi->dc_ios - ioj->dc_ios)) : 0.0; /* dareq-sz (still in sectors, not kB) */ xios.darqsz = (ioi->dc_ios - ioj->dc_ios) ? (ioi->dc_sectors - ioj->dc_sectors) / ((double) (ioi->dc_ios - ioj->dc_ios)) : 0.0; } if ((hpart == 4) || !hpart) { /* f_await */ xios.f_await = (ioi->fl_ios - ioj->fl_ios) ? (ioi->fl_ticks - ioj->fl_ticks) / ((double) (ioi->fl_ios - ioj->fl_ios)) : 0.0; } } if (DISPLAY_JSON_OUTPUT(flags)) { write_json_ext_stat(tab, itv, fctr, d, ioi, ioj, dname, &xds, &xios); } else { write_plain_ext_stat(itv, fctr, hpart, d, ioi, ioj, dname, &xds, &xios); } } /* *************************************************************************** * Write basic stats, read from /proc/diskstats or from sysfs, in plain * format. * * IN: * @itv Interval of time. * @fctr Conversion factor. * @ioi Current sample statistics. * @ioj Previous sample statistics. * @devname Current device name. * @rd_sec Number of sectors read. * @wr_sec Number of sectors written. * @dc_sec Number of sectors discarded. *************************************************************************** */ void write_plain_basic_stat(unsigned long long itv, int fctr, struct io_stats *ioi, struct io_stats *ioj, char *devname, unsigned long long rd_sec, unsigned long long wr_sec, unsigned long long dc_sec) { double rsectors, wsectors, dsectors; if (!DISPLAY_PRETTY(flags)) { cprintf_in(IS_STR, "%-13s", devname, 0); } rsectors = S_VALUE(ioj->rd_sectors, ioi->rd_sectors, itv); wsectors = S_VALUE(ioj->wr_sectors, ioi->wr_sectors, itv); dsectors = S_VALUE(ioj->dc_sectors, ioi->dc_sectors, itv); if (!DISPLAY_UNIT(flags)) { rsectors /= fctr; wsectors /= fctr; dsectors /= fctr; } /* tps */ cprintf_f(NO_UNIT, 1, 8, 2, /* Origin (unmerged) flush operations are counted as writes */ S_VALUE(ioj->rd_ios + ioj->wr_ios + ioj->dc_ios, ioi->rd_ios + ioi->wr_ios + ioi->dc_ios, itv)); if (DISPLAY_SHORT_OUTPUT(flags)) { /* kB_read/s kB_w+d/s */ cprintf_f(DISPLAY_UNIT(flags) ? UNIT_SECTOR : NO_UNIT, 2, 12, 2, rsectors, wsectors + dsectors); /* kB_read kB_w+d */ cprintf_u64(DISPLAY_UNIT(flags) ? UNIT_SECTOR : NO_UNIT, 2, 10, DISPLAY_UNIT(flags) ? (unsigned long long) rd_sec : (unsigned long long) rd_sec / fctr, DISPLAY_UNIT(flags) ? (unsigned long long) wr_sec + dc_sec : (unsigned long long) (wr_sec + dc_sec) / fctr); } else { /* kB_read/s kB_wrtn/s kB_dscd/s */ cprintf_f(DISPLAY_UNIT(flags) ? UNIT_SECTOR : NO_UNIT, 3, 12, 2, rsectors, wsectors, dsectors); /* kB_read kB_wrtn kB_dscd */ cprintf_u64(DISPLAY_UNIT(flags) ? UNIT_SECTOR : NO_UNIT, 3, 10, DISPLAY_UNIT(flags) ? (unsigned long long) rd_sec : (unsigned long long) rd_sec / fctr, DISPLAY_UNIT(flags) ? (unsigned long long) wr_sec : (unsigned long long) wr_sec / fctr, DISPLAY_UNIT(flags) ? (unsigned long long) dc_sec : (unsigned long long) dc_sec / fctr); } if (DISPLAY_PRETTY(flags)) { cprintf_in(IS_STR, " %s", devname, 0); } printf("\n"); } /* *************************************************************************** * Write basic stats, read from /proc/diskstats or from sysfs, in JSON * format. * * IN: * @tab Number of tabs to print. * @itv Interval of time. * @fctr Conversion factor. * @ioi Current sample statistics. * @ioj Previous sample statistics. * @devname Current device name. * @rd_sec Number of sectors read. * @wr_sec Number of sectors written. *************************************************************************** */ void write_json_basic_stat(int tab, unsigned long long itv, int fctr, struct io_stats *ioi, struct io_stats *ioj, char *devname, unsigned long long rd_sec, unsigned long long wr_sec, unsigned long long dc_sec) { char line[256]; xprintf0(tab, "{\"disk_device\": \"%s\", \"tps\": %.2f, ", devname, /* Origin (unmerged) flush operations are counted as writes */ S_VALUE(ioj->rd_ios + ioj->wr_ios + ioj->dc_ios, ioi->rd_ios + ioi->wr_ios + ioi->dc_ios, itv)); if (DISPLAY_KILOBYTES(flags)) { sprintf(line, "\"kB_read/s\": %%.2f, \"kB_wrtn/s\": %%.2f, \"kB_dscd/s\": %%.2f, " "\"kB_read\": %%llu, \"kB_wrtn\": %%llu, \"kB_dscd\": %%llu}"); } else if (DISPLAY_MEGABYTES(flags)) { sprintf(line, "\"MB_read/s\": %%.2f, \"MB_wrtn/s\": %%.2f, \"MB_dscd/s\": %%.2f, " "\"MB_read\": %%llu, \"MB_wrtn\": %%llu, \"MB_dscd\": %%llu}"); } else { sprintf(line, "\"Blk_read/s\": %%.2f, \"Blk_wrtn/s\": %%.2f, \"Blk_dscd/s\": %%.2f, " "\"Blk_read\": %%llu, \"Blk_wrtn\": %%llu, \"Blk_dscd\": %%llu}"); } printf(line, S_VALUE(ioj->rd_sectors, ioi->rd_sectors, itv) / fctr, S_VALUE(ioj->wr_sectors, ioi->wr_sectors, itv) / fctr, S_VALUE(ioj->dc_sectors, ioi->dc_sectors, itv) / fctr, (unsigned long long) rd_sec / fctr, (unsigned long long) wr_sec / fctr, (unsigned long long) dc_sec / fctr); } /* *************************************************************************** * Write basic stats, read from /proc/diskstats or from sysfs, in plain or * JSON format. * * IN: * @itv Interval of time. * @fctr Conversion factor. * @d Structure containing device description. * @ioi Current sample statistics. * @ioj Previous sample statistics. * @tab Number of tabs to print (JSON format only). * @dname Name to be used for display for current device. *************************************************************************** */ void write_basic_stat(unsigned long long itv, int fctr, struct io_device *d, struct io_stats *ioi, struct io_stats *ioj, int tab, char *dname) { unsigned long long rd_sec, wr_sec, dc_sec; /* Print stats coming from /sys or /proc/diskstats */ rd_sec = ioi->rd_sectors - ioj->rd_sectors; if ((ioi->rd_sectors < ioj->rd_sectors) && (ioj->rd_sectors <= 0xffffffff)) { rd_sec &= 0xffffffff; } wr_sec = ioi->wr_sectors - ioj->wr_sectors; if ((ioi->wr_sectors < ioj->wr_sectors) && (ioj->wr_sectors <= 0xffffffff)) { wr_sec &= 0xffffffff; } dc_sec = ioi->dc_sectors - ioj->dc_sectors; if ((ioi->dc_sectors < ioj->dc_sectors) && (ioj->dc_sectors <= 0xffffffff)) { dc_sec &= 0xffffffff; } if (DISPLAY_JSON_OUTPUT(flags)) { write_json_basic_stat(tab, itv, fctr, ioi, ioj, dname, rd_sec, wr_sec, dc_sec); } else { write_plain_basic_stat(itv, fctr, ioi, ioj, dname, rd_sec, wr_sec, dc_sec); } } /* *************************************************************************** * Print everything now (stats and uptime). * * IN: * @curr Index in array for current sample statistics. * @rectime Current date and time. * @skip TRUE if nothing should be displayed (option -y). We must * go through write_stats() anyway to compute groups statistics. *************************************************************************** */ void write_stats(int curr, struct tm *rectime, int skip) { int h, hl = 0, hh = 0, fctr = 1, tab = 4, next = FALSE; unsigned long long itv; struct io_device *d, *dtmp, *g = NULL, *dnext = NULL; char *dev_name; /* Test stdout */ TEST_STDOUT(STDOUT_FILENO); if (DISPLAY_JSON_OUTPUT(flags) && !skip) { xprintf(tab++, "{"); } /* Print time stamp */ if (DISPLAY_TIMESTAMP(flags) && !skip) { write_sample_timestamp(tab, rectime); #ifdef DEBUG if (DISPLAY_DEBUG(flags)) { fprintf(stderr, "%s\n", timestamp); } #endif } if (DISPLAY_CPU(flags) && !skip) { /* Display CPU utilization */ write_cpu_stat(curr, tab); if (DISPLAY_JSON_OUTPUT(flags)) { if (DISPLAY_DISK(flags)) { printf(","); } printf("\n"); } } /* Calculate time interval in 1/100th of a second */ itv = get_interval(uptime_cs[!curr], uptime_cs[curr]); if (DISPLAY_DISK(flags)) { struct io_stats *ioi, *ioj, iozero; memset(&iozero, 0, sizeof(struct io_stats)); if (DISPLAY_PRETTY(flags) && DISPLAY_EXTENDED(flags) && !DISPLAY_SHORT_OUTPUT(flags) && !DISPLAY_JSON_OUTPUT(flags) && !DISPLAY_COMPACT(flags)) { hl = 1; hh = 4; } for (h = hl; h <= hh; h++) { if (!skip) { /* Display disk stats header */ write_disk_stat_header(&fctr, &tab, h); } for (d = dev_list; ; d = dnext) { if (d == NULL) { if (g == NULL) /* No group processing in progress */ break; /* Display last group before exit */ dnext = NULL; d = g; g = NULL; } else { dnext = d->next; if (d->dev_tp >= T_GROUP) { /* * This is a new group: Save group position * and display previous one. */ if (g != NULL) { dtmp = g; g = d; d = dtmp; memset(g->dev_stats[curr], 0, sizeof(struct io_stats)); } else { g = d; memset(g->dev_stats[curr], 0, sizeof(struct io_stats)); continue; /* No previous group to display */ } } } if (!d->exist && (d->dev_tp < T_GROUP)) /* Current device is non existent (e.g. it has been unregistered from the system */ continue; if ((g != NULL) && (h == hl) && (d->dev_tp < T_GROUP)) { /* We are within a group: Increment number of disks in the group */ (g->dev_tp)++; /* Add current device stats to group */ compute_device_groups_stats(curr, d, g); } if (DISPLAY_GROUP_TOTAL_ONLY(flags) && (g != NULL) && (d->dev_tp < T_GROUP)) continue; ioi = d->dev_stats[curr]; ioj = d->dev_stats[!curr]; /* Origin (unmerged) flush operations are counted as writes */ if (!DISPLAY_UNFILTERED(flags)) { if (!ioi->rd_ios && !ioi->wr_ios && !ioi->dc_ios && !ioi->fl_ios) continue; } if (DISPLAY_ZERO_OMIT(flags)) { if ((ioi->rd_ios == ioj->rd_ios) && (ioi->wr_ios == ioj->wr_ios) && (ioi->dc_ios == ioj->dc_ios) && (ioi->fl_ios == ioj->fl_ios)) /* No activity: Ignore it */ continue; } /* Try to detect if device has been removed then inserted again */ if (((ioi->rd_ios + ioi->wr_ios + ioi->dc_ios + ioi->fl_ios) < (ioj->rd_ios + ioj->wr_ios + ioj->dc_ios + ioj->fl_ios)) && (!ioj->rd_sectors || (ioi->rd_sectors < ioj->rd_sectors)) && (!ioj->wr_sectors || (ioi->wr_sectors < ioj->wr_sectors)) && (!ioj->dc_sectors || (ioi->dc_sectors < ioj->dc_sectors))) { ioj = &iozero; } dev_name = get_device_name(d->major, d->minor, NULL, 0, DISPLAY_DEVMAP_NAME(flags), DISPLAY_PERSIST_NAME_I(flags), FALSE, d->name); #ifdef DEBUG if (DISPLAY_DEBUG(flags)) { /* Debug output */ fprintf(stderr, "name=%s itv=%llu fctr=%d ioi{ rd_sectors=%lu " "wr_sectors=%lu dc_sectors=%lu " "rd_ios=%lu rd_merges=%lu rd_ticks=%u " "wr_ios=%lu wr_merges=%lu wr_ticks=%u " "dc_ios=%lu dc_merges=%lu dc_ticks=%u " "fl_ios=%lu fl_ticks=%u " "ios_pgr=%u tot_ticks=%u " "rq_ticks=%u }\n", dev_name, itv, fctr, ioi->rd_sectors, ioi->wr_sectors, ioi->dc_sectors, ioi->rd_ios, ioi->rd_merges, ioi->rd_ticks, ioi->wr_ios, ioi->wr_merges, ioi->wr_ticks, ioi->dc_ios, ioi->dc_merges, ioi->dc_ticks, ioi->fl_ios, ioi->fl_ticks, ioi->ios_pgr, ioi->tot_ticks, ioi->rq_ticks); } #endif if (!skip) { if (DISPLAY_JSON_OUTPUT(flags) && next) { printf(",\n"); } next = TRUE; if (DISPLAY_EXTENDED(flags)) { write_ext_stat(itv, fctr, h, d, ioi, ioj, tab, dev_name); } else { write_basic_stat(itv, fctr, d, ioi, ioj, tab, dev_name); } } } if ((h > 0) && (h < hh) && !skip) { printf("\n"); } } if (DISPLAY_JSON_OUTPUT(flags) && !skip) { printf("\n"); xprintf(--tab, "]"); } } if (!skip) { if (DISPLAY_JSON_OUTPUT(flags)) { xprintf0(--tab, "}"); } else { printf("\n"); } } } /* *************************************************************************** * Main loop: Read I/O stats from the relevant sources and display them. * * IN: * @count Number of reports to print. * @rectime Current date and time. *************************************************************************** */ void rw_io_stat_loop(long int count, struct tm *rectime) { int curr = 1; int skip = 0; /* Should we skip first report? */ if (DISPLAY_OMIT_SINCE_BOOT(flags) && interval > 0) { skip = 1; } /* Set a handler for SIGALRM */ memset(&alrm_act, 0, sizeof(alrm_act)); alrm_act.sa_handler = alarm_handler; sigaction(SIGALRM, &alrm_act, NULL); alarm(interval); /* Set a handler for SIGINT */ memset(&int_act, 0, sizeof(int_act)); int_act.sa_handler = int_handler; sigaction(SIGINT, &int_act, NULL); do { /* Every device is potentially nonexistent */ set_devices_nonexistent(dev_list); /* Read system uptime */ read_uptime(&(uptime_cs[curr])); /* Read stats for CPU "all" */ read_stat_cpu(st_cpu[curr], 1); /* * Compute the total number of jiffies spent by all processors. * NB: Don't add cpu_guest/cpu_guest_nice because cpu_user/cpu_nice * already include them. */ tot_jiffies[curr] = st_cpu[curr]->cpu_user + st_cpu[curr]->cpu_nice + st_cpu[curr]->cpu_sys + st_cpu[curr]->cpu_idle + st_cpu[curr]->cpu_iowait + st_cpu[curr]->cpu_hardirq + st_cpu[curr]->cpu_steal + st_cpu[curr]->cpu_softirq; if (DISPLAY_EVERYTHING(flags)) { read_diskstats_stat(curr); } else { read_sysfs_dlist_stat(curr); } /* Get time */ get_localtime(rectime, 0); /* Print results */ write_stats(curr, rectime, skip); if (!skip) { if (count > 0) { count--; } } if (count) { curr ^= 1; __pause(); if (sigint_caught) { /* SIGINT signal caught => Terminate JSON output properly */ count = 0; } else if (DISPLAY_JSON_OUTPUT(flags) && count && !skip) { printf(","); } skip = 0; } printf("\n"); } while (count); if (DISPLAY_JSON_OUTPUT(flags)) { printf("\t\t\t]\n\t\t}\n\t]\n}}\n"); } } /* *************************************************************************** * Main entry to the iostat program. *************************************************************************** */ int main(int argc, char **argv) { int it = 0; int opt = 1; int i, report_set = FALSE; long count = 1; struct utsname header; struct tm rectime; char *t, *persist_devname, *devname; char group_name[MAX_NAME_LEN]; #ifdef USE_NLS /* Init National Language Support */ init_nls(); #endif alt_dir[0] = '\0'; /* Process args... */ while (opt < argc) { /* -p option used individually. See below for grouped use */ if (!strcmp(argv[opt], "-p")) { if (argv[++opt] && (strspn(argv[opt], DIGITS) != strlen(argv[opt])) && (strncmp(argv[opt], "-", 1))) { flags |= I_D_UNFILTERED; for (t = strtok(argv[opt], ","); t; t = strtok(NULL, ",")) { if (!strcmp(t, K_ALL)) { flags |= I_D_EVERYTHING; } else { devname = device_name(t); if (DISPLAY_PERSIST_NAME_I(flags)) { /* Get device persistent name */ persist_devname = get_pretty_name_from_persistent(devname); if (persist_devname != NULL) { devname = persist_devname; } } /* Store device name */ add_list_device(&dev_list, devname, T_PART_DEV, 0, 0); } } opt++; } else { flags |= I_D_EVERYTHING; } } else if (!strcmp(argv[opt], "-g")) { if (!argv[++opt]) { usage(argv[0]); } flags |= I_F_GROUP_DEFINED; /* * MAX_NAME_LEN - 2: one char for the heading space, * and one for the trailing '\0'. */ snprintf(group_name, MAX_NAME_LEN, " %-.*s", MAX_NAME_LEN - 2, argv[opt++]); add_list_device(&dev_list, group_name, T_GROUP, 0, 0); } else if (!strcmp(argv[opt], "--human")) { flags |= I_D_UNIT; opt++; } else if (!strcmp(argv[opt], "--pretty")) { /* Display an easy-to-read CIFS report */ flags |= I_D_PRETTY; opt++; } else if (!strcmp(argv[opt], "--compact")) { flags |= I_D_COMPACT; opt++; } #ifdef TEST else if (!strncmp(argv[opt], "--getenv", 8)) { __env = TRUE; opt++; } #endif else if (!strncmp(argv[opt], "--dec=", 6) && (strlen(argv[opt]) == 7)) { /* Get number of decimal places */ dplaces_nr = atoi(argv[opt] + 6); if ((dplaces_nr < 0) || (dplaces_nr > 2)) { usage(argv[0]); } opt++; } else if (!strcmp(argv[opt], "-f") || !strcmp(argv[opt], "+f")) { if (alt_dir[0] || !argv[opt + 1]) { usage(argv[0]); } if (argv[opt++][0] == '+') { flags |= I_D_ALL_DIR; } strncpy(alt_dir, argv[opt++], sizeof(alt_dir)); alt_dir[sizeof(alt_dir) - 1] = '\0'; if (!check_dir(alt_dir)) { usage(argv[0]); } } else if (!strcmp(argv[opt], "-j")) { if (!argv[++opt]) { usage(argv[0]); } if (strnlen(argv[opt], sizeof(persistent_name_type)) >= sizeof(persistent_name_type) - 1) { usage(argv[0]); } strncpy(persistent_name_type, argv[opt], sizeof(persistent_name_type) - 1); persistent_name_type[sizeof(persistent_name_type) - 1] = '\0'; strtolower(persistent_name_type); /* Check that this is a valid type of persistent device name */ if (!get_persistent_type_dir(persistent_name_type)) { fprintf(stderr, _("Invalid type of persistent device name\n")); exit(1); } /* Persistent names are usually long: Pretty display them */ flags |= I_D_PERSIST_NAME + I_D_PRETTY; opt++; } else if (!strcmp(argv[opt], "-o")) { /* Select output format */ if (argv[++opt] && !strcmp(argv[opt], K_JSON)) { flags |= I_D_JSON_OUTPUT; opt++; } else { usage(argv[0]); } } #ifdef DEBUG else if (!strcmp(argv[opt], "--debuginfo")) { flags |= I_D_DEBUG; opt++; } #endif else if (!strncmp(argv[opt], "-", 1)) { for (i = 1; *(argv[opt] + i); i++) { switch (*(argv[opt] + i)) { case 'c': /* Display cpu usage */ flags |= I_D_CPU; report_set = TRUE; break; case 'd': /* Display disk utilization */ flags |= I_D_DISK; report_set = TRUE; break; case 'H': /* Display stats only for the groups */ flags |= I_D_GROUP_TOTAL_ONLY; break; case 'h': /* Option -h is equivalent to --pretty --human */ flags |= I_D_PRETTY + I_D_UNIT; break; case 'k': if (DISPLAY_MEGABYTES(flags)) { usage(argv[0]); } /* Display stats in kB/s */ flags |= I_D_KILOBYTES; break; case 'm': if (DISPLAY_KILOBYTES(flags)) { usage(argv[0]); } /* Display stats in MB/s */ flags |= I_D_MEGABYTES; break; case 'N': /* Display device mapper logical name */ flags |= I_D_DEVMAP_NAME; break; case 'p': /* If option -p is grouped then it cannot take an arg */ flags |= I_D_EVERYTHING; break; case 's': /* Display short output */ flags |= I_D_SHORT_OUTPUT; break; case 't': /* Display timestamp */ flags |= I_D_TIMESTAMP; break; case 'x': /* Display extended stats */ flags |= I_D_EXTENDED; break; case 'y': /* Don't display stats since system restart */ flags |= I_D_OMIT_SINCE_BOOT; break; case 'z': /* Omit output for devices with no activity */ flags |= I_D_ZERO_OMIT; break; case 'V': /* Print version number and exit */ print_version(); break; default: usage(argv[0]); } } opt++; } else if (strspn(argv[opt], DIGITS) != strlen(argv[opt])) { /* * By default iostat doesn't display unused devices. * If some devices are explicitly entered on the command line * then don't apply this rule any more. */ flags |= I_D_UNFILTERED; if (strcmp(argv[opt], K_ALL)) { /* Store device name entered on the command line */ devname = device_name(argv[opt++]); if (DISPLAY_PERSIST_NAME_I(flags)) { persist_devname = get_pretty_name_from_persistent(devname); if (persist_devname != NULL) { devname = persist_devname; } } add_list_device(&dev_list, devname, 0, UKWN_MAJ_NR, 0); } else { flags |= I_D_ALL_DEVICES; opt++; } } else if (!it) { interval = atol(argv[opt++]); if (interval < 0) { usage(argv[0]); } count = -1; it = 1; } else if (it > 0) { count = atol(argv[opt++]); if ((count < 1) || !interval) { usage(argv[0]); } it = -1; } else { usage(argv[0]); } } if (!interval) { count = 1; } /* Init color strings */ init_colors(); /* Default: Display CPU and DISK reports */ if (!report_set) { flags |= I_D_CPU + I_D_DISK; } /* * Also display DISK reports if options -p, -x or a device has been entered * on the command line. */ if (DISPLAY_EVERYTHING(flags) || DISPLAY_EXTENDED(flags) || DISPLAY_UNFILTERED(flags)) { flags |= I_D_DISK; } if (!DISPLAY_UNFILTERED(flags)) { flags |= I_D_ALL_DEVICES; } /* Option -H can only be used with option -g */ if (DISPLAY_GROUP_TOTAL_ONLY(flags) && !GROUP_DEFINED(flags)) { usage(argv[0]); } /* Select disk output unit (kB/s or blocks/s) */ set_disk_output_unit(); if (DISPLAY_JSON_OUTPUT(flags)) { /* Use a decimal point to make JSON code compliant with RFC7159 */ setlocale(LC_NUMERIC, "C"); } /* Allocate and init stat common counters */ init_stats(); /* How many processors on this machine? */ cpu_nr = get_cpu_nr(~0, FALSE); get_localtime(&rectime, 0); /* * Don't buffer data if redirected to a pipe. * Note: With musl-c, the behavior of this function is undefined except * when it is the first operation on the stream. */ setbuf(stdout, NULL); /* Get system name, release number and hostname */ __uname(&header); if (print_gal_header(&rectime, header.sysname, header.release, header.nodename, header.machine, cpu_nr, DISPLAY_JSON_OUTPUT(flags))) { flags |= I_D_ISO; } if (!DISPLAY_JSON_OUTPUT(flags)) { printf("\n"); } /* Main loop */ rw_io_stat_loop(count, &rectime); return 0; }