diff --git a/cmd/zpool/zpool_iter.c b/cmd/zpool/zpool_iter.c index 7ce0ccf9efbc..069fa14fb74a 100644 --- a/cmd/zpool/zpool_iter.c +++ b/cmd/zpool/zpool_iter.c @@ -321,6 +321,83 @@ for_each_vdev(zpool_handle_t *zhp, pool_vdev_iter_f func, void *data) return (for_each_vdev_cb(zhp, nvroot, func, data)); } +/* + * Process a line of command output + * + * When running 'zpool iostat|status -c' the lines of output can either be + * in the form of: + * + * column_name=value + * + * Or just: + * + * value + * + * Process the column_name (if any) and value. + * + * Returns 1 if line was processed, and there are more lines can still be + * processed. + * + * Returns 0 if this was the last line to process, or error. + */ +static int +vdev_process_cmd_output(vdev_cmd_data_t *data, char *line) +{ + char *col = NULL; + char *val = line; + char *equals; + + if (!line) + return (0); + + equals = strchr(line, '='); + + if (equals) { + /* + * We have a 'column=value' type line. Split it into the + * column and value strings by turning the '=' into a '\0'. + */ + *equals = '\0'; + col = line; + val = equals + 1; + } else { + val = line; + } + + if (val != NULL) { + data->lines = realloc(data->lines, + (data->lines_cnt + 1) * sizeof (*data->lines)); + + if (data->lines == NULL) + return (0); + + + data->lines[data->lines_cnt] = strdup(val); + data->lines_cnt++; + } + + if (col != NULL) { + data->cols = realloc(data->cols, + (data->cols_cnt + 1) * sizeof (*data->cols)); + + data->cols_width = realloc(data->cols_width, + (data->cols_cnt + 1) * sizeof (*data->cols_width)); + + if (data->cols == NULL || data->cols_width == NULL) + return (0); + + data->cols[data->cols_cnt] = strdup(col); + data->cols_width[data->cols_cnt] = + MAX(strlen(col), strlen(val)); + data->cols_cnt++; + } + + if (val != NULL && col == NULL) + return (0); + + return (1); +} + /* Thread function run for each vdev */ static void vdev_run_cmd_thread(void *cb_cmd_data) @@ -330,6 +407,7 @@ vdev_run_cmd_thread(void *cb_cmd_data) FILE *fp; size_t len = 0; char cmd[_POSIX_ARG_MAX]; + char *line; /* Set our VDEV_PATH and VDEV_UPATH env vars and run command */ if (snprintf(cmd, sizeof (cmd), "VDEV_PATH=%s && VDEV_UPATH=\"%s\" && " @@ -345,16 +423,24 @@ vdev_run_cmd_thread(void *cb_cmd_data) if (fp == NULL) return; - data->line = NULL; + data->lines = data->cols = NULL; + data->cols_width = NULL; + data->lines_cnt = data->cols_cnt = 0; + + line = NULL; + do { + /* Save the first line of output from the command */ + if (getline(&line, &len, fp) != -1) { + /* Success. Remove \n from the end, if necessary. */ + if ((pos = strchr(line, '\n')) != NULL) + *pos = '\0'; + } else { + /* We read all the lines, or errored out */ + break; + } + } while (vdev_process_cmd_output(data, line)); + free(line); - /* Save the first line of output from the command */ - if (getline(&data->line, &len, fp) != -1) { - /* Success. Remove newline from the end, if necessary. */ - if ((pos = strchr(data->line, '\n')) != NULL) - *pos = '\0'; - } else { - data->line = NULL; - } pclose(fp); } @@ -504,12 +590,22 @@ all_pools_for_each_vdev_run(int argc, char **argv, char *cmd, void free_vdev_cmd_data_list(vdev_cmd_data_list_t *vcdl) { - int i; + int i, j; for (i = 0; i < vcdl->count; i++) { free(vcdl->data[i].path); free(vcdl->data[i].pool); free(vcdl->data[i].upath); - free(vcdl->data[i].line); + + for (j = 0; j < vcdl->data[i].lines_cnt; j++) + free(vcdl->data[i].lines[j]); + + free(vcdl->data[i].lines); + + for (j = 0; j < vcdl->data[i].cols_cnt; j++) + free(vcdl->data[i].cols[j]); + + free(vcdl->data[i].cols); + free(vcdl->data[i].cols_width); free(vcdl->data[i].vdev_enc_sysfs_path); } free(vcdl->data); diff --git a/cmd/zpool/zpool_main.c b/cmd/zpool/zpool_main.c index da6744b76b86..c74a6936d587 100644 --- a/cmd/zpool/zpool_main.c +++ b/cmd/zpool/zpool_main.c @@ -314,10 +314,9 @@ get_usage(zpool_help_t idx) "[-R root] [-F [-n]]\n" "\t [newpool]\n")); case HELP_IOSTAT: - return (gettext("\tiostat [-c CMD] [-T d | u] [-ghHLpPvy] " - "[[-lq]|[-r|-w]]\n" - "\t [[pool ...]|[pool vdev ...]|[vdev ...]] " - "[interval [count]]\n")); + return (gettext("\tiostat [[[-c CMD] [-lq]]|[-rw]] [-T d | u]" + "[-ghHLpPvy]\n\t [[pool ...]|[pool vdev ...]|[vdev ...]]" + " [interval [count]]\n")); case HELP_LABELCLEAR: return (gettext("\tlabelclear [-f] \n")); case HELP_LIST: @@ -1515,17 +1514,31 @@ typedef struct status_cbdata { vdev_cmd_data_list_t *vcdl; } status_cbdata_t; -/* Print output line for specific vdev in a specific pool */ +/* Print command output lines for specific vdev in a specific pool */ static void zpool_print_cmd(vdev_cmd_data_list_t *vcdl, const char *pool, char *path) { - int i; + vdev_cmd_data_t *data; + int i, j; + for (i = 0; i < vcdl->count; i++) { - if ((strcmp(vcdl->data[i].path, path) == 0) && - (strcmp(vcdl->data[i].pool, pool) == 0)) { - printf("%s", vcdl->data[i].line); - break; + if ((strcmp(vcdl->data[i].path, path) != 0) || + (strcmp(vcdl->data[i].pool, pool) != 0)) { + continue; } + + data = &vcdl->data[i]; + for (j = 0; j < data->lines_cnt; j++) { + if (j < data->cols_cnt) { + printf("%*s", data->cols_width[j], + data->lines[j]); + } else { + printf("%s", data->lines[j]); + } + if (j != data->lines_cnt - 1) + printf(" "); + } + break; } } @@ -2772,9 +2785,52 @@ print_iostat_labels(iostat_cbdata_t *cb, unsigned int force_column_width, } } - printf("\n"); } + +/* + * print_cmd_columns - Print custom column titles from -c + * + * If the user specified the "zpool status|iostat -c" then print their custom + * column titles in the header. For example, print_cmd_columns() would print + * the " col1 col2" part of this: + * + * $ zpool iostat -vc 'echo col1=val1; echo col2=val2' + * ... + * capacity operations bandwidth + * pool alloc free read write read write col1 col2 + * ---------- ----- ----- ----- ----- ----- ----- ---- ---- + * mypool 269K 1008M 0 0 107 946 + * mirror 269K 1008M 0 0 107 946 + * sdb - - 0 0 102 473 val1 val2 + * sdc - - 0 0 5 473 val1 val2 + * ---------- ----- ----- ----- ----- ----- ----- ---- ---- + */ +void +print_cmd_columns(vdev_cmd_data_list_t *vcdl, int use_dashes) +{ + int i, j; + vdev_cmd_data_t *data = &vcdl->data[0]; + + if (vcdl->count == 0 || data == NULL) + return; + /* + * Each vdev cmd should have the same column names unless the user did + * something weird with their cmd. Just take the column names from the + * first vdev and assume it works for all of them. + */ + for (i = 0; i < data->cols_cnt; i++) { + printf(" "); + if (use_dashes) { + for (j = 0; j < data->cols_width[i]; j++) + printf("-"); + } else { + printf("%*s", data->cols_width[i], data->cols[i]); + } + } +} + + /* * Utility function to print out a line of dashes like: * @@ -2843,7 +2899,6 @@ print_iostat_dashes(iostat_cbdata_t *cb, unsigned int force_column_width, "--------------------"); } } - printf("\n"); } @@ -2885,12 +2940,22 @@ print_iostat_header_impl(iostat_cbdata_t *cb, unsigned int force_column_width, print_iostat_labels(cb, force_column_width, iostat_top_labels); + printf("\n"); printf("%-*s", namewidth, title); print_iostat_labels(cb, force_column_width, iostat_bottom_labels); + if (cb->vcdl != NULL) + print_cmd_columns(cb->vcdl, 0); + + printf("\n"); print_iostat_separator_impl(cb, force_column_width); + + if (cb->vcdl != NULL) + print_cmd_columns(cb->vcdl, 1); + + printf("\n"); } static void @@ -3424,11 +3489,8 @@ print_vdev_stats(zpool_handle_t *zhp, const char *name, nvlist_t *oldnv, char *path; if (nvlist_lookup_string(newnv, ZPOOL_CONFIG_PATH, &path) == 0) { - if (!(cb->cb_flags & IOS_ANYHISTO_M)) - printf(" "); + printf(" "); zpool_print_cmd(cb->vcdl, zpool_get_name(zhp), path); - if (cb->cb_flags & IOS_ANYHISTO_M) - printf("\n"); } } @@ -3575,6 +3637,10 @@ print_iostat(zpool_handle_t *zhp, void *data) if ((ret != 0) && !(cb->cb_flags & IOS_ANYHISTO_M) && !cb->cb_scripted && cb->cb_verbose && !cb->cb_vdev_names_count) { print_iostat_separator(cb); + if (cb->vcdl != NULL) { + print_cmd_columns(cb->vcdl, 1); + } + printf("\n"); } return (ret); @@ -3965,7 +4031,7 @@ fsleep(float sec) /* - * zpool iostat [-c CMD] [-ghHLpPvy] [[-lq]|[-r|-w]] [-n name] [-T d|u] + * zpool iostat [[-c CMD] [-lq]|[-rw]] [-ghHLpPvy] [-n name] [-T d|u] * [[ pool ...]|[pool vdev ...]|[vdev ...]] * [interval [count]] * @@ -4158,10 +4224,10 @@ zpool_do_iostat(int argc, char **argv) return (1); } - if ((l_histo || rq_histo) && (queues || latency)) { + if ((l_histo || rq_histo) && (cmd != NULL || latency || queues)) { pool_list_free(list); (void) fprintf(stderr, - gettext("[-r|-w] isn't allowed with [-q|-l]\n")); + gettext("[-r|-w] isn't allowed with [-c|-l|-q]\n")); usage(B_FALSE); return (1); } @@ -4249,6 +4315,13 @@ zpool_do_iostat(int argc, char **argv) if (timestamp_fmt != NODATE) print_timestamp(timestamp_fmt); + if (cmd != NULL && cb.cb_verbose && + !(cb.cb_flags & IOS_ANYHISTO_M)) { + cb.vcdl = all_pools_for_each_vdev_run(argc, + argv, cmd, g_zfs, cb.cb_vdev_names, + cb.cb_vdev_names_count, cb.cb_name_flags); + } + /* * If it's the first time and we're not skipping it, * or either skip or verbose mode, print the header. @@ -4267,10 +4340,6 @@ zpool_do_iostat(int argc, char **argv) continue; } - if (cmd != NULL && cb.cb_verbose) - cb.vcdl = all_pools_for_each_vdev_run(argc, - argv, cmd, g_zfs, cb.cb_vdev_names, - cb.cb_vdev_names_count, cb.cb_name_flags); pool_list_iter(list, B_FALSE, print_iostat, &cb); @@ -6012,9 +6081,14 @@ status_callback(zpool_handle_t *zhp, void *data) cbp->cb_namewidth = 10; (void) printf(gettext("config:\n\n")); - (void) printf(gettext("\t%-*s %-8s %5s %5s %5s\n"), + (void) printf(gettext("\t%-*s %-8s %5s %5s %5s"), cbp->cb_namewidth, "NAME", "STATE", "READ", "WRITE", "CKSUM"); + + if (cbp->vcdl != NULL) + print_cmd_columns(cbp->vcdl, 0); + + printf("\n"); print_status_config(zhp, cbp, zpool_get_name(zhp), nvroot, 0, B_FALSE); diff --git a/cmd/zpool/zpool_util.h b/cmd/zpool/zpool_util.h index 61ed33c154a2..e912d49f0f36 100644 --- a/cmd/zpool/zpool_util.h +++ b/cmd/zpool/zpool_util.h @@ -75,7 +75,14 @@ libzfs_handle_t *g_zfs; typedef struct vdev_cmd_data { - char *line; /* cmd output */ + char **lines; /* Array of lines of output, minus the column name */ + int lines_cnt; /* Number of lines in the array */ + + char **cols; /* Array of column names */ + int cols_cnt; /* Number of column names */ + + int *cols_width; /* Array of column widths */ + char *path; /* vdev path */ char *upath; /* vdev underlying path */ char *pool; /* Pool name */ diff --git a/man/man8/zpool.8 b/man/man8/zpool.8 index 35a195aa0077..b71ea9747e5c 100644 --- a/man/man8/zpool.8 +++ b/man/man8/zpool.8 @@ -96,7 +96,7 @@ zpool \- configures ZFS storage pools .LP .nf -\fB\fBzpool iostat\fR [\fB-c\fR \fBCMD\fR] [\fB-T\fR \fBd\fR | \fBu\fR] [\fB-ghHLpPvy\fR] [\fB-lq\fR]|[\fB-r\fR|-\fBw\fR]] +\fB\fBzpool iostat\fR [[[\fB-c\fR \fBCMD\fR] [\fB-lq\fR]] | \fB-rw\fR] [\fB-T\fR \fBd\fR | \fBu\fR] [\fB-ghHLpPvy\fR] [[\fIpool\fR ...]|[\fIpool vdev\fR ...]|[\fIvdev\fR ...]] [\fIinterval\fR[\fIcount\fR]]\fR .fi @@ -1523,7 +1523,7 @@ Scan using the default search path, the libblkid cache will not be consulted. A .sp .ne 2 .na -\fB\fBzpool iostat\fR [\fB-c\fR \fBCMD\fR] [\fB-T\fR \fBd\fR | \fBu\fR] [\fB-ghHLpPvy\fR] [[\fB-lq\fR]|[\fB-r\fR|\fB-w\fR]] [[\fIpool\fR ...]|[\fIpool vdev\fR ...]|[\fIvdev\fR ...]] [\fIinterval\fR[\fIcount\fR]]\fR +\fB\fBzpool iostat\fR [[[\fB-c\fR \fBCMD\fR] [\fB-lq\fR]] | \fB-rw\fR] [\fB-T\fR \fBd\fR | \fBu\fR] [\fB-ghHLpPvy\fR] [[\fIpool\fR ...]|[\fIpool vdev\fR ...]|[\fIvdev\fR ...]] [\fIinterval\fR[\fIcount\fR]]\fR .ad .sp .6 @@ -1545,11 +1545,16 @@ base 1024. To get the raw values, use the \fB-p\fR flag. \fB\fB-c\fR \fBCMD\fR .ad .RS 12n -Run a command on each vdev and include first line of output +Run a command on each vdev and include the output in zpool iostat .sp The \fB-c\fR option allows you to run an arbitrary command on each vdev and -display the first line of output in zpool iostat. The following environment -vars are set before running each command: +display the output in zpool iostat. If the output is in the form of +"name=value", then the column name is set to "name" and the value is set to +"value". Multiple lines can be use to output multiple columns. If the +output is not in this format, then the first line of output is displayed +without a column title. + +The following environment vars are set before running each command: .sp \fB$VDEV_PATH\fR: Full path to the vdev. .LP @@ -2117,11 +2122,16 @@ If a scrub or resilver is in progress, this command reports the percentage done \fB\fB-c\fR \fBCMD\fR .ad .RS 12n -Run a command on each vdev and include first line of output +Run a command on each vdev and include the output in zpool status .sp The \fB-c\fR option allows you to run an arbitrary command on each vdev and -display the first line of output in zpool iostat. The following environment -vars are set before running each command: +display the output in zpool iostat. If the output is in the form of +"name=value", then the column name is set to "name" and the value is set to +"value". Multiple lines can be use to output multiple columns. If the +output is not in this format, then the first line of output is displayed +without a column title. + +The following environment vars are set before running each command: .sp \fB$VDEV_PATH\fR: Full path to the vdev. .LP @@ -2581,6 +2591,18 @@ mypool ONLINE 0 0 0 .fi .in -2 +.sp +.in +2 +.nf +# \fBzpool status -c \[aq]echo path=$VDEV_PATH; echo size=$(lsblk -n --nodeps -o size $VDEV_PATH)\[aq]\fR +NAME STATE READ WRITE CKSUM path size +mypool ONLINE 0 0 0 + mirror-0 ONLINE 0 0 0 + sdb ONLINE 0 0 0 /dev/sdb 1G + sdc ONLINE 0 0 0 /dev/sdc 1G +.fi +.in -2 + .sp .in +2 .nf diff --git a/tests/zfs-tests/tests/functional/cli_user/misc/zpool_status_001_neg.ksh b/tests/zfs-tests/tests/functional/cli_user/misc/zpool_status_001_neg.ksh index 4c6f11c0a859..c75e52b2c98d 100755 --- a/tests/zfs-tests/tests/functional/cli_user/misc/zpool_status_001_neg.ksh +++ b/tests/zfs-tests/tests/functional/cli_user/misc/zpool_status_001_neg.ksh @@ -81,9 +81,14 @@ C2=$($ZPOOL status -P -c 'echo vdev_test{$VDEV_PATH}{$VDEV_UPATH}' | \ if [ "$C1" != "$C2" ] ; then log_fail "zpool status -c option failed. Expected $C1 vdevs, got $C2" else - log_pass "zpool status -c option passed. Expected $C1 vdevs, got $C2" + log_note "zpool status -c option passed. Expected $C1 vdevs, got $C2" fi +# Make sure the -c option prints custom column titles. The column line should +# have both the "col1" and "col2" headings. +log_must eval "$ZPOOL status -c 'echo col1=value1; echo col2=value2' | \ + $GREP -qE 'col1\s+col2'" + # $TESTPOOL.virt has an offline device, so -x will show it log_must eval "$ZPOOL status -x $TESTPOOL.virt > /tmp/pool-status.$$" check_pool_status diff --git a/tests/zfs-tests/tests/functional/cli_user/zpool_iostat/zpool_iostat_005_pos.ksh b/tests/zfs-tests/tests/functional/cli_user/zpool_iostat/zpool_iostat_005_pos.ksh index 89a9a4975320..15e6753c2de1 100755 --- a/tests/zfs-tests/tests/functional/cli_user/zpool_iostat/zpool_iostat_005_pos.ksh +++ b/tests/zfs-tests/tests/functional/cli_user/zpool_iostat/zpool_iostat_005_pos.ksh @@ -78,3 +78,8 @@ if [ "$C2" != "1" ] ; then else log_note "zpool iostat -c passed, expected 1 vdev, got $C2" fi + +# Make sure the -c option prints custom column titles. The column line should +# have both the "col1" and "col2" headings. +log_must $ZPOOL iostat -vc 'echo col1=value1; echo col2=value2' | \ + $GREP -qE 'col1\s+col2'