xref: /freebsd/sys/contrib/openzfs/cmd/zpool_influxdb/zpool_influxdb.c (revision 22649d4dba730d46244fd2dff4fd174903c8379f)
1 // SPDX-License-Identifier: CDDL-1.0
2 /*
3  * Gather top-level ZFS pool and resilver/scan statistics and print using
4  * influxdb line protocol
5  * usage: [options] [pool_name]
6  * where options are:
7  *   --execd, -e           run in telegraf execd input plugin mode, [CR] on
8  *                         stdin causes a sample to be printed and wait for
9  *                         the next [CR]
10  *   --no-histograms, -n   don't print histogram data (reduces cardinality
11  *                         if you don't care about histograms)
12  *   --sum-histogram-buckets, -s sum histogram bucket values
13  *
14  * To integrate into telegraf use one of:
15  * 1. the `inputs.execd` plugin with the `--execd` option
16  * 2. the `inputs.exec` plugin to simply run with no options
17  *
18  * NOTE: libzfs is an unstable interface. YMMV.
19  *
20  * The design goals of this software include:
21  * + be as lightweight as possible
22  * + reduce the number of external dependencies as far as possible, hence
23  *   there is no dependency on a client library for managing the metric
24  *   collection -- info is printed, KISS
25  * + broken pools or kernel bugs can cause this process to hang in an
26  *   unkillable state. For this reason, it is best to keep the damage limited
27  *   to a small process like zpool_influxdb rather than a larger collector.
28  *
29  * Copyright 2018-2020 Richard Elling
30  *
31  * This software is dual-licensed MIT and CDDL.
32  *
33  * The MIT License (MIT)
34  *
35  * Permission is hereby granted, free of charge, to any person obtaining a copy
36  * of this software and associated documentation files (the "Software"), to deal
37  * in the Software without restriction, including without limitation the rights
38  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
39  * copies of the Software, and to permit persons to whom the Software is
40  * furnished to do so, subject to the following conditions:
41  *
42  * The above copyright notice and this permission notice shall be included in
43  * all copies or substantial portions of the Software.
44  *
45  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
46  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
47  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
48  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
49  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
50  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
51  * SOFTWARE.
52  *
53  * This file and its contents are supplied under the terms of the
54  * Common Development and Distribution License ("CDDL"), version 1.0.
55  * You may only use this file in accordance with the terms of version
56  * 1.0 of the CDDL.
57  *
58  * A full copy of the text of the CDDL should have accompanied this
59  * source.  A copy of the CDDL is also available via the Internet at
60  * https://opensource.org/license/CDDL-1.0.
61  */
62 #include <string.h>
63 #include <getopt.h>
64 #include <stdio.h>
65 #include <stdint.h>
66 #include <inttypes.h>
67 #include <libzfs.h>
68 
69 #define	POOL_MEASUREMENT	"zpool_stats"
70 #define	SCAN_MEASUREMENT	"zpool_scan_stats"
71 #define	VDEV_MEASUREMENT	"zpool_vdev_stats"
72 #define	POOL_LATENCY_MEASUREMENT	"zpool_latency"
73 #define	POOL_QUEUE_MEASUREMENT	"zpool_vdev_queue"
74 #define	MIN_LAT_INDEX	10  /* minimum latency index 10 = 1024ns */
75 #define	POOL_IO_SIZE_MEASUREMENT	"zpool_io_size"
76 #define	MIN_SIZE_INDEX	9  /* minimum size index 9 = 512 bytes */
77 
78 /* global options */
79 int execd_mode = 0;
80 int no_histograms = 0;
81 int sum_histogram_buckets = 0;
82 char metric_data_type = 'u';
83 uint64_t metric_value_mask = UINT64_MAX;
84 uint64_t timestamp = 0;
85 int complained_about_sync = 0;
86 const char *tags = "";
87 
88 typedef int (*stat_printer_f)(nvlist_t *, const char *, const char *);
89 
90 /*
91  * influxdb line protocol rules for escaping are important because the
92  * zpool name can include characters that need to be escaped
93  *
94  * caller is responsible for freeing result
95  */
96 static char *
escape_string(const char * s)97 escape_string(const char *s)
98 {
99 	const char *c;
100 	char *d;
101 	char *t = (char *)malloc(ZFS_MAX_DATASET_NAME_LEN * 2);
102 	if (t == NULL) {
103 		fprintf(stderr, "error: cannot allocate memory\n");
104 		exit(1);
105 	}
106 
107 	for (c = s, d = t; *c != '\0'; c++, d++) {
108 		switch (*c) {
109 		case ' ':
110 		case ',':
111 		case '=':
112 		case '\\':
113 			*d++ = '\\';
114 			zfs_fallthrough;
115 		default:
116 			*d = *c;
117 		}
118 	}
119 	*d = '\0';
120 	return (t);
121 }
122 
123 /*
124  * print key=value where value is a uint64_t
125  */
126 static void
print_kv(const char * key,uint64_t value)127 print_kv(const char *key, uint64_t value)
128 {
129 	printf("%s=%llu%c", key,
130 	    (u_longlong_t)value & metric_value_mask, metric_data_type);
131 }
132 
133 /*
134  * print_scan_status() prints the details as often seen in the "zpool status"
135  * output. However, unlike the zpool command, which is intended for humans,
136  * this output is suitable for long-term tracking in influxdb.
137  * TODO: update to include issued scan data
138  */
139 static int
print_scan_status(nvlist_t * nvroot,const char * pool_name)140 print_scan_status(nvlist_t *nvroot, const char *pool_name)
141 {
142 	uint_t c;
143 	int64_t elapsed;
144 	uint64_t examined, pass_exam, paused_time, paused_ts, rate;
145 	uint64_t remaining_time;
146 	pool_scan_stat_t *ps = NULL;
147 	double pct_done;
148 	const char *const state[DSS_NUM_STATES] = {
149 	    "none", "scanning", "finished", "canceled"};
150 	const char *func;
151 
152 	(void) nvlist_lookup_uint64_array(nvroot,
153 	    ZPOOL_CONFIG_SCAN_STATS,
154 	    (uint64_t **)&ps, &c);
155 
156 	/*
157 	 * ignore if there are no stats
158 	 */
159 	if (ps == NULL)
160 		return (0);
161 
162 	/*
163 	 * return error if state is bogus
164 	 */
165 	if (ps->pss_state >= DSS_NUM_STATES ||
166 	    ps->pss_func >= POOL_SCAN_FUNCS) {
167 		if (complained_about_sync % 1000 == 0) {
168 			fprintf(stderr, "error: cannot decode scan stats: "
169 			    "ZFS is out of sync with compiled zpool_influxdb");
170 			complained_about_sync++;
171 		}
172 		return (1);
173 	}
174 
175 	switch (ps->pss_func) {
176 	case POOL_SCAN_NONE:
177 		func = "none_requested";
178 		break;
179 	case POOL_SCAN_SCRUB:
180 		func = "scrub";
181 		break;
182 	case POOL_SCAN_RESILVER:
183 		func = "resilver";
184 		break;
185 #ifdef POOL_SCAN_REBUILD
186 	case POOL_SCAN_REBUILD:
187 		func = "rebuild";
188 		break;
189 #endif
190 	default:
191 		func = "scan";
192 	}
193 
194 	/* overall progress */
195 	examined = ps->pss_examined ? ps->pss_examined : 1;
196 	pct_done = 0.0;
197 	if (ps->pss_to_examine > 0)
198 		pct_done = 100.0 * examined / ps->pss_to_examine;
199 
200 #ifdef EZFS_SCRUB_PAUSED
201 	paused_ts = ps->pss_pass_scrub_pause;
202 	paused_time = ps->pss_pass_scrub_spent_paused;
203 #else
204 	paused_ts = 0;
205 	paused_time = 0;
206 #endif
207 
208 	/* calculations for this pass */
209 	if (ps->pss_state == DSS_SCANNING) {
210 		elapsed = (int64_t)time(NULL) - (int64_t)ps->pss_pass_start -
211 		    (int64_t)paused_time;
212 		elapsed = (elapsed > 0) ? elapsed : 1;
213 		pass_exam = ps->pss_pass_exam ? ps->pss_pass_exam : 1;
214 		rate = pass_exam / elapsed;
215 		rate = (rate > 0) ? rate : 1;
216 		remaining_time = ps->pss_to_examine - examined / rate;
217 	} else {
218 		elapsed =
219 		    (int64_t)ps->pss_end_time - (int64_t)ps->pss_pass_start -
220 		    (int64_t)paused_time;
221 		elapsed = (elapsed > 0) ? elapsed : 1;
222 		pass_exam = ps->pss_pass_exam ? ps->pss_pass_exam : 1;
223 		rate = pass_exam / elapsed;
224 		remaining_time = 0;
225 	}
226 	rate = rate ? rate : 1;
227 
228 	/* influxdb line protocol format: "tags metrics timestamp" */
229 	printf("%s%s,function=%s,name=%s,state=%s ",
230 	    SCAN_MEASUREMENT, tags, func, pool_name, state[ps->pss_state]);
231 	print_kv("end_ts", ps->pss_end_time);
232 	print_kv(",errors", ps->pss_errors);
233 	print_kv(",examined", examined);
234 	print_kv(",skipped", ps->pss_skipped);
235 	print_kv(",issued", ps->pss_issued);
236 	print_kv(",pass_examined", pass_exam);
237 	print_kv(",pass_issued", ps->pss_pass_issued);
238 	print_kv(",paused_ts", paused_ts);
239 	print_kv(",paused_t", paused_time);
240 	printf(",pct_done=%.2f", pct_done);
241 	print_kv(",processed", ps->pss_processed);
242 	print_kv(",rate", rate);
243 	print_kv(",remaining_t", remaining_time);
244 	print_kv(",start_ts", ps->pss_start_time);
245 	print_kv(",to_examine", ps->pss_to_examine);
246 	printf(" %llu\n", (u_longlong_t)timestamp);
247 	return (0);
248 }
249 
250 /*
251  * get a vdev name that corresponds to the top-level vdev names
252  * printed by `zpool status`
253  */
254 static char *
get_vdev_name(nvlist_t * nvroot,const char * parent_name)255 get_vdev_name(nvlist_t *nvroot, const char *parent_name)
256 {
257 	static char vdev_name[256];
258 	uint64_t vdev_id = 0;
259 
260 	const char *vdev_type = "unknown";
261 	(void) nvlist_lookup_string(nvroot, ZPOOL_CONFIG_TYPE, &vdev_type);
262 
263 	if (nvlist_lookup_uint64(
264 	    nvroot, ZPOOL_CONFIG_ID, &vdev_id) != 0)
265 		vdev_id = UINT64_MAX;
266 
267 	if (parent_name == NULL) {
268 		(void) snprintf(vdev_name, sizeof (vdev_name), "%s",
269 		    vdev_type);
270 	} else {
271 		(void) snprintf(vdev_name, sizeof (vdev_name),
272 		    "%.220s/%s-%llu",
273 		    parent_name, vdev_type, (u_longlong_t)vdev_id);
274 	}
275 	return (vdev_name);
276 }
277 
278 /*
279  * get a string suitable for an influxdb tag that describes this vdev
280  *
281  * By default only the vdev hierarchical name is shown, separated by '/'
282  * If the vdev has an associated path, which is typical of leaf vdevs,
283  * then the path is added.
284  * It would be nice to have the devid instead of the path, but under
285  * Linux we cannot be sure a devid will exist and we'd rather have
286  * something than nothing, so we'll use path instead.
287  */
288 static char *
get_vdev_desc(nvlist_t * nvroot,const char * parent_name)289 get_vdev_desc(nvlist_t *nvroot, const char *parent_name)
290 {
291 	static char vdev_desc[2 * MAXPATHLEN];
292 	char vdev_value[MAXPATHLEN];
293 	char *s, *t;
294 
295 	const char *vdev_type = "unknown";
296 	uint64_t vdev_id = UINT64_MAX;
297 	const char *vdev_path = NULL;
298 	(void) nvlist_lookup_string(nvroot, ZPOOL_CONFIG_TYPE, &vdev_type);
299 	(void) nvlist_lookup_uint64(nvroot, ZPOOL_CONFIG_ID, &vdev_id);
300 	(void) nvlist_lookup_string(nvroot, ZPOOL_CONFIG_PATH, &vdev_path);
301 
302 	if (parent_name == NULL) {
303 		s = escape_string(vdev_type);
304 		(void) snprintf(vdev_value, sizeof (vdev_value), "vdev=%s", s);
305 		free(s);
306 	} else {
307 		s = escape_string((char *)parent_name);
308 		t = escape_string(vdev_type);
309 		(void) snprintf(vdev_value, sizeof (vdev_value),
310 		    "vdev=%s/%s-%llu", s, t, (u_longlong_t)vdev_id);
311 		free(s);
312 		free(t);
313 	}
314 	if (vdev_path == NULL) {
315 		(void) snprintf(vdev_desc, sizeof (vdev_desc), "%s",
316 		    vdev_value);
317 	} else {
318 		s = escape_string(vdev_path);
319 		(void) snprintf(vdev_desc, sizeof (vdev_desc), "path=%s,%s",
320 		    s, vdev_value);
321 		free(s);
322 	}
323 	return (vdev_desc);
324 }
325 
326 /*
327  * vdev summary stats are a combination of the data shown by
328  * `zpool status` and `zpool list -v`
329  */
330 static int
print_summary_stats(nvlist_t * nvroot,const char * pool_name,const char * parent_name)331 print_summary_stats(nvlist_t *nvroot, const char *pool_name,
332     const char *parent_name)
333 {
334 	uint_t c;
335 	vdev_stat_t *vs;
336 	char *vdev_desc = NULL;
337 	vdev_desc = get_vdev_desc(nvroot, parent_name);
338 	if (nvlist_lookup_uint64_array(nvroot, ZPOOL_CONFIG_VDEV_STATS,
339 	    (uint64_t **)&vs, &c) != 0) {
340 		return (1);
341 	}
342 	printf("%s%s,name=%s,state=%s,%s ", POOL_MEASUREMENT, tags,
343 	    pool_name, zpool_state_to_name((vdev_state_t)vs->vs_state,
344 	    (vdev_aux_t)vs->vs_aux), vdev_desc);
345 	print_kv("alloc", vs->vs_alloc);
346 	print_kv(",free", vs->vs_space - vs->vs_alloc);
347 	print_kv(",size", vs->vs_space);
348 	print_kv(",read_bytes", vs->vs_bytes[ZIO_TYPE_READ]);
349 	print_kv(",read_errors", vs->vs_read_errors);
350 	print_kv(",read_ops", vs->vs_ops[ZIO_TYPE_READ]);
351 	print_kv(",write_bytes", vs->vs_bytes[ZIO_TYPE_WRITE]);
352 	print_kv(",write_errors", vs->vs_write_errors);
353 	print_kv(",write_ops", vs->vs_ops[ZIO_TYPE_WRITE]);
354 	print_kv(",checksum_errors", vs->vs_checksum_errors);
355 	print_kv(",fragmentation", vs->vs_fragmentation);
356 	print_kv(",slow_ios", vs->vs_slow_ios);
357 	printf(" %llu\n", (u_longlong_t)timestamp);
358 	return (0);
359 }
360 
361 /*
362  * vdev latency stats are histograms stored as nvlist arrays of uint64.
363  * Latency stats include the ZIO scheduler classes plus lower-level
364  * vdev latencies.
365  *
366  * In many cases, the top-level "root" view obscures the underlying
367  * top-level vdev operations. For example, if a pool has a log, special,
368  * or cache device, then each can behave very differently. It is useful
369  * to see how each is responding.
370  */
371 static int
print_vdev_latency_stats(nvlist_t * nvroot,const char * pool_name,const char * parent_name)372 print_vdev_latency_stats(nvlist_t *nvroot, const char *pool_name,
373     const char *parent_name)
374 {
375 	uint_t c, end = 0;
376 	nvlist_t *nv_ex;
377 	char *vdev_desc = NULL;
378 
379 	/* short_names become part of the metric name and are influxdb-ready */
380 	struct lat_lookup {
381 	    const char *name;
382 	    const char *short_name;
383 	    uint64_t sum;
384 	    uint64_t *array;
385 	};
386 	struct lat_lookup lat_type[] = {
387 	    {ZPOOL_CONFIG_VDEV_TOT_R_LAT_HISTO,   "total_read", 0},
388 	    {ZPOOL_CONFIG_VDEV_TOT_W_LAT_HISTO,   "total_write", 0},
389 	    {ZPOOL_CONFIG_VDEV_DISK_R_LAT_HISTO,  "disk_read", 0},
390 	    {ZPOOL_CONFIG_VDEV_DISK_W_LAT_HISTO,  "disk_write", 0},
391 	    {ZPOOL_CONFIG_VDEV_SYNC_R_LAT_HISTO,  "sync_read", 0},
392 	    {ZPOOL_CONFIG_VDEV_SYNC_W_LAT_HISTO,  "sync_write", 0},
393 	    {ZPOOL_CONFIG_VDEV_ASYNC_R_LAT_HISTO, "async_read", 0},
394 	    {ZPOOL_CONFIG_VDEV_ASYNC_W_LAT_HISTO, "async_write", 0},
395 	    {ZPOOL_CONFIG_VDEV_SCRUB_LAT_HISTO,   "scrub", 0},
396 #ifdef ZPOOL_CONFIG_VDEV_TRIM_LAT_HISTO
397 	    {ZPOOL_CONFIG_VDEV_TRIM_LAT_HISTO,    "trim", 0},
398 #endif
399 	    {ZPOOL_CONFIG_VDEV_REBUILD_LAT_HISTO,    "rebuild", 0},
400 	    {NULL,	NULL}
401 	};
402 
403 	if (nvlist_lookup_nvlist(nvroot,
404 	    ZPOOL_CONFIG_VDEV_STATS_EX, &nv_ex) != 0) {
405 		return (6);
406 	}
407 
408 	vdev_desc = get_vdev_desc(nvroot, parent_name);
409 
410 	for (int i = 0; lat_type[i].name; i++) {
411 		if (nvlist_lookup_uint64_array(nv_ex,
412 		    lat_type[i].name, &lat_type[i].array, &c) != 0) {
413 			fprintf(stderr, "error: can't get %s\n",
414 			    lat_type[i].name);
415 			return (3);
416 		}
417 		/* end count count, all of the arrays are the same size */
418 		end = c - 1;
419 	}
420 
421 	for (int bucket = 0; bucket <= end; bucket++) {
422 		if (bucket < MIN_LAT_INDEX) {
423 			/* don't print, but collect the sum */
424 			for (int i = 0; lat_type[i].name; i++) {
425 				lat_type[i].sum += lat_type[i].array[bucket];
426 			}
427 			continue;
428 		}
429 		if (bucket < end) {
430 			printf("%s%s,le=%0.6f,name=%s,%s ",
431 			    POOL_LATENCY_MEASUREMENT, tags,
432 			    (float)(1ULL << bucket) * 1e-9,
433 			    pool_name, vdev_desc);
434 		} else {
435 			printf("%s%s,le=+Inf,name=%s,%s ",
436 			    POOL_LATENCY_MEASUREMENT, tags, pool_name,
437 			    vdev_desc);
438 		}
439 		for (int i = 0; lat_type[i].name; i++) {
440 			if (bucket <= MIN_LAT_INDEX || sum_histogram_buckets) {
441 				lat_type[i].sum += lat_type[i].array[bucket];
442 			} else {
443 				lat_type[i].sum = lat_type[i].array[bucket];
444 			}
445 			print_kv(lat_type[i].short_name, lat_type[i].sum);
446 			if (lat_type[i + 1].name != NULL) {
447 				printf(",");
448 			}
449 		}
450 		printf(" %llu\n", (u_longlong_t)timestamp);
451 	}
452 	return (0);
453 }
454 
455 /*
456  * vdev request size stats are histograms stored as nvlist arrays of uint64.
457  * Request size stats include the ZIO scheduler classes plus lower-level
458  * vdev sizes. Both independent (ind) and aggregated (agg) sizes are reported.
459  *
460  * In many cases, the top-level "root" view obscures the underlying
461  * top-level vdev operations. For example, if a pool has a log, special,
462  * or cache device, then each can behave very differently. It is useful
463  * to see how each is responding.
464  */
465 static int
print_vdev_size_stats(nvlist_t * nvroot,const char * pool_name,const char * parent_name)466 print_vdev_size_stats(nvlist_t *nvroot, const char *pool_name,
467     const char *parent_name)
468 {
469 	uint_t c, end = 0;
470 	nvlist_t *nv_ex;
471 	char *vdev_desc = NULL;
472 
473 	/* short_names become the field name */
474 	struct size_lookup {
475 	    const char *name;
476 	    const char *short_name;
477 	    uint64_t sum;
478 	    uint64_t *array;
479 	};
480 	struct size_lookup size_type[] = {
481 	    {ZPOOL_CONFIG_VDEV_SYNC_IND_R_HISTO,   "sync_read_ind"},
482 	    {ZPOOL_CONFIG_VDEV_SYNC_IND_W_HISTO,   "sync_write_ind"},
483 	    {ZPOOL_CONFIG_VDEV_ASYNC_IND_R_HISTO,  "async_read_ind"},
484 	    {ZPOOL_CONFIG_VDEV_ASYNC_IND_W_HISTO,  "async_write_ind"},
485 	    {ZPOOL_CONFIG_VDEV_IND_SCRUB_HISTO,    "scrub_read_ind"},
486 	    {ZPOOL_CONFIG_VDEV_SYNC_AGG_R_HISTO,   "sync_read_agg"},
487 	    {ZPOOL_CONFIG_VDEV_SYNC_AGG_W_HISTO,   "sync_write_agg"},
488 	    {ZPOOL_CONFIG_VDEV_ASYNC_AGG_R_HISTO,  "async_read_agg"},
489 	    {ZPOOL_CONFIG_VDEV_ASYNC_AGG_W_HISTO,  "async_write_agg"},
490 	    {ZPOOL_CONFIG_VDEV_AGG_SCRUB_HISTO,    "scrub_read_agg"},
491 #ifdef ZPOOL_CONFIG_VDEV_IND_TRIM_HISTO
492 	    {ZPOOL_CONFIG_VDEV_IND_TRIM_HISTO,    "trim_write_ind"},
493 	    {ZPOOL_CONFIG_VDEV_AGG_TRIM_HISTO,    "trim_write_agg"},
494 #endif
495 	    {ZPOOL_CONFIG_VDEV_IND_REBUILD_HISTO,    "rebuild_write_ind"},
496 	    {ZPOOL_CONFIG_VDEV_AGG_REBUILD_HISTO,    "rebuild_write_agg"},
497 	    {NULL,	NULL}
498 	};
499 
500 	if (nvlist_lookup_nvlist(nvroot,
501 	    ZPOOL_CONFIG_VDEV_STATS_EX, &nv_ex) != 0) {
502 		return (6);
503 	}
504 
505 	vdev_desc = get_vdev_desc(nvroot, parent_name);
506 
507 	for (int i = 0; size_type[i].name; i++) {
508 		if (nvlist_lookup_uint64_array(nv_ex, size_type[i].name,
509 		    &size_type[i].array, &c) != 0) {
510 			fprintf(stderr, "error: can't get %s\n",
511 			    size_type[i].name);
512 			return (3);
513 		}
514 		/* end count count, all of the arrays are the same size */
515 		end = c - 1;
516 	}
517 
518 	for (int bucket = 0; bucket <= end; bucket++) {
519 		if (bucket < MIN_SIZE_INDEX) {
520 			/* don't print, but collect the sum */
521 			for (int i = 0; size_type[i].name; i++) {
522 				size_type[i].sum += size_type[i].array[bucket];
523 			}
524 			continue;
525 		}
526 
527 		if (bucket < end) {
528 			printf("%s%s,le=%llu,name=%s,%s ",
529 			    POOL_IO_SIZE_MEASUREMENT, tags, 1ULL << bucket,
530 			    pool_name, vdev_desc);
531 		} else {
532 			printf("%s%s,le=+Inf,name=%s,%s ",
533 			    POOL_IO_SIZE_MEASUREMENT, tags, pool_name,
534 			    vdev_desc);
535 		}
536 		for (int i = 0; size_type[i].name; i++) {
537 			if (bucket <= MIN_SIZE_INDEX || sum_histogram_buckets) {
538 				size_type[i].sum += size_type[i].array[bucket];
539 			} else {
540 				size_type[i].sum = size_type[i].array[bucket];
541 			}
542 			print_kv(size_type[i].short_name, size_type[i].sum);
543 			if (size_type[i + 1].name != NULL) {
544 				printf(",");
545 			}
546 		}
547 		printf(" %llu\n", (u_longlong_t)timestamp);
548 	}
549 	return (0);
550 }
551 
552 /*
553  * ZIO scheduler queue stats are stored as gauges. This is unfortunate
554  * because the values can change very rapidly and any point-in-time
555  * value will quickly be obsoleted. It is also not easy to downsample.
556  * Thus only the top-level queue stats might be beneficial... maybe.
557  */
558 static int
print_queue_stats(nvlist_t * nvroot,const char * pool_name,const char * parent_name)559 print_queue_stats(nvlist_t *nvroot, const char *pool_name,
560     const char *parent_name)
561 {
562 	nvlist_t *nv_ex;
563 	uint64_t value;
564 
565 	/* short_names are used for the field name */
566 	struct queue_lookup {
567 	    const char *name;
568 	    const char *short_name;
569 	};
570 	struct queue_lookup queue_type[] = {
571 	    {ZPOOL_CONFIG_VDEV_SYNC_R_ACTIVE_QUEUE,	"sync_r_active"},
572 	    {ZPOOL_CONFIG_VDEV_SYNC_W_ACTIVE_QUEUE,	"sync_w_active"},
573 	    {ZPOOL_CONFIG_VDEV_ASYNC_R_ACTIVE_QUEUE,	"async_r_active"},
574 	    {ZPOOL_CONFIG_VDEV_ASYNC_W_ACTIVE_QUEUE,	"async_w_active"},
575 	    {ZPOOL_CONFIG_VDEV_SCRUB_ACTIVE_QUEUE,	"async_scrub_active"},
576 	    {ZPOOL_CONFIG_VDEV_REBUILD_ACTIVE_QUEUE,	"rebuild_active"},
577 	    {ZPOOL_CONFIG_VDEV_SYNC_R_PEND_QUEUE,	"sync_r_pend"},
578 	    {ZPOOL_CONFIG_VDEV_SYNC_W_PEND_QUEUE,	"sync_w_pend"},
579 	    {ZPOOL_CONFIG_VDEV_ASYNC_R_PEND_QUEUE,	"async_r_pend"},
580 	    {ZPOOL_CONFIG_VDEV_ASYNC_W_PEND_QUEUE,	"async_w_pend"},
581 	    {ZPOOL_CONFIG_VDEV_SCRUB_PEND_QUEUE,	"async_scrub_pend"},
582 	    {ZPOOL_CONFIG_VDEV_REBUILD_PEND_QUEUE,	"rebuild_pend"},
583 	    {NULL,	NULL}
584 	};
585 
586 	if (nvlist_lookup_nvlist(nvroot,
587 	    ZPOOL_CONFIG_VDEV_STATS_EX, &nv_ex) != 0) {
588 		return (6);
589 	}
590 
591 	printf("%s%s,name=%s,%s ", POOL_QUEUE_MEASUREMENT, tags, pool_name,
592 	    get_vdev_desc(nvroot, parent_name));
593 	for (int i = 0; queue_type[i].name; i++) {
594 		if (nvlist_lookup_uint64(nv_ex,
595 		    queue_type[i].name, &value) != 0) {
596 			fprintf(stderr, "error: can't get %s\n",
597 			    queue_type[i].name);
598 			return (3);
599 		}
600 		print_kv(queue_type[i].short_name, value);
601 		if (queue_type[i + 1].name != NULL) {
602 			printf(",");
603 		}
604 	}
605 	printf(" %llu\n", (u_longlong_t)timestamp);
606 	return (0);
607 }
608 
609 /*
610  * top-level vdev stats are at the pool level
611  */
612 static int
print_top_level_vdev_stats(nvlist_t * nvroot,const char * pool_name)613 print_top_level_vdev_stats(nvlist_t *nvroot, const char *pool_name)
614 {
615 	nvlist_t *nv_ex;
616 	uint64_t value;
617 
618 	/* short_names become part of the metric name */
619 	struct queue_lookup {
620 	    const char *name;
621 	    const char *short_name;
622 	};
623 	struct queue_lookup queue_type[] = {
624 	    {ZPOOL_CONFIG_VDEV_SYNC_R_ACTIVE_QUEUE, "sync_r_active_queue"},
625 	    {ZPOOL_CONFIG_VDEV_SYNC_W_ACTIVE_QUEUE, "sync_w_active_queue"},
626 	    {ZPOOL_CONFIG_VDEV_ASYNC_R_ACTIVE_QUEUE, "async_r_active_queue"},
627 	    {ZPOOL_CONFIG_VDEV_ASYNC_W_ACTIVE_QUEUE, "async_w_active_queue"},
628 	    {ZPOOL_CONFIG_VDEV_SCRUB_ACTIVE_QUEUE, "async_scrub_active_queue"},
629 	    {ZPOOL_CONFIG_VDEV_REBUILD_ACTIVE_QUEUE, "rebuild_active_queue"},
630 	    {ZPOOL_CONFIG_VDEV_SYNC_R_PEND_QUEUE, "sync_r_pend_queue"},
631 	    {ZPOOL_CONFIG_VDEV_SYNC_W_PEND_QUEUE, "sync_w_pend_queue"},
632 	    {ZPOOL_CONFIG_VDEV_ASYNC_R_PEND_QUEUE, "async_r_pend_queue"},
633 	    {ZPOOL_CONFIG_VDEV_ASYNC_W_PEND_QUEUE, "async_w_pend_queue"},
634 	    {ZPOOL_CONFIG_VDEV_SCRUB_PEND_QUEUE, "async_scrub_pend_queue"},
635 	    {ZPOOL_CONFIG_VDEV_REBUILD_PEND_QUEUE, "rebuild_pend_queue"},
636 	    {NULL, NULL}
637 	};
638 
639 	if (nvlist_lookup_nvlist(nvroot,
640 	    ZPOOL_CONFIG_VDEV_STATS_EX, &nv_ex) != 0) {
641 		return (6);
642 	}
643 
644 	printf("%s%s,name=%s,vdev=root ", VDEV_MEASUREMENT, tags,
645 	    pool_name);
646 	for (int i = 0; queue_type[i].name; i++) {
647 		if (nvlist_lookup_uint64(nv_ex,
648 		    queue_type[i].name, &value) != 0) {
649 			fprintf(stderr, "error: can't get %s\n",
650 			    queue_type[i].name);
651 			return (3);
652 		}
653 		if (i > 0)
654 			printf(",");
655 		print_kv(queue_type[i].short_name, value);
656 	}
657 
658 	printf(" %llu\n", (u_longlong_t)timestamp);
659 	return (0);
660 }
661 
662 /*
663  * recursive stats printer
664  */
665 static int
print_recursive_stats(stat_printer_f func,nvlist_t * nvroot,const char * pool_name,const char * parent_name,int descend)666 print_recursive_stats(stat_printer_f func, nvlist_t *nvroot,
667     const char *pool_name, const char *parent_name, int descend)
668 {
669 	uint_t c, children;
670 	nvlist_t **child;
671 	char vdev_name[256];
672 	int err;
673 
674 	err = func(nvroot, pool_name, parent_name);
675 	if (err)
676 		return (err);
677 
678 	if (descend && nvlist_lookup_nvlist_array(nvroot, ZPOOL_CONFIG_CHILDREN,
679 	    &child, &children) == 0) {
680 		(void) strlcpy(vdev_name, get_vdev_name(nvroot, parent_name),
681 		    sizeof (vdev_name));
682 
683 		for (c = 0; c < children; c++) {
684 			err = print_recursive_stats(func, child[c], pool_name,
685 			    vdev_name, descend);
686 			if (err)
687 				return (err);
688 		}
689 	}
690 	return (0);
691 }
692 
693 /*
694  * call-back to print the stats from the pool config
695  *
696  * Note: if the pool is broken, this can hang indefinitely and perhaps in an
697  * unkillable state.
698  */
699 static int
print_stats(zpool_handle_t * zhp,void * data)700 print_stats(zpool_handle_t *zhp, void *data)
701 {
702 	uint_t c;
703 	int err;
704 	boolean_t missing;
705 	nvlist_t *config, *nvroot;
706 	vdev_stat_t *vs;
707 	struct timespec tv;
708 	char *pool_name;
709 
710 	/* if not this pool return quickly */
711 	if (data &&
712 	    strncmp(data, zpool_get_name(zhp), ZFS_MAX_DATASET_NAME_LEN) != 0) {
713 		zpool_close(zhp);
714 		return (0);
715 	}
716 
717 	if (zpool_refresh_stats(zhp, &missing) != 0) {
718 		zpool_close(zhp);
719 		return (1);
720 	}
721 
722 	config = zpool_get_config(zhp, NULL);
723 	if (clock_gettime(CLOCK_REALTIME, &tv) != 0)
724 		timestamp = (uint64_t)time(NULL) * 1000000000;
725 	else
726 		timestamp =
727 		    ((uint64_t)tv.tv_sec * 1000000000) + (uint64_t)tv.tv_nsec;
728 
729 	if (nvlist_lookup_nvlist(
730 	    config, ZPOOL_CONFIG_VDEV_TREE, &nvroot) != 0) {
731 	zpool_close(zhp);
732 		return (2);
733 	}
734 	if (nvlist_lookup_uint64_array(nvroot, ZPOOL_CONFIG_VDEV_STATS,
735 	    (uint64_t **)&vs, &c) != 0) {
736 	zpool_close(zhp);
737 		return (3);
738 	}
739 
740 	pool_name = escape_string(zpool_get_name(zhp));
741 	err = print_recursive_stats(print_summary_stats, nvroot,
742 	    pool_name, NULL, 1);
743 	/* if any of these return an error, skip the rest */
744 	if (err == 0)
745 	err = print_top_level_vdev_stats(nvroot, pool_name);
746 
747 	if (no_histograms == 0) {
748 	if (err == 0)
749 		err = print_recursive_stats(print_vdev_latency_stats, nvroot,
750 		    pool_name, NULL, 1);
751 	if (err == 0)
752 		err = print_recursive_stats(print_vdev_size_stats, nvroot,
753 		    pool_name, NULL, 1);
754 	if (err == 0)
755 		err = print_recursive_stats(print_queue_stats, nvroot,
756 		    pool_name, NULL, 0);
757 	}
758 	if (err == 0)
759 		err = print_scan_status(nvroot, pool_name);
760 
761 	free(pool_name);
762 	zpool_close(zhp);
763 	return (err);
764 }
765 
766 static void
usage(char * name)767 usage(char *name)
768 {
769 	fprintf(stderr, "usage: %s [--execd][--no-histograms]"
770 	    "[--sum-histogram-buckets] [--signed-int] [poolname]\n", name);
771 	exit(EXIT_FAILURE);
772 }
773 
774 int
main(int argc,char * argv[])775 main(int argc, char *argv[])
776 {
777 	int opt;
778 	int ret = 8;
779 	char *line = NULL, *ttags = NULL;
780 	size_t len, tagslen = 0;
781 	struct option long_options[] = {
782 	    {"execd", no_argument, NULL, 'e'},
783 	    {"help", no_argument, NULL, 'h'},
784 	    {"no-histograms", no_argument, NULL, 'n'},
785 	    {"signed-int", no_argument, NULL, 'i'},
786 	    {"sum-histogram-buckets", no_argument, NULL, 's'},
787 	    {"tags", required_argument, NULL, 't'},
788 	    {0, 0, 0, 0}
789 	};
790 	while ((opt = getopt_long(
791 	    argc, argv, "ehinst:", long_options, NULL)) != -1) {
792 		switch (opt) {
793 		case 'e':
794 			execd_mode = 1;
795 			break;
796 		case 'i':
797 			metric_data_type = 'i';
798 			metric_value_mask = INT64_MAX;
799 			break;
800 		case 'n':
801 			no_histograms = 1;
802 			break;
803 		case 's':
804 			sum_histogram_buckets = 1;
805 			break;
806 		case 't':
807 			free(ttags);
808 			tagslen = strlen(optarg) + 2;
809 			ttags = calloc(1, tagslen);
810 			if (ttags == NULL) {
811 				fprintf(stderr,
812 				    "error: cannot allocate memory "
813 				    "for tags\n");
814 				exit(1);
815 			}
816 			(void) snprintf(ttags, tagslen, ",%s", optarg);
817 			tags = ttags;
818 			break;
819 		default:
820 			usage(argv[0]);
821 		}
822 	}
823 
824 	libzfs_handle_t *g_zfs;
825 	if ((g_zfs = libzfs_init()) == NULL) {
826 		fprintf(stderr,
827 		    "error: cannot initialize libzfs. "
828 		    "Is the zfs module loaded or zrepl running?\n");
829 		exit(EXIT_FAILURE);
830 	}
831 	if (execd_mode == 0) {
832 		ret = zpool_iter(g_zfs, print_stats, argv[optind]);
833 		return (ret);
834 	}
835 	while (getline(&line, &len, stdin) != -1) {
836 		ret = zpool_iter(g_zfs, print_stats, argv[optind]);
837 		fflush(stdout);
838 	}
839 	return (ret);
840 }
841