Permalink
Browse files

Add -c to zpool iostat & status to run command

This patch adds a command (-c) option to zpool status and zpool iostat.  The
-c option allows you to run an arbitrary command on each vdev and display
the first line of output in zpool status/iostat.  The environment vars
VDEV_PATH and VDEV_UPATH are set to the vdev's path and "underlying path"
before running the command.  For device mapper, multipath, or partitioned
vdevs, VDEV_UPATH is the actual underlying /dev/sd* disk.  This can be useful
if the command you're running requires a /dev/sd* device.

The patch also uses /sys/block/<dev>/slaves/ to lookup the underlying device
instead of using libdevmapper.  This not only removes the libdevmapper
requirement at build time, but also allows you to resolve device mapper
devices without being root.  This means that UDEV_UPATH get set correctly
when running zpool status/iostat as an unprivileged user.

Example:

$ zpool status -c 'echo I am $VDEV_PATH, $VDEV_UPATH'

NAME        STATE     READ WRITE CKSUM
mypool      ONLINE       0     0     0
  mirror-0  ONLINE       0     0     0
    mpatha  ONLINE       0     0     0  I am /dev/mapper/mpatha, /dev/sdc
    sdb     ONLINE       0     0     0  I am /dev/sdb1, /dev/sdb

Reviewed-by: Giuseppe Di Natale <dinatale2@llnl.gov>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: Tony Hutter <hutter2@llnl.gov>
Closes #5368
  • Loading branch information...
1 parent 2f71caf commit 8720e9e7482fa2dce4f34c56d3c7451833413d7d @tonyhutter tonyhutter committed with behlendorf Nov 29, 2016
@@ -215,9 +215,7 @@ zfs_process_add(zpool_handle_t *zhp, nvlist_t *vdev, boolean_t labeled)
if (offline)
return; /* don't intervene if it was taken offline */
-#ifdef HAVE_LIBDEVMAPPER
is_dm = zfs_dev_is_dm(path);
-#endif
zed_log_msg(LOG_INFO, "zfs_process_add: pool '%s' vdev '%s', phys '%s'"
" wholedisk %d, dm %d (%llu)", zpool_get_name(zhp), path,
physpath ? physpath : "NULL", wholedisk, is_dm,
@@ -12,8 +12,6 @@
# Linux SCSI enclosure services (ses) driver. The script will do nothing
# if you have no enclosure, or if your enclosure isn't supported.
#
-# This script also requires ZFS to be built with libdevmapper support.
-#
# Exit codes:
# 0: enclosure led successfully set
# 1: enclosure leds not not available
View
@@ -33,6 +33,7 @@
#include <strings.h>
#include <libzfs.h>
+#include <sys/zfs_context.h>
#include "zpool_util.h"
@@ -316,3 +317,162 @@ for_each_vdev(zpool_handle_t *zhp, pool_vdev_iter_f func, void *data)
}
return (for_each_vdev_cb(zhp, nvroot, func, data));
}
+
+/* Thread function run for each vdev */
+static void
+vdev_run_cmd_thread(void *cb_cmd_data)
+{
+ vdev_cmd_data_t *data = cb_cmd_data;
+ char *pos = NULL;
+ FILE *fp;
+ size_t len = 0;
+ char cmd[_POSIX_ARG_MAX];
+
+ /* Set our VDEV_PATH and VDEV_UPATH env vars and run command */
+ if (snprintf(cmd, sizeof (cmd), "VDEV_PATH=%s && VDEV_UPATH=%s && %s",
+ data->path, data->upath ? data->upath : "\"\"", data->cmd) >=
+ sizeof (cmd)) {
+ /* Our string was truncated */
+ return;
+ }
+
+ fp = popen(cmd, "r");
+ if (fp == NULL)
+ return;
+
+ data->line = NULL;
+
+ /* 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);
+}
+
+/* For each vdev in the pool run a command */
+static int
+for_each_vdev_run_cb(zpool_handle_t *zhp, nvlist_t *nv, void *cb_vcdl)
+{
+ vdev_cmd_data_list_t *vcdl = cb_vcdl;
+ vdev_cmd_data_t *data;
+ char *path = NULL;
+ int i;
+
+ if (nvlist_lookup_string(nv, ZPOOL_CONFIG_PATH, &path) != 0)
+ return (1);
+
+ /* Spares show more than once if they're in use, so skip if exists */
+ for (i = 0; i < vcdl->count; i++) {
+ if ((strcmp(vcdl->data[i].path, path) == 0) &&
+ (strcmp(vcdl->data[i].pool, zpool_get_name(zhp)) == 0)) {
+ /* vdev already exists, skip it */
+ return (0);
+ }
+ }
+
+ /*
+ * Resize our array and add in the new element.
+ */
+ if (!(vcdl->data = realloc(vcdl->data,
+ sizeof (*vcdl->data) * (vcdl->count + 1))))
+ return (ENOMEM); /* couldn't realloc */
+
+ data = &vcdl->data[vcdl->count];
+
+ data->pool = strdup(zpool_get_name(zhp));
+ data->path = strdup(path);
+ data->upath = zfs_get_underlying_path(path);
+ data->cmd = vcdl->cmd;
+
+ vcdl->count++;
+
+ return (0);
+}
+
+/* Get the names and count of the vdevs */
+static int
+all_pools_for_each_vdev_gather_cb(zpool_handle_t *zhp, void *cb_vcdl)
+{
+ return (for_each_vdev(zhp, for_each_vdev_run_cb, cb_vcdl));
+}
+
+/*
+ * Now that vcdl is populated with our complete list of vdevs, spawn
+ * off the commands.
+ */
+static void
+all_pools_for_each_vdev_run_vcdl(vdev_cmd_data_list_t *vcdl)
+{
+ taskq_t *t;
+ int i;
+ /* 5 * boot_ncpus selfishly chosen since it works best on LLNL's HW */
+ int max_threads = 5 * boot_ncpus;
+
+ /*
+ * Under Linux we use a taskq to parallelize running a command
+ * on each vdev. It is therefore necessary to initialize this
+ * functionality for the duration of the threads.
+ */
+ thread_init();
+
+ t = taskq_create("z_pool_cmd", max_threads, defclsyspri, max_threads,
+ INT_MAX, 0);
+ if (t == NULL)
+ return;
+
+ /* Spawn off the command for each vdev */
+ for (i = 0; i < vcdl->count; i++) {
+ (void) taskq_dispatch(t, vdev_run_cmd_thread,
+ (void *) &vcdl->data[i], TQ_SLEEP);
+ }
+
+ /* Wait for threads to finish */
+ taskq_wait(t);
+ taskq_destroy(t);
+ thread_fini();
+}
+
+/*
+ * Run command 'cmd' on all vdevs in all pools. Saves the first line of output
+ * from the command in vcdk->data[].line for all vdevs.
+ *
+ * Returns a vdev_cmd_data_list_t that must be freed with
+ * free_vdev_cmd_data_list();
+ */
+vdev_cmd_data_list_t *
+all_pools_for_each_vdev_run(int argc, char **argv, char *cmd)
+{
+ vdev_cmd_data_list_t *vcdl;
+ vcdl = safe_malloc(sizeof (vcdl));
+ vcdl->cmd = cmd;
+
+ /* Gather our list of all vdevs in all pools */
+ for_each_pool(argc, argv, B_TRUE, NULL,
+ all_pools_for_each_vdev_gather_cb, vcdl);
+
+ /* Run command on all vdevs in all pools */
+ all_pools_for_each_vdev_run_vcdl(vcdl);
+
+ return (vcdl);
+}
+
+/*
+ * Free the vdev_cmd_data_list_t created by all_pools_for_each_vdev_run()
+ */
+void
+free_vdev_cmd_data_list(vdev_cmd_data_list_t *vcdl)
+{
+ int i;
+ 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);
+ }
+ free(vcdl->data);
+ free(vcdl);
+}
@@ -1510,8 +1510,23 @@ typedef struct status_cbdata {
boolean_t cb_first;
boolean_t cb_dedup_stats;
boolean_t cb_print_status;
+ vdev_cmd_data_list_t *vcdl;
} status_cbdata_t;
+/* Print output line 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;
+ 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;
+ }
+ }
+}
+
/*
* Print out configuration state as requested by status_callback.
*/
@@ -1528,6 +1543,7 @@ print_status_config(zpool_handle_t *zhp, status_cbdata_t *cb, const char *name,
uint64_t notpresent;
spare_cbdata_t spare_cb;
char *state;
+ char *path = NULL;
if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_CHILDREN,
&child, &children) != 0)
@@ -1560,7 +1576,6 @@ print_status_config(zpool_handle_t *zhp, status_cbdata_t *cb, const char *name,
if (nvlist_lookup_uint64(nv, ZPOOL_CONFIG_NOT_PRESENT,
&notpresent) == 0) {
- char *path;
verify(nvlist_lookup_string(nv, ZPOOL_CONFIG_PATH, &path) == 0);
(void) printf(" was %s", path);
} else if (vs->vs_aux != 0) {
@@ -1641,6 +1656,13 @@ print_status_config(zpool_handle_t *zhp, status_cbdata_t *cb, const char *name,
"resilvering" : "repairing");
}
+ if (cb->vcdl != NULL) {
+ if (nvlist_lookup_string(nv, ZPOOL_CONFIG_PATH, &path) == 0) {
+ printf(" ");
+ zpool_print_cmd(cb->vcdl, zpool_get_name(zhp), path);
+ }
+ }
+
(void) printf("\n");
for (c = 0; c < children; c++) {
@@ -2586,6 +2608,7 @@ typedef struct iostat_cbdata {
boolean_t cb_literal;
boolean_t cb_scripted;
zpool_list_t *cb_list;
+ vdev_cmd_data_list_t *vcdl;
} iostat_cbdata_t;
/* iostat labels */
@@ -3393,6 +3416,18 @@ print_vdev_stats(zpool_handle_t *zhp, const char *name, nvlist_t *oldnv,
print_iostat_histos(cb, oldnv, newnv, scale, name);
}
+ if (cb->vcdl != NULL) {
+ char *path;
+ if (nvlist_lookup_string(newnv, ZPOOL_CONFIG_PATH,
+ &path) == 0) {
+ if (!(cb->cb_flags & IOS_ANYHISTO_M))
+ printf(" ");
+ zpool_print_cmd(cb->vcdl, zpool_get_name(zhp), path);
+ if (cb->cb_flags & IOS_ANYHISTO_M)
+ printf("\n");
+ }
+ }
+
if (!(cb->cb_flags & IOS_ANYHISTO_M))
printf("\n");
@@ -3924,10 +3959,11 @@ fsleep(float sec) {
/*
- * zpool iostat [-ghHLpPvy] [[-lq]|[-r|-w]] [-n name] [-T d|u]
+ * zpool iostat [-c CMD] [-ghHLpPvy] [[-lq]|[-r|-w]] [-n name] [-T d|u]
* [[ pool ...]|[pool vdev ...]|[vdev ...]]
* [interval [count]]
*
+ * -c CMD For each vdev, run command CMD
* -g Display guid for individual vdev name.
* -L Follow links when resolving vdev path name.
* -P Display full path for vdev name.
@@ -3965,6 +4001,7 @@ zpool_do_iostat(int argc, char **argv)
boolean_t follow_links = B_FALSE;
boolean_t full_name = B_FALSE;
iostat_cbdata_t cb = { 0 };
+ char *cmd = NULL;
/* Used for printing error message */
const char flag_to_arg[] = {[IOS_LATENCY] = 'l', [IOS_QUEUES] = 'q',
@@ -3973,8 +4010,11 @@ zpool_do_iostat(int argc, char **argv)
uint64_t unsupported_flags;
/* check options */
- while ((c = getopt(argc, argv, "gLPT:vyhplqrwH")) != -1) {
+ while ((c = getopt(argc, argv, "c:gLPT:vyhplqrwH")) != -1) {
switch (c) {
+ case 'c':
+ cmd = optarg;
+ break;
case 'g':
guid = B_TRUE;
break;
@@ -4167,7 +4207,6 @@ zpool_do_iostat(int argc, char **argv)
return (1);
}
-
for (;;) {
if ((npools = pool_list_count(list)) == 0)
(void) fprintf(stderr, gettext("no pools available\n"));
@@ -4217,8 +4256,15 @@ zpool_do_iostat(int argc, char **argv)
continue;
}
+ if (cmd != NULL)
+ cb.vcdl = all_pools_for_each_vdev_run(argc,
+ argv, cmd);
+
pool_list_iter(list, B_FALSE, print_iostat, &cb);
+ if (cb.vcdl != NULL)
+ free_vdev_cmd_data_list(cb.vcdl);
+
/*
* If there's more than one pool, and we're not in
* verbose mode (which prints a separator for us),
@@ -6016,8 +6062,9 @@ status_callback(zpool_handle_t *zhp, void *data)
}
/*
- * zpool status [-gLPvx] [-T d|u] [pool] ... [interval [count]]
+ * zpool status [-c CMD] [-gLPvx] [-T d|u] [pool] ... [interval [count]]
*
+ * -c CMD For each vdev, run command CMD
* -g Display guid for individual vdev name.
* -L Follow links when resolving vdev path name.
* -P Display full path for vdev name.
@@ -6036,10 +6083,14 @@ zpool_do_status(int argc, char **argv)
float interval = 0;
unsigned long count = 0;
status_cbdata_t cb = { 0 };
+ char *cmd = NULL;
/* check options */
- while ((c = getopt(argc, argv, "gLPvxDT:")) != -1) {
+ while ((c = getopt(argc, argv, "c:gLPvxDT:")) != -1) {
switch (c) {
+ case 'c':
+ cmd = optarg;
+ break;
case 'g':
cb.cb_name_flags |= VDEV_NAME_GUID;
break;
@@ -6083,9 +6134,15 @@ zpool_do_status(int argc, char **argv)
if (timestamp_fmt != NODATE)
print_timestamp(timestamp_fmt);
+ if (cmd != NULL)
+ cb.vcdl = all_pools_for_each_vdev_run(argc, argv, cmd);
+
ret = for_each_pool(argc, argv, B_TRUE, NULL,
status_callback, &cb);
+ if (cb.vcdl != NULL)
+ free_vdev_cmd_data_list(cb.vcdl);
+
if (argc == 0 && cb.cb_count == 0)
(void) fprintf(stderr, gettext("no pools available\n"));
else if (cb.cb_explain && cb.cb_first && cb.cb_allpools)
@@ -72,6 +72,27 @@ void pool_list_remove(zpool_list_t *, zpool_handle_t *);
libzfs_handle_t *g_zfs;
+
+typedef struct vdev_cmd_data
+{
+ char *line; /* cmd output */
+ char *path; /* vdev path */
+ char *upath; /* vdev underlying path */
+ char *pool; /* Pool name */
+ char *cmd; /* backpointer to cmd */
+} vdev_cmd_data_t;
+
+typedef struct vdev_cmd_data_list
+{
+ char *cmd; /* Command to run */
+ unsigned int count; /* Number of vdev_cmd_data items (vdevs) */
+ vdev_cmd_data_t *data; /* Array of vdevs */
+} vdev_cmd_data_list_t;
+
+vdev_cmd_data_list_t * all_pools_for_each_vdev_run(int argc, char **argv,
+ char *cmd);
+void free_vdev_cmd_data_list(vdev_cmd_data_list_t *vcdl);
+
#ifdef __cplusplus
}
#endif
Oops, something went wrong.

0 comments on commit 8720e9e

Please sign in to comment.