From: Sebastien GODARD Date: Sat, 20 Jul 2019 08:13:23 +0000 (+0200) Subject: iostat: Major code refactoring X-Git-Tag: v12.1.6~16 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=6857aa71d80d46cf872a3f56f8634a67c62be637;p=sysstat iostat: Major code refactoring This patch adds the following features: * Structures for devices statistics are now dynamically allocated. It is no longer possible to miss a device if many of them are added into the system while iostat is running. * Check for devices which are removed then inserted again into the system. iostat will no longer display abormally high numbers in these cases. * Better handle devices entered on the command line, which may be partitions and/or whole devices. Signed-off-by: Sebastien GODARD --- diff --git a/common.c b/common.c index e403634..08bdcd2 100644 --- a/common.c +++ b/common.c @@ -347,57 +347,6 @@ int get_dev_part_nr(char *dev_name) return part; } -/* - *************************************************************************** - * Look for block devices present in /sys/ filesystem: - * Check first that sysfs is mounted (done by trying to open /sys/block - * directory), then find number of devices registered. - * - * IN: - * @display_partitions Set to TRUE if partitions must also be counted. - * - * RETURNS: - * Total number of block devices (and partitions if @display_partitions was - * set). - *************************************************************************** - */ -int get_sysfs_dev_nr(int display_partitions) -{ - DIR *dir; - struct dirent *drd; - char line[MAX_PF_NAME]; - int dev = 0; - - /* Open /sys/block directory */ - if ((dir = opendir(SYSFS_BLOCK)) == NULL) - /* sysfs not mounted, or perhaps this is an old kernel */ - return 0; - - /* Get current file entry in /sys/block directory */ - while ((drd = readdir(dir)) != NULL) { - if (!strcmp(drd->d_name, ".") || !strcmp(drd->d_name, "..")) - continue; - snprintf(line, MAX_PF_NAME, "%s/%s/%s", SYSFS_BLOCK, drd->d_name, S_STAT); - line[MAX_PF_NAME - 1] = '\0'; - - /* Try to guess if current entry is a directory containing a stat file */ - if (!access(line, R_OK)) { - /* Yep... */ - dev++; - - if (display_partitions) { - /* We also want the number of partitions for this device */ - dev += get_dev_part_nr(drd->d_name); - } - } - } - - /* Close /sys/block directory */ - closedir(dir); - - return dev; -} - /* *************************************************************************** * Read /proc/devices file and get device-mapper major number. @@ -623,7 +572,10 @@ int get_win_height(void) /* *************************************************************************** - * Canonicalize and remove /dev from path name. + * Canonicalize and remove /dev from path name. If the device has a slash + * character in its name, replace it with a bang character ('!'), e.g.: + * cciss/c0d0 -> cciss!c0d0 + * cciss/c0d0p1 -> cciss!c0d0p1 * * IN: * @name Device name (may begin with "/dev/" or can be a symlink). @@ -635,7 +587,7 @@ int get_win_height(void) char *device_name(char *name) { static char out[MAX_FILE_LEN]; - char *resolved_name; + char *resolved_name = NULL, *slash; int i = 0; /* realpath() creates new string, so we need to free it later */ @@ -656,6 +608,11 @@ char *device_name(char *name) strncpy(out, resolved_name + i, MAX_FILE_LEN); out[MAX_FILE_LEN - 1] = '\0'; + /* Some devices may have a slash in their name (eg. cciss/c0d0...) */ + while ((slash = strchr(out, '/'))) { + *slash = '!'; + } + free(resolved_name); return out; diff --git a/common.h b/common.h index 8f0d37b..651d6e6 100644 --- a/common.h +++ b/common.h @@ -88,6 +88,7 @@ #define SYSFS_MANUFACTURER "manufacturer" #define SYSFS_PRODUCT "product" #define SYSFS_FCHOST PRE "/sys/class/fc_host" +#define SLASH_DEV PRE "/dev/" #define MAX_FILE_LEN 512 #define MAX_PF_NAME 1024 diff --git a/iostat.c b/iostat.c index d1fc70b..cbb586e 100644 --- a/iostat.c +++ b/iostat.c @@ -55,12 +55,8 @@ char *sccsid(void) { return (SCCSID); } struct stats_cpu *st_cpu[2]; unsigned long long uptime_cs[2] = {0, 0}; unsigned long long tot_jiffies[2] = {0, 0}; -struct io_stats *st_iodev[2]; -struct io_hdr_stats *st_hdr_iodev; -struct io_dlist *st_dev_list; +struct io_device *dev_list; -/* Last group name entered on the command line */ -char group_name[MAX_NAME_LEN]; /* Number of decimal places */ int dplaces_nr = -1; @@ -171,334 +167,145 @@ void init_stats(void) /* *************************************************************************** - * Set every device entry to unregistered status. But don't change status - * for group entries (whose status is DISK_GROUP). + * Set every device entry to nonexistent status. * * IN: - * @iodev_nr Number of devices and partitions. - * @st_hdr_iodev Pointer on first structure describing a device/partition. + * @dlist Pointer on the start of the linked list. *************************************************************************** */ -void set_entries_unregistered(int iodev_nr, struct io_hdr_stats *st_hdr_iodev) +void set_devices_nonexistent(struct io_device *dlist) { - int i; - struct io_hdr_stats *shi = st_hdr_iodev; - - for (i = 0; i < iodev_nr; i++, shi++) { - if (shi->status == DISK_REGISTERED) { - shi->status = DISK_UNREGISTERED; - } + while (dlist != NULL) { + dlist->exist = FALSE; + dlist = dlist->next; } } /* *************************************************************************** - * Free unregistered entries (mark them as unused). + * 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: - * @iodev_nr Number of devices and partitions. - * @st_hdr_iodev Pointer on first structure describing a device/partition. + * @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. + * + * 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. *************************************************************************** */ -void free_unregistered_entries(int iodev_nr, struct io_hdr_stats *st_hdr_iodev) +struct io_device *add_list_device(struct io_device **dlist, char *name, int dtype) { + struct io_device *d, *ds; int i; - struct io_hdr_stats *shi = st_hdr_iodev; - for (i = 0; i < iodev_nr; i++, shi++) { - if (shi->status == DISK_UNREGISTERED) { - shi->used = FALSE; + 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); } -} -/* - *************************************************************************** - * Allocate and init I/O device structures. - * - * IN: - * @dev_nr Number of devices and partitions (also including groups - * if option -g has been used). - *************************************************************************** - */ -void salloc_device(int dev_nr) -{ - int i; + /* 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 ((st_iodev[i] = - (struct io_stats *) malloc(IO_STATS_SIZE * dev_nr)) == NULL) { + if ((d->dev_stats[i] = (struct io_stats *) malloc(sizeof(struct io_stats))) == NULL) { perror("malloc"); exit(4); } - memset(st_iodev[i], 0, IO_STATS_SIZE * dev_nr); + memset(d->dev_stats[i], 0, sizeof(struct io_stats)); } + strcpy(d->name, name); + d->exist = TRUE; + d->next = ds; - if ((st_hdr_iodev = - (struct io_hdr_stats *) malloc(IO_HDR_STATS_SIZE * dev_nr)) == NULL) { - perror("malloc"); - exit(4); + if (dtype == T_GROUP) { + d->dev_tp = dtype; } - memset(st_hdr_iodev, 0, IO_HDR_STATS_SIZE * dev_nr); -} - -/* - *************************************************************************** - * Allocate structures for devices entered on the command line. - * - * IN: - * @list_len Number of arguments on the command line. - *************************************************************************** - */ -void salloc_dev_list(int list_len) -{ - if ((st_dev_list = (struct io_dlist *) malloc(IO_DLIST_SIZE * list_len)) == NULL) { - perror("malloc"); - exit(4); + else if (is_device(name, ACCEPT_VIRTUAL_DEVICES)) { + d->dev_tp = (dtype == T_PART_DEV ? T_PART_DEV : T_DEV); + } + else { + /* This is a partition (T_PART) */ + d->dev_tp = T_PART; } - memset(st_dev_list, 0, IO_DLIST_SIZE * list_len); -} -/* - *************************************************************************** - * Free structures used for devices entered on the command line. - *************************************************************************** - */ -void sfree_dev_list(void) -{ - free(st_dev_list); + return d; } /* *************************************************************************** - * Look for the device in the device list and store it if not found. + * Get device major and minor numbers. * * IN: - * @dlist_idx Length of the device list. - * @device_name Name of the device. + * @filename Name of the device ("sda", "/dev/sdb1"...) * * OUT: - * @dlist_idx Length of the device list. + * @major Major number of the device. + * @minor Minor number of the device. * * RETURNS: - * Position of the device in the list. - *************************************************************************** - */ -int update_dev_list(int *dlist_idx, char *device_name) -{ - int i; - struct io_dlist *sdli = st_dev_list; - - for (i = 0; i < *dlist_idx; i++, sdli++) { - if (!strcmp(sdli->dev_name, device_name)) - break; - } - - if (i == *dlist_idx) { - /* - * Device not found: Store it. - * Group names will be distinguished from real device names - * thanks to their names which begin with a space. - */ - (*dlist_idx)++; - strncpy(sdli->dev_name, device_name, MAX_NAME_LEN - 1); - } - - return i; -} - -/* - *************************************************************************** - * Allocate and init structures, according to system state. - * - * IN: - * @iodev_nr Number of devices and partitions. + * 0 on success, and -1 otherwise. *************************************************************************** */ -void io_sys_init(int *iodev_nr) +int get_major_minor_nr(char filename[], int *major, int *minor) { - /* Allocate and init stat common counters */ - init_stats(); - - /* How many processors on this machine? */ - cpu_nr = get_cpu_nr(~0, FALSE); - - /* Get number of block devices and partitions in /proc/diskstats */ - if ((*iodev_nr = get_diskstats_dev_nr(CNT_PART, CNT_ALL_DEV)) > 0) { - flags |= I_F_HAS_DISKSTATS; - *iodev_nr += NR_DEV_PREALLOC; - } - - if (!HAS_DISKSTATS(flags) || - (DISPLAY_PARTITIONS(flags) && !DISPLAY_PART_ALL(flags))) { - /* - * If /proc/diskstats exists but we also want stats for the partitions - * of a particular device, stats will have to be found in /sys. So we - * need to know if /sys is mounted or not, and set flags accordingly. - */ - - /* Get number of block devices (and partitions) in sysfs */ - if ((*iodev_nr = get_sysfs_dev_nr(DISPLAY_PARTITIONS(flags))) > 0) { - flags |= I_F_HAS_SYSFS; - *iodev_nr += NR_DEV_PREALLOC; - } - else { - fprintf(stderr, _("Cannot find disk data\n")); - exit(2); - } - } + struct stat statbuf; + char *bang; + char dfile[MAX_PF_NAME]; - /* Also allocate stat structures for "group" devices */ - *iodev_nr += group_nr; - - /* - * Allocate structures for number of disks found, but also - * for groups of devices if option -g has been entered on the command line. - * iodev_nr must be <> 0. - */ - salloc_device(*iodev_nr); -} + snprintf(dfile, sizeof(dfile), "%s%s", filename[0] == '/' ? "" : SLASH_DEV, filename); + dfile[sizeof(dfile) - 1] = '\0'; -/* - *************************************************************************** - * When group stats are to be displayed (option -g entered on the command - * line), save devices and group names in the io_hdr_stats structures. This - * is normally done later when stats are actually read from /proc or /sys - * files (via a call to save_stats() function), but here we want to make - * sure that the structures are ordered and that each device belongs to its - * proper group. - * Note that we can still have an unexpected device that gets attached to a - * group as devices can be registered or unregistered dynamically. - * - * IN: - * @iodev_nr Number of devices and partitions. - * @dlist_idx Number of devices entered on the command line. - *************************************************************************** - */ -void presave_device_list(int iodev_nr, int *dlist_idx) -{ - int i; - struct io_hdr_stats *shi = st_hdr_iodev; - struct io_dlist *sdli = st_dev_list; - - if (*dlist_idx > 0) { - /* First, save the last group name entered on the command line in the list */ - update_dev_list(dlist_idx, group_name); - - /* Now save devices and group names in the io_hdr_stats structures */ - for (i = 0; (i < *dlist_idx) && (i < iodev_nr); i++, shi++, sdli++) { - strncpy(shi->name, sdli->dev_name, MAX_NAME_LEN); - shi->name[MAX_NAME_LEN - 1] = '\0'; - shi->used = TRUE; - if (shi->name[0] == ' ') { - /* Current device name is in fact the name of a group */ - shi->status = DISK_GROUP; - } - else { - shi->status = DISK_REGISTERED; - } - } - } - else { + while ((bang = strchr(dfile, '!'))) { /* - * No device names have been entered on the command line but - * the name of a group. Save that name at the end of the - * data table so that all devices that will be read will be - * included in that group. + * 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. */ - shi += iodev_nr - 1; - strncpy(shi->name, group_name, MAX_NAME_LEN); - shi->name[MAX_NAME_LEN - 1] = '\0'; - shi->used = TRUE; - shi->status = DISK_GROUP; + *bang = '/'; } -} -/* - *************************************************************************** - * Free various structures. - *************************************************************************** -*/ -void io_sys_free(void) -{ - int i; - - for (i = 0; i < 2; i++) { - /* Free CPU structures */ - free(st_cpu[i]); + if (__stat(dfile, &statbuf) < 0) + return -1; - /* Free I/O device structures */ - free(st_iodev[i]); - } + *major = major(statbuf.st_rdev); + *minor = minor(statbuf.st_rdev); - free(st_hdr_iodev); -} - -/* - *************************************************************************** - * Save stats for current device or partition. - * - * IN: - * @name Name of the device/partition. - * @curr Index in array for current sample statistics. - * @st_io Structure with device or partition to save. - * @iodev_nr Number of devices and partitions. - * @st_hdr_iodev Pointer on structures describing a device/partition. - * - * OUT: - * @st_hdr_iodev Pointer on structures describing a device/partition. - *************************************************************************** - */ -void save_stats(char *name, int curr, void *st_io, int iodev_nr, - struct io_hdr_stats *st_hdr_iodev) -{ - int i; - struct io_hdr_stats *st_hdr_iodev_i; - struct io_stats *st_iodev_i; - - /* Look for device in data table */ - for (i = 0; i < iodev_nr; i++) { - st_hdr_iodev_i = st_hdr_iodev + i; - if (!strcmp(st_hdr_iodev_i->name, name)) { - break; - } - } - - if (i == iodev_nr) { - /* - * This is a new device: Look for an unused entry to store it. - * Thus we are able to handle dynamically registered devices. - */ - for (i = 0; i < iodev_nr; i++) { - st_hdr_iodev_i = st_hdr_iodev + i; - if (!st_hdr_iodev_i->used) { - /* Unused entry found... */ - st_hdr_iodev_i->used = TRUE; /* Indicate it is now used */ - strncpy(st_hdr_iodev_i->name, name, MAX_NAME_LEN - 1); - st_hdr_iodev_i->name[MAX_NAME_LEN - 1] = '\0'; - st_iodev_i = st_iodev[!curr] + i; - memset(st_iodev_i, 0, IO_STATS_SIZE); - break; - } - } - } - if (i < iodev_nr) { - st_hdr_iodev_i = st_hdr_iodev + i; - if (st_hdr_iodev_i->status == DISK_UNREGISTERED) { - st_hdr_iodev_i->status = DISK_REGISTERED; - if (st_hdr_iodev_i->used == FALSE) { - st_iodev_i = st_iodev[!curr] + i; - memset(st_iodev_i, 0, IO_STATS_SIZE); - st_hdr_iodev_i->used = TRUE; - } - } - st_iodev_i = st_iodev[curr] + i; - *st_iodev_i = *((struct io_stats *) st_io); - } - /* - * else it was a new device - * but there was no free structure to store it. - */ + return 0; } /* @@ -506,16 +313,17 @@ void save_stats(char *name, int curr, void *st_io, int iodev_nr, * Read sysfs stat for current block device or partition. * * IN: - * @curr Index in array for current sample statistics. * @filename File name where stats will be read. - * @dev_name Device or partition name. - * @iodev_nr Number of devices and partitions. + * @ios Structure where stats will be saved. + * + * OUT: + * @ios Structure where stats have been saved. * * RETURNS: - * 0 if file couldn't be opened, 1 otherwise. + * 0 on success, -1 otherwise. *************************************************************************** */ -int read_sysfs_file_stat(int curr, char *filename, char *dev_name, int iodev_nr) +int read_sysfs_file_stat(char *filename, struct io_stats *ios) { FILE *fp; struct io_stats sdev; @@ -527,7 +335,7 @@ int read_sysfs_file_stat(int curr, char *filename, char *dev_name, int iodev_nr) /* Try to read given stat file */ if ((fp = fopen(filename, "r")) == NULL) - return 0; + return -1; i = fscanf(fp, "%lu %lu %lu %lu %lu %lu %lu %u %u %u %u %lu %lu %lu %lu", &rd_ios, &rd_merges_or_rd_sec, &rd_sec_or_wr_ios, &rd_ticks_or_wr_sec, @@ -566,182 +374,239 @@ int read_sysfs_file_stat(int curr, char *filename, char *dev_name, int iodev_nr) sdev.wr_sectors = rd_ticks_or_wr_sec; } - if ((i >= 11) || !DISPLAY_EXTENDED(flags)) { - /* - * In fact, we _don't_ save stats if it's a partition without - * extended stats and yet we want to display ext stats. - */ - save_stats(dev_name, curr, &sdev, iodev_nr, st_hdr_iodev); - } + *ios = sdev; fclose(fp); - return 1; + return 0; } /* *************************************************************************** - * Read sysfs stats for all the partitions of a device. + * 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. - * @dev_name Device name. - * @iodev_nr Number of devices and partitions. + * @dname Whole device name. + * + * RETURNS: + * 0 on success, -1 otherwise. *************************************************************************** */ -void read_sysfs_dlist_part_stat(int curr, char *dev_name, int iodev_nr) +int read_sysfs_device_part_stat(int curr, char *dname) { DIR *dir; struct dirent *drd; + struct io_stats sdev; + struct io_device *d; char dfile[MAX_PF_NAME], filename[MAX_PF_NAME + 512]; + int major, minor; - snprintf(dfile, sizeof(dfile), "%s/%s", SYSFS_BLOCK, dev_name); + snprintf(dfile, sizeof(dfile), "%s/%s", SYSFS_BLOCK, dname); dfile[sizeof(dfile) - 1] = '\0'; /* Open current device directory in /sys/block */ if ((dir = __opendir(dfile)) == NULL) - return; + 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 */ - read_sysfs_file_stat(curr, filename, drd->d_name, iodev_nr); + if (read_sysfs_file_stat(filename, &sdev) < 0) + continue; + + d = add_list_device(&dev_list, drd->d_name, 0); + if (d != NULL) { + *(d->dev_stats[curr]) = sdev; + } + + if (!d->major) { + /* Get major and minor numbers for given device */ + if (get_major_minor_nr(d->name, &major, &minor) == 0) { + d->major = major; + d->minor = minor; + } + } } /* Close device directory */ __closedir(dir); + + return 0; } /* *************************************************************************** - * Read stats from the sysfs filesystem for the devices entered on the - * command line. + * Read sysfs stats for every whole device. Devices are saved in the linked + * list. * * IN: * @curr Index in array for current sample statistics. - * @iodev_nr Number of devices and partitions. - * @dlist_idx Number of devices entered on the command line. + * + * RETURNS: + * 0 on success, -1 otherwise. *************************************************************************** */ -void read_sysfs_dlist_stat(int curr, int iodev_nr, int dlist_idx) +int read_sysfs_all_devices_stat(int curr) { - int dev, ok; - char filename[MAX_PF_NAME]; - char *slash; - struct io_dlist *st_dev_list_i; + DIR *dir; + struct dirent *drd; + struct io_stats sdev; + struct io_device *d; + char dfile[MAX_PF_NAME]; + int major, minor; - /* Every I/O device (or partition) is potentially unregistered */ - set_entries_unregistered(iodev_nr, st_hdr_iodev); + /* Open /sys/block directory */ + if ((dir = __opendir(SYSFS_BLOCK)) == NULL) + return -1; - for (dev = 0; dev < dlist_idx; dev++) { - st_dev_list_i = st_dev_list + dev; + /* Get current entry */ + while ((drd = __readdir(dir)) != NULL) { - /* Some devices may have a slash in their name (eg. cciss/c0d0...) */ - while ((slash = strchr(st_dev_list_i->dev_name, '/'))) { - *slash = '!'; - } + if (!strcmp(drd->d_name, ".") || !strcmp(drd->d_name, "..")) + continue; + snprintf(dfile, sizeof(dfile), "%s/%s/%s", SYSFS_BLOCK, drd->d_name, S_STAT); + dfile[sizeof(dfile) - 1] = '\0'; - snprintf(filename, MAX_PF_NAME, "%s/%s/%s", - SYSFS_BLOCK, st_dev_list_i->dev_name, S_STAT); - filename[MAX_PF_NAME - 1] = '\0'; + /* Read current whole device stats */ + if (read_sysfs_file_stat(dfile, &sdev) < 0) + continue; - /* Read device stats */ - ok = read_sysfs_file_stat(curr, filename, st_dev_list_i->dev_name, iodev_nr); + d = add_list_device(&dev_list, drd->d_name, 0); + if (d != NULL) { + *(d->dev_stats[curr]) = sdev; + } - if (ok && st_dev_list_i->disp_part) { - /* Also read stats for its partitions */ - read_sysfs_dlist_part_stat(curr, st_dev_list_i->dev_name, iodev_nr); + if (!d->major) { + /* Get major and minor numbers for given device */ + if (get_major_minor_nr(d->name, &major, &minor) == 0) { + d->major = major; + d->minor = minor; + } } } - /* Free structures corresponding to unregistered devices */ - free_unregistered_entries(iodev_nr, st_hdr_iodev); + /* Close device directory */ + __closedir(dir); + + return 0; } /* *************************************************************************** - * Read stats from the sysfs filesystem for every block devices found. + * Read sysfs stats for a partition using /sys/dev/block/M:m/ directory. * * IN: * @curr Index in array for current sample statistics. - * @iodev_nr Number of devices and partitions. + * @d Device structure. + * + * RETURNS: + * 0 on success, and -1 otherwise. *************************************************************************** */ -void read_sysfs_stat(int curr, int iodev_nr) +int read_sysfs_part_stat(int curr, struct io_device *d) { - DIR *dir; - struct dirent *drd; - char filename[MAX_PF_NAME]; - int ok; + char dfile[MAX_PF_NAME]; + int major, minor; - /* Every I/O device entry is potentially unregistered */ - set_entries_unregistered(iodev_nr, st_hdr_iodev); + if (!d->major) { + /* Get major and minor numbers for given device */ + if (get_major_minor_nr(d->name, &major, &minor) < 0) + return -1; + d->major = major; + d->minor = minor; + } - /* Open /sys/block directory */ - if ((dir = __opendir(SYSFS_BLOCK)) != NULL) { + /* Read stats for device */ + snprintf(dfile, sizeof(dfile), "%s/%d:%d/%s", + SYSFS_DEV_BLOCK, major, minor, S_STAT); + dfile[sizeof(dfile) - 1] = '\0'; - /* Get current entry */ - while ((drd = __readdir(dir)) != NULL) { - if (!strcmp(drd->d_name, ".") || !strcmp(drd->d_name, "..")) - continue; - snprintf(filename, MAX_PF_NAME, "%s/%s/%s", - SYSFS_BLOCK, drd->d_name, S_STAT); - filename[MAX_PF_NAME - 1] = '\0'; + return read_sysfs_file_stat(dfile, d->dev_stats[curr]); +} + +/* + *************************************************************************** + * 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; + char dfile[MAX_PF_NAME]; - /* If current entry is a directory, try to read its stat file */ - ok = read_sysfs_file_stat(curr, filename, drd->d_name, iodev_nr); + for (dlist = dev_list; dlist != NULL; dlist = dlist->next) { + if (dlist->exist) + /* Device statis already read */ + continue; + else if (dlist->dev_tp == T_PART) { /* - * If '-p ALL' was entered on the command line, - * also try to read stats for its partitions + * This is a partition. + * Read its stats using /sys/dev/block/M:n/ directory. */ - if (ok && DISPLAY_PART_ALL(flags)) { - read_sysfs_dlist_part_stat(curr, drd->d_name, iodev_nr); + if (read_sysfs_part_stat(curr, dlist) == 0) { + dlist->exist = TRUE; } } - /* Close /sys/block directory */ - __closedir(dir); + else if ((dlist->dev_tp == T_PART_DEV) || (dlist->dev_tp == T_DEV)) { + /* Read stats for current whole device using /sys/block/ directory */ + snprintf(dfile, sizeof(dfile), "%s/%s/%s", + SYSFS_BLOCK, dlist->name, S_STAT); + dfile[sizeof(dfile) - 1] = '\0'; + if (read_sysfs_file_stat(dfile, 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); + } + } } - /* Free structures corresponding to unregistered devices */ - free_unregistered_entries(iodev_nr, st_hdr_iodev); + /* Read all whole devices stats if requested ("iostat ALL ...") */ + if (DISPLAY_ALL_DEVICES(flags)) { + read_sysfs_all_devices_stat(curr); + } } /* *************************************************************************** - * Read stats from /proc/diskstats. + * Read stats from /proc/diskstats. Only used when "-p ALL" has been entered + * on the command line. * * IN: * @curr Index in array for current sample statistics. - * @iodev_nr Number of devices and partitions. - * @dlist_idx Number of devices entered on the command line. *************************************************************************** */ -void read_diskstats_stat(int curr, int iodev_nr, int dlist_idx) +void read_diskstats_stat(int curr) { FILE *fp; char line[256], dev_name[MAX_NAME_LEN]; - char *dm_name; + struct io_device *d; struct io_stats sdev; int i; unsigned int ios_pgr, tot_ticks, rq_ticks, wr_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, dc_ticks; - char *ioc_dname; unsigned int major, minor; memset(&sdev, 0, sizeof(struct io_stats)); - /* Every I/O device entry is potentially unregistered */ - set_entries_unregistered(iodev_nr, st_hdr_iodev); - if ((fp = fopen(DISKSTATS, "r")) == NULL) return; @@ -755,10 +620,6 @@ void read_diskstats_stat(int curr, int iodev_nr, int dlist_idx) &dc_ios, &dc_merges, &dc_sec, &dc_ticks); if (i >= 14) { - /* Device or partition */ - if (!dlist_idx && !DISPLAY_PARTITIONS(flags) && - !is_device(dev_name, ACCEPT_VIRTUAL_DEVICES)) - continue; sdev.rd_ios = rd_ios; sdev.rd_merges = rd_merges_or_rd_sec; sdev.rd_sectors = rd_sec_or_wr_ios; @@ -781,8 +642,7 @@ void read_diskstats_stat(int curr, int iodev_nr, int dlist_idx) } else if (i == 7) { /* Partition without extended statistics */ - if (DISPLAY_EXTENDED(flags) || - (!dlist_idx && !DISPLAY_PARTITIONS(flags))) + if (DISPLAY_EXTENDED(flags)) continue; sdev.rd_ios = rd_ios; @@ -794,90 +654,49 @@ void read_diskstats_stat(int curr, int iodev_nr, int dlist_idx) /* Unknown entry: Ignore it */ continue; - if ((ioc_dname = ioc_name(major, minor)) != NULL) { - if (strcmp(dev_name, ioc_dname) && strcmp(ioc_dname, K_NODEV)) { - /* - * No match: Use name generated from sysstat.ioconf data - * (if different from "nodev") works around known issues - * with EMC PowerPath. - */ - strncpy(dev_name, ioc_dname, MAX_NAME_LEN - 1); - dev_name[MAX_NAME_LEN - 1] = '\0'; - } - } - - if ((DISPLAY_DEVMAP_NAME(flags)) && (major == dm_major)) { - /* - * If the device is a device mapper device, try to get its - * assigned name of its logical device. - */ - dm_name = transform_devmapname(major, minor); - if (dm_name) { - strncpy(dev_name, dm_name, MAX_NAME_LEN - 1); - dev_name[MAX_NAME_LEN - 1] = '\0'; - } + d = add_list_device(&dev_list, dev_name, 0); + if (d != NULL) { + *d->dev_stats[curr] = sdev; + d->major = major; + d->minor = minor; } - - save_stats(dev_name, curr, &sdev, iodev_nr, st_hdr_iodev); } fclose(fp); - - /* Free structures corresponding to unregistered devices */ - free_unregistered_entries(iodev_nr, st_hdr_iodev); } /* *************************************************************************** - * Compute stats for device groups using stats of every device belonging - * to each of these groups. + * 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, int iodev_nr) +void compute_device_groups_stats(int curr, struct io_device *d, struct io_device *g) { - struct io_stats gdev, *ioi; - struct io_hdr_stats *shi = st_hdr_iodev; - int i, nr_disks; - - memset(&gdev, 0, IO_STATS_SIZE); - nr_disks = 0; - - for (i = 0; i < iodev_nr; i++, shi++) { - if (shi->used && (shi->status == DISK_REGISTERED)) { - ioi = st_iodev[curr] + i; - - if (!DISPLAY_UNFILTERED(flags)) { - if (!ioi->rd_ios && !ioi->wr_ios && !ioi->dc_ios) - continue; - } - - gdev.rd_ios += ioi->rd_ios; - gdev.rd_merges += ioi->rd_merges; - gdev.rd_sectors += ioi->rd_sectors; - gdev.rd_ticks += ioi->rd_ticks; - gdev.wr_ios += ioi->wr_ios; - gdev.wr_merges += ioi->wr_merges; - gdev.wr_sectors += ioi->wr_sectors; - gdev.wr_ticks += ioi->wr_ticks; - gdev.dc_ios += ioi->dc_ios; - gdev.dc_merges += ioi->dc_merges; - gdev.dc_sectors += ioi->dc_sectors; - gdev.dc_ticks += ioi->dc_ticks; - gdev.ios_pgr += ioi->ios_pgr; - gdev.tot_ticks += ioi->tot_ticks; - gdev.rq_ticks += ioi->rq_ticks; - nr_disks++; - } - else if (shi->status == DISK_GROUP) { - save_stats(shi->name, curr, &gdev, iodev_nr, st_hdr_iodev); - shi->used = nr_disks; - nr_disks = 0; - memset(&gdev, 0, IO_STATS_SIZE); - } - } + if (!DISPLAY_UNFILTERED(flags)) { + if (!d->dev_stats[curr]->rd_ios && + !d->dev_stats[curr]->wr_ios && + !d->dev_stats[curr]->dc_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]->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; } /* @@ -1112,7 +931,7 @@ void write_disk_stat_header(int *fctr, int *tab, int hpart) * @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. - * @shi Structures describing the devices and partitions. + * @d Structure containing device description. * @ioi Current sample statistics. * @ioj Previous sample statistics. * @devname Current device name. @@ -1121,14 +940,28 @@ void write_disk_stat_header(int *fctr, int *tab, int hpart) *************************************************************************** */ void write_plain_ext_stat(unsigned long long itv, int fctr, int hpart, - struct io_hdr_stats *shi, struct io_stats *ioi, + 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_HUMAN_READ(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 */ cprintf_f(NO_UNIT, 1, 8, 2, @@ -1156,12 +989,8 @@ void write_plain_ext_stat(unsigned long long itv, int fctr, int hpart, /* * %util * Again: Ticks in milliseconds. - * In the case of a device group (option -g), shi->used is the number of - * devices in the group. Else shi->used equals 1. */ - cprintf_pc(DISPLAY_UNIT(flags), 1, 6, 2, - shi->used ? xds->util / 10.0 / (double) shi->used - : xds->util / 10.0); /* shi->used should never be zero here */ + cprintf_pc(DISPLAY_UNIT(flags), 1, 6, 2, xds->util / 10.0 / (double) n); } else { if ((hpart == 1) || !hpart) { @@ -1240,12 +1069,14 @@ void write_plain_ext_stat(unsigned long long itv, int fctr, int hpart, /* * %util * Again: Ticks in milliseconds. - * In the case of a device group (option -g), shi->used is the number of - * devices in the group. Else shi->used equals 1. */ - cprintf_pc(DISPLAY_UNIT(flags), 1, 6, 2, - shi->used ? xds->util / 10.0 / (double) shi->used - : xds->util / 10.0); /* shi->used should never be zero here */ + 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); } } @@ -1264,7 +1095,7 @@ void write_plain_ext_stat(unsigned long long itv, int fctr, int hpart, * @tab Number of tabs to print. * @itv Interval of time. * @fctr Conversion factor. - * @shi Structures describing the devices and partitions. + * @d Structure containing the device description. * @ioi Current sample statistics. * @ioj Previous sample statistics. * @devname Current device name. @@ -1273,12 +1104,17 @@ void write_plain_ext_stat(unsigned long long itv, int fctr, int hpart, *************************************************************************** */ void write_json_ext_stat(int tab, unsigned long long itv, int fctr, - struct io_hdr_stats *shi, struct io_stats *ioi, + 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); @@ -1342,9 +1178,14 @@ void write_json_ext_stat(int tab, unsigned long long itv, int fctr, xios->darqsz / 2, S_VALUE(ioj->rq_ticks, ioi->rq_ticks, itv) / 1000.0); } - printf("\"util\": %.2f}", - shi->used ? xds->util / 10.0 / (double) shi->used - : xds->util / 10.0); /* shi->used should never be zero here */ + + if (d->dev_tp > T_GROUP) { + n = d->dev_tp - T_GROUP; + } + else { + n = 1; + } + printf("\"util\": %.2f}", xds->util / 10.0 / (double) n); } /* @@ -1358,17 +1199,17 @@ void write_json_ext_stat(int tab, unsigned long long itv, int fctr, * @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. - * @shi Structures describing the devices and partitions. + * @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_hdr_stats *shi, struct io_stats *ioi, - struct io_stats *ioj, int tab) + struct io_device *d, struct io_stats *ioi, + struct io_stats *ioj, int tab, char *dname) { - char *devname = NULL; struct stats_disk sdc, sdp; struct ext_disk_stats xds; struct ext_io_stats xios; @@ -1463,19 +1304,11 @@ void write_ext_stat(unsigned long long itv, int fctr, int hpart, } } - /* Get device name */ - if (DISPLAY_PERSIST_NAME_I(flags)) { - devname = get_persistent_name_from_pretty(shi->name); - } - if (!devname) { - devname = shi->name; - } - if (DISPLAY_JSON_OUTPUT(flags)) { - write_json_ext_stat(tab, itv, fctr, shi, ioi, ioj, devname, &xds, &xios); + write_json_ext_stat(tab, itv, fctr, d, ioi, ioj, dname, &xds, &xios); } else { - write_plain_ext_stat(itv, fctr, hpart, shi, ioi, ioj, devname, &xds, &xios); + write_plain_ext_stat(itv, fctr, hpart, d, ioi, ioj, dname, &xds, &xios); } } @@ -1608,27 +1441,19 @@ void write_json_basic_stat(int tab, unsigned long long itv, int fctr, * IN: * @itv Interval of time. * @fctr Conversion factor. - * @shi Structures describing the devices and partitions. + * @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_hdr_stats *shi, struct io_stats *ioi, - struct io_stats *ioj, int tab) + struct io_device *d, struct io_stats *ioi, + struct io_stats *ioj, int tab, char *dname) { - char *devname = NULL; unsigned long long rd_sec, wr_sec, dc_sec; - /* Print device name */ - if (DISPLAY_PERSIST_NAME_I(flags)) { - devname = get_persistent_name_from_pretty(shi->name); - } - if (!devname) { - devname = shi->name; - } - /* 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)) { @@ -1644,11 +1469,11 @@ void write_basic_stat(unsigned long long itv, int fctr, } if (DISPLAY_JSON_OUTPUT(flags)) { - write_json_basic_stat(tab, itv, fctr, ioi, ioj, devname, + 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, devname, + write_plain_basic_stat(itv, fctr, ioi, ioj, dname, rd_sec, wr_sec, dc_sec); } } @@ -1660,26 +1485,26 @@ void write_basic_stat(unsigned long long itv, int fctr, * IN: * @curr Index in array for current sample statistics. * @rectime Current date and time. - * @iodev_nr Number of devices and partitions. - * @dlist_idx Number of devices entered on the command line. + * @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 iodev_nr, int dlist_idx) +void write_stats(int curr, struct tm *rectime, int skip) { - int dev, h, hl = 0, hh = 0, i, fctr = 1, tab = 4, next = FALSE; + int h, hl = 0, hh = 0, fctr = 1, tab = 4, next = FALSE; unsigned long long itv; - struct io_hdr_stats *shi; - struct io_dlist *st_dev_list_i; + struct io_device *d, *dtmp, *g = NULL, *dnext = NULL; + char *dev_name, *pdname; /* Test stdout */ TEST_STDOUT(STDOUT_FILENO); - if (DISPLAY_JSON_OUTPUT(flags)) { + if (DISPLAY_JSON_OUTPUT(flags) && !skip) { xprintf(tab++, "{"); } /* Print time stamp */ - if (DISPLAY_TIMESTAMP(flags)) { + if (DISPLAY_TIMESTAMP(flags) && !skip) { write_sample_timestamp(tab, rectime); #ifdef DEBUG if (DISPLAY_DEBUG(flags)) { @@ -1688,7 +1513,7 @@ void write_stats(int curr, struct tm *rectime, int iodev_nr, int dlist_idx) #endif } - if (DISPLAY_CPU(flags)) { + if (DISPLAY_CPU(flags) && !skip) { /* Display CPU utilization */ write_cpu_stat(curr, tab); @@ -1704,7 +1529,9 @@ void write_stats(int curr, struct tm *rectime, int iodev_nr, int dlist_idx) itv = get_interval(uptime_cs[!curr], uptime_cs[curr]); if (DISPLAY_DISK(flags)) { - struct io_stats *ioi, *ioj; + struct io_stats *ioi, *ioj, iozero; + + memset(&iozero, 0, sizeof(struct io_stats)); if (DISPLAY_HUMAN_READ(flags) && DISPLAY_EXTENDED(flags) && @@ -1715,111 +1542,180 @@ void write_stats(int curr, struct tm *rectime, int iodev_nr, int dlist_idx) for (h = hl; h <= hh; h++) { - shi = st_hdr_iodev; + if (!skip) { + /* Display disk stats header */ + write_disk_stat_header(&fctr, &tab, h); + } - /* Display disk stats header */ - write_disk_stat_header(&fctr, &tab, h); + for (d = dev_list; ; d = dnext) { - for (i = 0; i < iodev_nr; i++, shi++) { - if (shi->used) { + 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 (dlist_idx && !HAS_SYSFS(flags)) { + if (d->dev_tp >= T_GROUP) { /* - * With /proc/diskstats, stats for every device - * are read even if we have entered a list on devices - * on the command line. Thus we need to check - * if stats for current device are to be displayed. + * This is a new group: Save group position + * and display previous one. */ - for (dev = 0; dev < dlist_idx; dev++) { - st_dev_list_i = st_dev_list + dev; - if (!strcmp(shi->name, st_dev_list_i->dev_name)) - break; + 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 (dev == dlist_idx) - /* Device not found in list: Don't display it */ - continue; } + } - ioi = st_iodev[curr] + i; - ioj = st_iodev[!curr] + i; + if (!d->exist && (d->dev_tp < T_GROUP)) + /* Current device is non existent (e.g. it has been unregistered from the system */ + continue; - if (!DISPLAY_UNFILTERED(flags)) { - if (!ioi->rd_ios && !ioi->wr_ios && !ioi->dc_ios) - 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]; + + if (!DISPLAY_UNFILTERED(flags)) { + if (!ioi->rd_ios && !ioi->wr_ios && !ioi->dc_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)) + /* No activity: Ignore it */ + 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)) - /* 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) < (ioj->rd_ios + ioj->wr_ios + ioj->dc_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; + } + + /* Get device name to print */ + dev_name = NULL; + + if ((DISPLAY_DEVMAP_NAME(flags)) && (d->major == dm_major)) { + /* + * If the device is a device mapper device, try to get its + * assigned name of its logical device. + */ + dev_name = transform_devmapname(d->major, d->minor); + } + + if (!dev_name && ((dev_name = ioc_name(d->major, d->minor)) != NULL)) { + if (!strcmp(d->name, dev_name) || !strcmp(dev_name, K_NODEV)) { + /* + * Ignore name given by sysstat.ioconf if it's the same + * as current one or if it's "nodev". + * NB: Using names generated from sysstat.ioconf data + * works around known issues with EMC PowerPath. + */ + dev_name = NULL; } + } - if (DISPLAY_GROUP_TOTAL_ONLY(flags)) { - if (shi->status != DISK_GROUP) - continue; + if (DISPLAY_PERSIST_NAME_I(flags)) { + pdname = get_persistent_name_from_pretty(dev_name ? dev_name : d->name); + if (pdname) { + dev_name = pdname; } + } + + if (!dev_name) { + dev_name = 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 " - "ios_pgr=%u tot_ticks=%u " - "rq_ticks=%u }\n", - shi->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->ios_pgr, - ioi->tot_ticks, - ioi->rq_ticks); - } + 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 " + "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->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, shi, ioi, ioj, tab); + write_ext_stat(itv, fctr, h, d, ioi, ioj, tab, dev_name); } else { - write_basic_stat(itv, fctr, shi, ioi, ioj, tab); + write_basic_stat(itv, fctr, d, ioi, ioj, tab, dev_name); } } } - if ((h > 0) && (h < hh)) { + + if ((h > 0) && (h < hh) && !skip) { printf("\n"); } } - if (DISPLAY_JSON_OUTPUT(flags)) { + if (DISPLAY_JSON_OUTPUT(flags) && !skip) { printf("\n"); xprintf(--tab, "]"); } } - if (DISPLAY_JSON_OUTPUT(flags)) { - xprintf0(--tab, "}"); - } - else { - printf("\n"); + if (!skip) { + if (DISPLAY_JSON_OUTPUT(flags)) { + xprintf0(--tab, "}"); + } + else { + printf("\n"); + } } } @@ -1830,11 +1726,9 @@ void write_stats(int curr, struct tm *rectime, int iodev_nr, int dlist_idx) * IN: * @count Number of reports to print. * @rectime Current date and time. - * @iodev_nr Number of devices and partitions. - * @dlist_idx Number of devices entered on the command line. *************************************************************************** */ -void rw_io_stat_loop(long int count, struct tm *rectime, int iodev_nr, int dlist_idx) +void rw_io_stat_loop(long int count, struct tm *rectime) { int curr = 1; int skip = 0; @@ -1859,6 +1753,9 @@ void rw_io_stat_loop(long int count, struct tm *rectime, int iodev_nr, int dlist setbuf(stdout, NULL); do { + /* Every device is potentially nonexistent */ + set_devices_nonexistent(dev_list); + /* Read system uptime (only for SMP machines) */ read_uptime(&(uptime_cs[curr])); @@ -1875,45 +1772,20 @@ void rw_io_stat_loop(long int count, struct tm *rectime, int iodev_nr, int dlist st_cpu[curr]->cpu_iowait + st_cpu[curr]->cpu_hardirq + st_cpu[curr]->cpu_steal + st_cpu[curr]->cpu_softirq; - if (dlist_idx) { - /* - * A device or partition name was explicitly entered - * on the command line, with or without -p option - * (but not -p ALL). - */ - if (HAS_DISKSTATS(flags) && !DISPLAY_PARTITIONS(flags)) { - read_diskstats_stat(curr, iodev_nr, dlist_idx); - } - else if (HAS_SYSFS(flags)) { - read_sysfs_dlist_stat(curr, iodev_nr, dlist_idx); - } + if (DISPLAY_EVERYTHING(flags)) { + read_diskstats_stat(curr); } else { - /* - * No devices nor partitions entered on the command line - * (for example if -p ALL was used). - */ - if (HAS_DISKSTATS(flags)) { - read_diskstats_stat(curr, iodev_nr, dlist_idx); - } - else if (HAS_SYSFS(flags)) { - read_sysfs_stat(curr, iodev_nr); - } - } - - /* Compute device groups stats */ - if (group_nr > 0) { - compute_device_groups_stats(curr, iodev_nr); + read_sysfs_dlist_stat(curr); } /* Get time */ get_localtime(rectime, 0); - /* Check whether we should skip first report */ - if (!skip) { - /* Print results */ - write_stats(curr, rectime, iodev_nr, dlist_idx); + /* Print results */ + write_stats(curr, rectime, skip); + if (!skip) { if (count > 0) { count--; } @@ -1948,19 +1820,14 @@ void rw_io_stat_loop(long int count, struct tm *rectime, int iodev_nr, int dlist */ int main(int argc, char **argv) { - /* Nb of devices and partitions found. Includes nb of device groups */ - int iodev_nr = 0; - /* Nb of devices entered on the command line */ - int dlist_idx = 0; - int it = 0; int opt = 1; int i, report_set = FALSE; long count = 1; struct utsname header; - struct io_dlist *st_dev_list_i; struct tm rectime; char *t, *persist_devname, *devname; + char group_name[MAX_NAME_LEN]; #ifdef USE_NLS /* Init National Language Support */ @@ -1970,17 +1837,11 @@ int main(int argc, char **argv) /* Init color strings */ init_colors(); - /* Allocate structures for device list */ - if (argc > 1) { - salloc_dev_list(argc - 1 + count_csvalues(argc, argv)); - } - /* Process args... */ while (opt < argc) { /* -p option used individually. See below for grouped use */ if (!strcmp(argv[opt], "-p")) { - flags |= I_D_PARTITIONS; if (argv[++opt] && (strspn(argv[opt], DIGITS) != strlen(argv[opt])) && (strncmp(argv[opt], "-", 1))) { @@ -1988,7 +1849,7 @@ int main(int argc, char **argv) for (t = strtok(argv[opt], ","); t; t = strtok(NULL, ",")) { if (!strcmp(t, K_ALL)) { - flags |= I_D_PART_ALL; + flags |= I_D_EVERYTHING; } else { devname = device_name(t); @@ -2000,40 +1861,28 @@ int main(int argc, char **argv) } } /* Store device name */ - i = update_dev_list(&dlist_idx, devname); - st_dev_list_i = st_dev_list + i; - st_dev_list_i->disp_part = TRUE; + add_list_device(&dev_list, devname, T_PART_DEV); } } opt++; } else { - flags |= I_D_PART_ALL; + flags |= I_D_EVERYTHING; } } else if (!strcmp(argv[opt], "-g")) { - /* - * Option -g: Stats for a group of devices. - * group_name contains the last group name entered on - * the command line. If we define an additional one, save - * the previous one in the list. We do that this way because we - * want the group name to appear in the list _after_ all - * the devices included in that group. The last group name - * will be saved in the list later, in presave_device_list() function. - */ - if (group_nr > 0) { - update_dev_list(&dlist_idx, group_name); - } 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++]); - group_nr++; + add_list_device(&dev_list, group_name, T_GROUP); } else if (!strcmp(argv[opt], "--human")) { @@ -2144,7 +1993,7 @@ int main(int argc, char **argv) case 'p': /* If option -p is grouped then it cannot take an arg */ - flags |= I_D_PARTITIONS + I_D_PART_ALL; + flags |= I_D_EVERYTHING; break; case 's': @@ -2201,9 +2050,10 @@ int main(int argc, char **argv) devname = persist_devname; } } - update_dev_list(&dlist_idx, devname); + add_list_device(&dev_list, devname, 0); } else { + flags |= I_D_ALL_DEVICES; opt++; } } @@ -2241,24 +2091,22 @@ int main(int argc, char **argv) * Also display DISK reports if options -p, -x or a device has been entered * on the command line. */ - if (DISPLAY_PARTITIONS(flags) || DISPLAY_EXTENDED(flags) || + if (DISPLAY_EVERYTHING(flags) || DISPLAY_EXTENDED(flags) || DISPLAY_UNFILTERED(flags)) { flags |= I_D_DISK; } - /* Option -T can only be used with option -g */ - if (DISPLAY_GROUP_TOTAL_ONLY(flags) && !group_nr) { + 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(); - /* Ignore device list if '-p ALL' entered on the command line */ - if (DISPLAY_PART_ALL(flags)) { - dlist_idx = 0; - } - if (DISPLAY_DEVMAP_NAME(flags)) { dm_major = get_devmap_major(); } @@ -2268,15 +2116,11 @@ int main(int argc, char **argv) setlocale(LC_NUMERIC, "C"); } - /* Init structures according to machine architecture */ - io_sys_init(&iodev_nr); - if (group_nr > 0) { - /* - * If groups of devices have been defined - * then save devices and groups in the list. - */ - presave_device_list(iodev_nr, &dlist_idx); - } + /* 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); @@ -2292,11 +2136,7 @@ int main(int argc, char **argv) } /* Main loop */ - rw_io_stat_loop(count, &rectime, iodev_nr, dlist_idx); - - /* Free structures */ - io_sys_free(); - sfree_dev_list(); + rw_io_stat_loop(count, &rectime); return 0; } diff --git a/iostat.h b/iostat.h index ce6ccc1..621d56f 100644 --- a/iostat.h +++ b/iostat.h @@ -13,14 +13,14 @@ #define I_D_DISK 0x000002 #define I_D_TIMESTAMP 0x000004 #define I_D_EXTENDED 0x000008 -#define I_D_PART_ALL 0x000010 +#define I_D_EVERYTHING 0x000010 #define I_D_KILOBYTES 0x000020 -#define I_F_HAS_SYSFS 0x000040 +/* Unused 0x000040 */ #define I_D_DEBUG 0x000080 #define I_D_UNFILTERED 0x000100 #define I_D_MEGABYTES 0x000200 -#define I_D_PARTITIONS 0x000400 -#define I_F_HAS_DISKSTATS 0x000800 +#define I_D_ALL_DEVICES 0x000400 +#define I_F_GROUP_DEFINED 0x000800 #define I_D_HUMAN_READ 0x001000 #define I_D_PERSIST_NAME 0x002000 #define I_D_OMIT_SINCE_BOOT 0x004000 @@ -36,14 +36,13 @@ #define DISPLAY_DISK(m) (((m) & I_D_DISK) == I_D_DISK) #define DISPLAY_TIMESTAMP(m) (((m) & I_D_TIMESTAMP) == I_D_TIMESTAMP) #define DISPLAY_EXTENDED(m) (((m) & I_D_EXTENDED) == I_D_EXTENDED) -#define DISPLAY_PART_ALL(m) (((m) & I_D_PART_ALL) == I_D_PART_ALL) +#define DISPLAY_EVERYTHING(m) (((m) & I_D_EVERYTHING) == I_D_EVERYTHING) #define DISPLAY_KILOBYTES(m) (((m) & I_D_KILOBYTES) == I_D_KILOBYTES) #define DISPLAY_MEGABYTES(m) (((m) & I_D_MEGABYTES) == I_D_MEGABYTES) -#define HAS_SYSFS(m) (((m) & I_F_HAS_SYSFS) == I_F_HAS_SYSFS) #define DISPLAY_DEBUG(m) (((m) & I_D_DEBUG) == I_D_DEBUG) #define DISPLAY_UNFILTERED(m) (((m) & I_D_UNFILTERED) == I_D_UNFILTERED) -#define DISPLAY_PARTITIONS(m) (((m) & I_D_PARTITIONS) == I_D_PARTITIONS) -#define HAS_DISKSTATS(m) (((m) & I_F_HAS_DISKSTATS) == I_F_HAS_DISKSTATS) +#define DISPLAY_ALL_DEVICES(m) (((m) & I_D_ALL_DEVICES) == I_D_ALL_DEVICES) +#define GROUP_DEFINED(m) (((m) & I_F_GROUP_DEFINED) == I_F_GROUP_DEFINED) #define DISPLAY_HUMAN_READ(m) (((m) & I_D_HUMAN_READ) == I_D_HUMAN_READ) #define DISPLAY_PERSIST_NAME_I(m) (((m) & I_D_PERSIST_NAME) == I_D_PERSIST_NAME) #define DISPLAY_OMIT_SINCE_BOOT(m) (((m) & I_D_OMIT_SINCE_BOOT) == I_D_OMIT_SINCE_BOOT) @@ -55,8 +54,10 @@ #define DISPLAY_UNIT(m) (((m) & I_D_UNIT) == I_D_UNIT) #define DISPLAY_SHORT_OUTPUT(m) (((m) & I_D_SHORT_OUTPUT) == I_D_SHORT_OUTPUT) -/* Preallocation constants */ -#define NR_DEV_PREALLOC 4 +#define T_PART 0 +#define T_DEV 1 +#define T_PART_DEV 2 +#define T_GROUP 3 /* Environment variable */ #define ENV_POSIXLY_CORRECT "POSIXLY_CORRECT" @@ -107,6 +108,24 @@ struct io_stats { #define IO_STATS_SIZE (sizeof(struct io_stats)) +struct io_device { + char name[MAX_NAME_LEN]; + /* + * 0: Not a whole device (T_PART) + * 1: whole device (T_DEV) + * 2: whole device and all its partitions to be read (T_PART_DEV) + * 3+: group name (T_GROUP) (4 means 1 device in the group, 5 means 2 devices in the group, etc.) + */ + int dev_tp; + /* TRUE if device exists in /proc/diskstats or /sys. Don't apply for groups. */ + int exist; + /* major and minor numbers are set only for partitions (T_PART), not whole devices */ + int major; + int minor; + struct io_stats *dev_stats[2]; + struct io_device *next; +}; + struct ext_io_stats { /* r_await */ double r_await; @@ -136,11 +155,6 @@ struct ext_io_stats { double darqsz; }; -/* Possible values for field "status" in io_hdr_stats structure */ -#define DISK_UNREGISTERED 0 -#define DISK_REGISTERED 1 -#define DISK_GROUP 2 - /* * Each io_stats structure has an associated io_hdr_stats structure. * An io_hdr_stats structure tells if the corresponding device has been diff --git a/systest.c b/systest.c index a4d1cd0..13e6a17 100644 --- a/systest.c +++ b/systest.c @@ -145,6 +145,7 @@ void next_time_step(void) * If current file is considered as a virtual one ("virtualhd"), then set * its device ID (major 253, minor 2, corresponding here to dm-2) in the * stat structure normally filled by the stat() system call. + * Otherwise, open file and read its major and minor numbers. * * IN: * @name Pathname to file. @@ -153,16 +154,32 @@ void next_time_step(void) * @statbuf Structure containing device ID. * * RETURNS: - * 0 if it actually was the virtual device, 1 otherwise. + * 0 if it actually was the virtual device, 1 otherwise, and -1 on failure. *************************************************************************** */ int virtual_stat(const char *name, struct stat *statbuf) { + FILE *fp; + char line[128]; + int major, minor; + if (!strcmp(name, VIRTUALHD)) { statbuf->st_rdev = (253 << 8) + 2; return 0; } + statbuf->st_rdev = 0; + + if ((fp = fopen(name, "r")) == NULL) + return -1; + + if (fgets(line, sizeof(line), fp) != NULL) { + sscanf(line, "%d %d", &major, &minor); + statbuf->st_rdev = (major << 8) + minor; + } + + fclose(fp); + return 1; }