diff --git a/cmds-device.c b/cmds-device.c index 75ee293d..d4938f4e 100644 --- a/cmds-device.c +++ b/cmds-device.c @@ -284,12 +284,129 @@ static int cmd_ready_dev(int argc, char **argv) return ret; } +static const char * const cmd_dev_stats_usage[] = { + "btrfs device stats [-z] |", + "Show current device IO stats. -z to reset stats afterwards.", + NULL +}; + +static int cmd_dev_stats(int argc, char **argv) +{ + char *path; + struct btrfs_ioctl_fs_info_args fi_args; + struct btrfs_ioctl_dev_info_args *di_args = NULL; + int ret; + int fdmnt; + int i; + char c; + int fdres = -1; + int err = 0; + __u64 flags = 0; + + optind = 1; + while ((c = getopt(argc, argv, "z")) != -1) { + switch (c) { + case 'z': + flags = BTRFS_DEV_STATS_RESET; + break; + case '?': + default: + fprintf(stderr, "ERROR: device stat args invalid.\n" + " device stat [-z] |\n" + " -z to reset stats after reading.\n"); + return 1; + } + } + + if (optind + 1 != argc) { + fprintf(stderr, "ERROR: device stat needs path|device as single" + " argument\n"); + return 1; + } + + path = argv[optind]; + + fdmnt = open_file_or_dir(path); + if (fdmnt < 0) { + fprintf(stderr, "ERROR: can't access '%s'\n", path); + return 12; + } + + ret = get_fs_info(fdmnt, path, &fi_args, &di_args); + if (ret) { + fprintf(stderr, "ERROR: getting dev info for devstats failed: " + "%s\n", strerror(-ret)); + err = 1; + goto out; + } + if (!fi_args.num_devices) { + fprintf(stderr, "ERROR: no devices found\n"); + err = 1; + goto out; + } + + for (i = 0; i < fi_args.num_devices; i++) { + struct btrfs_ioctl_get_dev_stats args = {0}; + __u8 path[BTRFS_DEVICE_PATH_NAME_MAX + 1]; + + strncpy((char *)path, (char *)di_args[i].path, + BTRFS_DEVICE_PATH_NAME_MAX); + path[BTRFS_DEVICE_PATH_NAME_MAX] = '\0'; + + args.devid = di_args[i].devid; + args.nr_items = BTRFS_DEV_STAT_VALUES_MAX; + args.flags = flags; + + if (ioctl(fdmnt, BTRFS_IOC_GET_DEV_STATS, &args) < 0) { + fprintf(stderr, + "ERROR: ioctl(BTRFS_IOC_GET_DEV_STATS) on %s failed: %s\n", + path, strerror(errno)); + err = 1; + } else { + if (args.nr_items >= BTRFS_DEV_STAT_WRITE_ERRS + 1) + printf("[%s].write_io_errs %llu\n", + path, + (unsigned long long) args.values[ + BTRFS_DEV_STAT_WRITE_ERRS]); + if (args.nr_items >= BTRFS_DEV_STAT_READ_ERRS + 1) + printf("[%s].read_io_errs %llu\n", + path, + (unsigned long long) args.values[ + BTRFS_DEV_STAT_READ_ERRS]); + if (args.nr_items >= BTRFS_DEV_STAT_FLUSH_ERRS + 1) + printf("[%s].flush_io_errs %llu\n", + path, + (unsigned long long) args.values[ + BTRFS_DEV_STAT_FLUSH_ERRS]); + if (args.nr_items >= BTRFS_DEV_STAT_CORRUPTION_ERRS + 1) + printf("[%s].corruption_errs %llu\n", + path, + (unsigned long long) args.values[ + BTRFS_DEV_STAT_CORRUPTION_ERRS]); + if (args.nr_items >= BTRFS_DEV_STAT_GENERATION_ERRS + 1) + printf("[%s].generation_errs %llu\n", + path, + (unsigned long long) args.values[ + BTRFS_DEV_STAT_GENERATION_ERRS]); + } + } + +out: + free(di_args); + close(fdmnt); + if (fdres > -1) + close(fdres); + + return err; +} + const struct cmd_group device_cmd_group = { device_cmd_group_usage, NULL, { { "add", cmd_add_dev, cmd_add_dev_usage, NULL, 0 }, { "delete", cmd_rm_dev, cmd_rm_dev_usage, NULL, 0 }, { "scan", cmd_scan_dev, cmd_scan_dev_usage, NULL, 0 }, { "ready", cmd_ready_dev, cmd_ready_dev_usage, NULL, 0 }, + { "stats", cmd_dev_stats, cmd_dev_stats_usage, NULL, 0 }, { 0, 0, 0, 0, 0 } } }; diff --git a/ctree.h b/ctree.h index 1f5a795e..199f0288 100644 --- a/ctree.h +++ b/ctree.h @@ -1044,6 +1044,12 @@ struct btrfs_root { #define BTRFS_QGROUP_LIMIT_KEY 244 #define BTRFS_QGROUP_RELATION_KEY 246 +/* + * Persistently stores the io stats in the device tree. + * One key for all stats, (0, BTRFS_DEV_STATS_KEY, devid). + */ +#define BTRFS_DEV_STATS_KEY 249 + /* * string items are for debugging. They just store a short string of * data in the FS diff --git a/ioctl.h b/ioctl.h index d32e22cb..6fefb571 100644 --- a/ioctl.h +++ b/ioctl.h @@ -349,6 +349,39 @@ struct btrfs_ioctl_qgroup_create_args { __u64 qgroupid; }; +enum btrfs_dev_stat_values { + /* disk I/O failure stats */ + BTRFS_DEV_STAT_WRITE_ERRS, /* EIO or EREMOTEIO from lower layers */ + BTRFS_DEV_STAT_READ_ERRS, /* EIO or EREMOTEIO from lower layers */ + BTRFS_DEV_STAT_FLUSH_ERRS, /* EIO or EREMOTEIO from lower layers */ + + /* stats for indirect indications for I/O failures */ + BTRFS_DEV_STAT_CORRUPTION_ERRS, /* checksum error, bytenr error or + * contents is illegal: this is an + * indication that the block was damaged + * during read or write, or written to + * wrong location or read from wrong + * location */ + BTRFS_DEV_STAT_GENERATION_ERRS, /* an indication that blocks have not + * been written */ + + BTRFS_DEV_STAT_VALUES_MAX +}; + +/* Reset statistics after reading; needs SYS_ADMIN capability */ +#define BTRFS_DEV_STATS_RESET (1ULL << 0) + +struct btrfs_ioctl_get_dev_stats { + __u64 devid; /* in */ + __u64 nr_items; /* in/out */ + __u64 flags; /* in/out */ + + /* out values: */ + __u64 values[BTRFS_DEV_STAT_VALUES_MAX]; + + __u64 unused[128 - 2 - BTRFS_DEV_STAT_VALUES_MAX]; /* pad to 1k */ +}; + /* BTRFS_IOC_SNAP_CREATE is no longer used by the btrfs command */ #define BTRFS_IOC_SNAP_CREATE _IOW(BTRFS_IOCTL_MAGIC, 1, \ struct btrfs_ioctl_vol_args) @@ -421,7 +454,6 @@ struct btrfs_ioctl_clone_range_args { struct btrfs_ioctl_ino_path_args) #define BTRFS_IOC_DEVICES_READY _IOR(BTRFS_IOCTL_MAGIC, 39, \ struct btrfs_ioctl_vol_args) - #define BTRFS_IOC_SET_RECEIVED_SUBVOL _IOWR(BTRFS_IOCTL_MAGIC, 37, \ struct btrfs_ioctl_received_subvol_args) #define BTRFS_IOC_SEND _IOW(BTRFS_IOCTL_MAGIC, 38, struct btrfs_ioctl_send_args) @@ -434,4 +466,7 @@ struct btrfs_ioctl_clone_range_args { struct btrfs_ioctl_qgroup_create_args) #define BTRFS_IOC_QGROUP_LIMIT _IOR(BTRFS_IOCTL_MAGIC, 43, \ struct btrfs_ioctl_qgroup_limit_args) +#define BTRFS_IOC_GET_DEV_STATS _IOWR(BTRFS_IOCTL_MAGIC, 52, \ + struct btrfs_ioctl_get_dev_stats) + #endif diff --git a/man/btrfs.8.in b/man/btrfs.8.in index d20e332e..460592eb 100644 --- a/man/btrfs.8.in +++ b/man/btrfs.8.in @@ -33,6 +33,8 @@ btrfs \- control a btrfs filesystem .PP \fBbtrfs\fP \fBdevice scan\fP\fI [--all-devices| [...]]\fP .PP +\fBbtrfs\fP \fBdevice stats\fP [-z] {\fI\fP|\fI\fP} +.PP \fBbtrfs\fP \fBdevice add\fP\fI [...] \fP .PP \fBbtrfs\fP \fBdevice delete\fP\fI [...] \fP @@ -252,6 +254,18 @@ Balance the chunks of the filesystem identified by \fI\fR across the devices. .TP +\fBdevice stats\fP [-z] {\fI\fP|\fI\fP} +Read and print the device IO stats for all devices of the filesystem +identified by \fI\fR or for a single \fI\fR. + +.RS +\fIOptions\fR +.TP +.B -z +Reset stats to zero after reading them. +.RE +.TP + \fBdevice add\fR\fI [..] \fR Add device(s) to the filesystem identified by \fI\fR. .TP diff --git a/print-tree.c b/print-tree.c index 7c615dd2..828806db 100644 --- a/print-tree.c +++ b/print-tree.c @@ -455,6 +455,9 @@ static void print_key_type(u64 objectid, u8 type) case BTRFS_QGROUP_LIMIT_KEY: printf("BTRFS_QGROUP_LIMIT_KEY"); break; + case BTRFS_DEV_STATS_KEY: + printf("DEV_STATS_ITEM"); + break; default: printf("UNKNOWN.%d", type); }; @@ -777,6 +780,9 @@ void btrfs_print_leaf(struct btrfs_root *root, struct extent_buffer *l) str = l->data + btrfs_item_ptr_offset(l, i); printf("\t\titem data %.*s\n", btrfs_item_size(l, item), str); break; + case BTRFS_DEV_STATS_KEY: + printf("\t\tdevice stats\n"); + break; }; fflush(stdout); }