xref: /freebsd/usr.sbin/gstat/gstat.c (revision 9aff62dee28239f84b4aa4a3429cf26dbbf770cc)
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright (c) 2003 Poul-Henning Kamp
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. The names of the authors may not be used to endorse or promote
16  *    products derived from this software without specific prior written
17  *    permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 
33 #include <sys/devicestat.h>
34 #include <sys/mman.h>
35 #include <sys/resource.h>
36 #include <sys/time.h>
37 
38 #include <assert.h>
39 #include <curses.h>
40 #include <devstat.h>
41 #include <err.h>
42 #include <errno.h>
43 #include <fcntl.h>
44 #include <histedit.h>
45 #include <libgeom.h>
46 #include <paths.h>
47 #include <regex.h>
48 #include <stdarg.h>
49 #include <stdint.h>
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include <sysexits.h>
54 #include <unistd.h>
55 
56 #define HIGH_PCT_BUSY_THRESH 80
57 #define MEDIUM_PCT_BUSY_THRESH 50
58 
59 static int flag_a, flag_b, flag_B, flag_c, flag_C, flag_d, flag_o, flag_p,
60 	   flag_s;
61 static int flag_I = 1000000;
62 
63 static int quit;
64 static int max_flen;
65 static History *hist;
66 static EditLine *el;
67 static HistEvent hist_ev;
68 static regex_t f_re;
69 static char pf_s[100];
70 static char f_s[100];
71 static uintmax_t loop;
72 static double dt;
73 static double byte_scale = 1024;
74 static const char *byte_unit = "k";
75 
76 struct kstat {
77 	long double		transfers_per_second;
78 	long double		mb_per_second;
79 	long double		ms_per_transaction;
80 	long double		kb_per_transfer;
81 };
82 
83 static void __dead2
84 usage(void)
85 {
86 	fprintf(stderr,
87 		"usage: gstat [-abBcCdgkmops] [-f filter] [-I interval]\n");
88 	exit(EX_USAGE);
89 	/* NOTREACHED */
90 }
91 
92 static void
93 use_kb(void)
94 {
95 	byte_scale = 1024;
96 	byte_unit = "k";
97 }
98 
99 static void
100 use_mb(void)
101 {
102 	byte_scale = 1;
103 	byte_unit = "M";
104 }
105 
106 static void
107 use_gb(void)
108 {
109 	byte_scale = 1.0/1024;
110 	byte_unit = "G";
111 }
112 
113 static void
114 printer(const char *fmt, ...)
115 {
116 	va_list ap;
117 	va_start(ap, fmt);
118 	if (flag_B || (flag_b && loop == 2))
119 		vprintf(fmt, ap);
120 	if (!flag_b)
121 		vw_printw(stdscr, fmt, ap);
122 	va_end(ap);
123 }
124 
125 static void
126 text_fld_header(const char *k)
127 {
128 	if (flag_s)
129 		printer(" %s/s     kB   %sBps   ms/%s   ", k, byte_unit, k);
130 	else
131 		printer(" %s/s   %sBps   ms/%s   ", k, byte_unit, k);
132 }
133 
134 static void
135 text_header(void)
136 {
137 	int curx = 0, cury = 0;
138 	int maxx = 0, maxy = 0;
139 
140 	if (!flag_b)
141 		move(0,0);
142 	printer("dT: %5.3fs  w: %.3fs", dt,
143 			(float)flag_I / 1000000);
144 	if (f_s[0] != '\0') {
145 		printer("  filter: ");
146 		if (!flag_b) {
147 			getyx(stdscr, cury, curx);
148 			(void)cury;
149 			getmaxyx(stdscr, maxy, maxx);
150 			(void)maxy;
151 		}
152 		strlcpy(pf_s, f_s, sizeof(pf_s));
153 		max_flen = maxx - curx - 1;
154 		if ((int)strlen(f_s) > max_flen && max_flen >= 0) {
155 			if (max_flen > 3)
156 				pf_s[max_flen - 3] = '.';
157 			if (max_flen > 2)
158 				pf_s[max_flen - 2] = '.';
159 			if (max_flen > 1)
160 				pf_s[max_flen - 1] = '.';
161 			pf_s[max_flen] = '\0';
162 		}
163 		printer("%s", pf_s);
164 	}
165 
166 	if (!flag_b)
167 		clrtoeol();
168 	printer("\n");
169 
170 	printer(" L(q)  ops/s   ");
171 	text_fld_header("r");
172 	text_fld_header("w");
173 	if (flag_d)
174 		text_fld_header("d");
175 	if (flag_o)
176 		printer(" o/s   ms/o   ");
177 	printer("%%busy Name");
178 	if (!flag_b)
179 		clrtoeol();
180 	printer("\n");
181 }
182 
183 static void
184 print_kstat_text(const struct kstat *ks, int other)
185 {
186 	printer(" %6.0f", (double)ks->transfers_per_second);
187 	if (flag_s && !other)
188 		printer(" %6.0f", (double)ks->kb_per_transfer);
189 	if (!other)
190 		printer(" %6.0f", (double)ks->mb_per_second * byte_scale);
191 	if (ks->ms_per_transaction < 100)
192 		printer(" %6.3f", (double)ks->ms_per_transaction);
193 	else if (ks->ms_per_transaction < 1000)
194 		printer(" %6.2f", (double)ks->ms_per_transaction);
195 	else if (ks->ms_per_transaction < 10000)
196 		printer(" %6.1f", (double)ks->ms_per_transaction);
197 	else
198 		printer(" %6.0f", (double)ks->ms_per_transaction);
199 }
200 
201 static void
202 print_head_csv(void)
203 {
204 
205 	printf("timestamp,name,q-depth,total_ops/s");
206 	if (flag_s) {
207 		printf(",read/s,read_sz-KiB");
208 		printf(",read-KiB/s,ms/read");
209 		printf(",write/s,write_sz-KiB");
210 		printf(",write-KiB/s,ms/write");
211 	} else {
212 		printf(",read/s,read-KiB/s,ms/read");
213 		printf(",write/s,write-KiB/s,ms/write");
214 	}
215 	if (flag_d) {
216 		if (flag_s) {
217 			printf(",delete/s,delete-sz-KiB");
218 			printf(",delete-KiB/s,ms/delete");
219 		} else {
220 			printf(",delete/s,delete-KiB/s");
221 			printf(",ms/delete");
222 		}
223 	}
224 	if (flag_o)
225 		printf(",other/s,ms/other");
226 	printf(",%%busy\n");
227 }
228 
229 static void
230 print_kstat_csv(const struct kstat *ks, int other)
231 {
232 
233 	printf(",%.0f", (double)ks->transfers_per_second);
234 	if (flag_s && !other)
235 		printf(",%.0f", (double)ks->kb_per_transfer);
236 	if (!other)
237 		printf(",%.0f", (double)ks->mb_per_second * 1024);
238 	printf(",%.3f", (double)ks->ms_per_transaction);
239 }
240 
241 static const char*
242 el_prompt(void)
243 {
244 
245 	return ("Filter: ");
246 }
247 
248 static void
249 setup_interactive(void)
250 {
251 	short cf, cb;
252 
253 	/* Setup libedit */
254 	hist = history_init();
255 	if (hist == NULL)
256 		err(EX_SOFTWARE, "history_init()");
257 	history(hist, &hist_ev, H_SETSIZE, 100);
258 	el = el_init("gstat", stdin, stdout, stderr);
259 	if (el == NULL)
260 		err(EX_SOFTWARE, "el_init");
261 	el_set(el, EL_EDITOR, "emacs");
262 	el_set(el, EL_SIGNAL, 1);
263 	el_set(el, EL_HIST, history, hist);
264 	el_set(el, EL_PROMPT, el_prompt);
265 	if (f_s[0] != '\0')
266 		history(hist, &hist_ev, H_ENTER, f_s);
267 	/* Setup curses */
268 	initscr();
269 	start_color();
270 	use_default_colors();
271 	pair_content(0, &cf, &cb);
272 	init_pair(1, COLOR_GREEN, cb);
273 	init_pair(2, COLOR_MAGENTA, cb);
274 	init_pair(3, COLOR_RED, cb);
275 	cbreak();
276 	noecho();
277 	nonl();
278 	nodelay(stdscr, 1);
279 	intrflush(stdscr, FALSE);
280 	keypad(stdscr, TRUE);
281 }
282 
283 static int
284 poll_input(void)
285 {
286 	int i, line_len;
287 	char *p;
288 	const char *line;
289 	char tmp_f_s[100];
290 	regex_t tmp_f_re;
291 
292 	i = getch();
293 	if (i == ERR)
294 		return (0);
295 	switch (i) {
296 	case '>':
297 		flag_I *= 2;
298 		break;
299 	case '<':
300 		flag_I /= 2;
301 		if (flag_I < 1000)
302 			flag_I = 1000;
303 		break;
304 	case '1':
305 		flag_I = 1000000;
306 		break;
307 	case 'c':
308 		flag_c = !flag_c;
309 		break;
310 	case 'd':
311 		flag_d = !flag_d;
312 		break;
313 	case 'o':
314 		flag_o = !flag_o;
315 		break;
316 	case 'f':
317 		move(0,0);
318 		clrtoeol();
319 		refresh();
320 		line = el_gets(el, &line_len);
321 		if (line == NULL)
322 			err(1, "el_gets");
323 		if (line_len > 1)
324 			history(hist, &hist_ev, H_ENTER, line);
325 		strlcpy(tmp_f_s, line, sizeof(f_s));
326 		if ((p = strchr(tmp_f_s, '\n')) != NULL)
327 			*p = '\0';
328 		/*
329 		 * Fix the terminal.  We messed up
330 		 * curses idea of the screen by using
331 		 * libedit.
332 		 */
333 		clear();
334 		refresh();
335 		cbreak();
336 		noecho();
337 		nonl();
338 		if (regcomp(&tmp_f_re, tmp_f_s, REG_EXTENDED)
339 		    != 0) {
340 			move(0, 0);
341 			printw("Invalid filter");
342 			refresh();
343 			sleep(1);
344 		} else {
345 			strlcpy(f_s, tmp_f_s, sizeof(f_s));
346 			f_re = tmp_f_re;
347 		}
348 		break;
349 	case 'F':
350 		f_s[0] = '\0';
351 		break;
352 	case 'g':
353 	case 'G':
354 		use_gb();
355 		break;
356 	case 'k':
357 	case 'K':
358 		use_kb();
359 		break;
360 	case 'm':
361 	case 'M':
362 		use_mb();
363 		break;
364 	case 'q':
365 		quit = 1;
366 		break;
367 	case 'p':
368 		flag_p = !flag_p;
369 		break;
370 	case 's':
371 		flag_s = !flag_s;
372 		break;
373 	default:
374 		break;
375 	}
376 	return (1);
377 }
378 
379 int
380 main(int argc, char **argv)
381 {
382 	int error, i;
383 	int curx, cury, maxx, maxy;
384 	struct devstat *gsp, *gsq;
385 	void *sp, *sq;
386 	struct timespec tp, tq;
387 	struct gmesh gmp;
388 	struct gprovider *pp;
389 	struct gconsumer *cp;
390 	struct gident *gid;
391 	char *p;
392 	char ts[100], ts2[100], g_name[4096];
393 	long double xfer_per_sec, busy_pct;
394 	struct kstat krd, kwr, kfree, kother;
395 	uint64_t queue_len;
396 
397 	memset(&kother, 0, sizeof kother);
398 	hist = NULL;
399 	el = NULL;
400 	curx = -1;
401 	loop = 0;
402 	/* Turn on batch mode if output is not tty. */
403 	if (!isatty(fileno(stdout)))
404 		flag_b = 1;
405 
406 	f_s[0] = '\0';
407 	while ((i = getopt(argc, argv, "abBcdCf:gI:kmops")) != -1) {
408 		switch (i) {
409 		case 'a':
410 			flag_a = 1;
411 			break;
412 		case 'b':
413 			flag_b = 1;
414 			break;
415 		case 'B':
416 			flag_B = 1;
417 			flag_b = 1;
418 			break;
419 		case 'c':
420 			flag_c = 1;
421 			break;
422 		case 'C':
423 			flag_C = 1;
424 			/* csv out implies repeating batch mode */
425 			flag_b = 1;
426 			flag_B = 1;
427 			break;
428 		case 'd':
429 			flag_d = 1;
430 			break;
431 		case 'f':
432 			if (strlen(optarg) > sizeof(f_s) - 1)
433 				errx(EX_USAGE, "Filter string too long");
434 			if (regcomp(&f_re, optarg, REG_EXTENDED) != 0)
435 				errx(EX_USAGE,
436 				    "Invalid filter - see re_format(7)");
437 			strlcpy(f_s, optarg, sizeof(f_s));
438 			break;
439 		case 'g':
440 			use_gb();
441 			break;
442 		case 'I':
443 			errno = 0;
444 			i = strtoul(optarg, &p, 0);
445 			if (p == optarg || errno == EINVAL ||
446 			    errno == ERANGE) {
447 				errx(1, "Invalid argument to -I");
448 			} else if (!strcmp(p, "s"))
449 				i *= 1000000;
450 			else if (!strcmp(p, "ms"))
451 				i *= 1000;
452 			else if (!strcmp(p, "us"))
453 				i *= 1;
454 			flag_I = i;
455 			break;
456 		case 'k':
457 			use_kb();
458 			break;
459 		case 'm':
460 			use_mb();
461 			break;
462 		case 'o':
463 			flag_o = 1;
464 			break;
465 		case 'p':
466 			flag_p = 1;
467 			break;
468 		case 's':
469 			flag_s = 1;
470 			break;
471 		case '?':
472 		default:
473 			usage();
474 		}
475 	}
476 	argc -= optind;
477 	argv += optind;
478 	if (argc != 0)
479 		usage();
480 
481 	i = geom_gettree(&gmp);
482 	if (i != 0)
483 		err(1, "geom_gettree = %d", i);
484 	error = geom_stats_open();
485 	if (error)
486 		err(1, "geom_stats_open()");
487 	sq = geom_stats_snapshot_get();
488 	if (sq == NULL)
489 		err(1, "geom_stats_snapshot()");
490 	if (!flag_b)
491 		setup_interactive();
492 	if (flag_C)
493 		print_head_csv();
494 	geom_stats_snapshot_timestamp(sq, &tq);
495 	for (quit = 0; !quit;) {
496 		loop += 1;
497 		sp = geom_stats_snapshot_get();
498 		if (sp == NULL)
499 			err(1, "geom_stats_snapshot()");
500 		geom_stats_snapshot_timestamp(sp, &tp);
501 		dt = tp.tv_sec - tq.tv_sec;
502 		dt += (tp.tv_nsec - tq.tv_nsec) * 1e-9;
503 		tq = tp;
504 		if (flag_C) { /* set timestamp string */
505 			(void)strftime(ts2, sizeof(ts2),
506 					"%F %T", localtime(&tq.tv_sec));
507 			assert(strlen(ts2) < 50);
508 			(void)snprintf(ts, sizeof(ts),
509 					"%s.%.9ld" ,ts2,tq.tv_nsec);
510 		}
511 		if (loop > 1 && !flag_C)
512 			text_header();
513 
514 		geom_stats_snapshot_reset(sp);
515 		geom_stats_snapshot_reset(sq);
516 		for (;;) {
517 			gsp = geom_stats_snapshot_next(sp);
518 			gsq = geom_stats_snapshot_next(sq);
519 			if (gsp == NULL || gsq == NULL)
520 				break;
521 			if (gsp->id == NULL)
522 				continue;
523 			gid = geom_lookupid(&gmp, gsp->id);
524 			if (gid == NULL) {
525 				geom_deletetree(&gmp);
526 				i = geom_gettree(&gmp);
527 				if (i != 0)
528 					err(1, "geom_gettree = %d", i);
529 				gid = geom_lookupid(&gmp, gsp->id);
530 			}
531 			if (gid == NULL)
532 				continue;
533 			if (gid->lg_what == ISCONSUMER && !flag_c)
534 				continue;
535 			if (flag_p && gid->lg_what == ISPROVIDER &&
536 			   ((struct gprovider *)
537 			    (gid->lg_ptr))->lg_geom->lg_rank != 1)
538 				continue;
539 			/* Do not print past end of window */
540 			if (!flag_b) {
541 				getyx(stdscr, cury, curx);
542 				if (curx > 0)
543 					continue;
544 			}
545 			if ((gid->lg_what == ISPROVIDER
546 			    || gid->lg_what == ISCONSUMER) && f_s[0] != '\0') {
547 				pp = gid->lg_ptr;
548 				if ((regexec(&f_re, pp->lg_name, 0, NULL, 0)
549 				     != 0))
550 				  continue;
551 			}
552 			if (gsp->sequence0 != gsp->sequence1) {
553 				/*
554 				 * it is ok to skip entire line silently
555 				 * for CSV output
556 				 */
557 				if (!flag_C)
558 					printer("*\n");
559 				continue;
560 			}
561 
562 #define CONVENIENCE(oper, kst) \
563 	DSM_TRANSFERS_PER_SECOND_##oper, &kst.transfers_per_second, \
564 	DSM_MB_PER_SECOND_##oper, &kst.mb_per_second, \
565 	DSM_MS_PER_TRANSACTION_##oper, &kst.ms_per_transaction, \
566 	DSM_KB_PER_TRANSFER_##oper, &kst.kb_per_transfer,
567 
568 			devstat_compute_statistics(gsp, gsq, dt,
569 			    DSM_QUEUE_LENGTH, &queue_len,
570 			    DSM_TRANSFERS_PER_SECOND, &xfer_per_sec,
571 
572 			    CONVENIENCE(READ, krd)
573 			    CONVENIENCE(WRITE, kwr)
574 			    CONVENIENCE(FREE, kfree)
575 
576 			    DSM_BUSY_PCT, &busy_pct,
577 
578 			    DSM_TRANSFERS_PER_SECOND_OTHER,
579 				&kother.transfers_per_second,
580 			    DSM_MS_PER_TRANSACTION_OTHER,
581 				&kother.ms_per_transaction,
582 
583 			    DSM_NONE);
584 
585 			*gsq = *gsp;
586 			if (loop == 1)
587 				continue;
588 
589 			if (flag_a && busy_pct < 0.1) {
590 				continue;
591 			}
592 
593 			/* store name for geom device */
594 			if (gid->lg_what == ISPROVIDER) {
595 				pp = gid->lg_ptr;
596 				(void)snprintf(g_name, sizeof(g_name), "%s",
597 						pp->lg_name);
598 			} else if (gid->lg_what == ISCONSUMER) {
599 				cp = gid->lg_ptr;
600 				(void)snprintf(g_name, sizeof(g_name),
601 					"%s/%s/%s",
602 					cp->lg_geom->lg_class->lg_name,
603 					cp->lg_geom->lg_name,
604 					cp->lg_provider->lg_name);
605 			}
606 
607 			if (flag_C) {
608 				printf("%s", ts); /* timestamp */
609 				printf(",%s", g_name); /* print name */
610 				printf(",%ju", (uintmax_t)queue_len);
611 				printf(",%.0f", (double)xfer_per_sec);
612 				print_kstat_csv(&krd, 0);
613 				print_kstat_csv(&kwr, 0);
614 				if (flag_d)
615 					print_kstat_csv(&kfree, 0);
616 				if (flag_o)
617 					print_kstat_csv(&kother, 1);
618 				printf(",%.1lf\n", (double)busy_pct);
619 			} else {
620 				printer(" %4ju", (uintmax_t)queue_len);
621 				printer(" %6.0f", (double)xfer_per_sec);
622 
623 				print_kstat_text(&krd, 0);
624 				print_kstat_text(&kwr, 0);
625 				if (flag_d)
626 					print_kstat_text(&kfree, 0);
627 				if (flag_o)
628 					print_kstat_text(&kother, 1);
629 				if (!flag_b) {
630 					if (busy_pct > HIGH_PCT_BUSY_THRESH)
631 						i = 3;
632 					else if (busy_pct > MEDIUM_PCT_BUSY_THRESH)
633 						i = 2;
634 					else
635 						i = 1;
636 					attron(COLOR_PAIR(i));
637 				}
638 				printer("  %6.1lf", (double)busy_pct);
639 				if (!flag_b)
640 					attroff(COLOR_PAIR(i));
641 
642 				printer(" %s", g_name);
643 				if (!flag_b)
644 					clrtoeol();
645 				printer("\n");
646 			}
647 		}
648 		geom_stats_snapshot_free(sp);
649 		if (flag_b || flag_C) {
650 			if (!flag_B && !flag_C && loop == 2)
651 				break;
652 			if (fflush(stdout) == EOF)
653 				break;
654 			usleep(flag_I);
655 		} else {
656 			getyx(stdscr, cury, curx);
657 			getmaxyx(stdscr, maxy, maxx);
658 			(void)maxx;
659 			clrtobot();
660 			if (maxy - 1 <= cury)
661 				move(maxy - 1, 0);
662 			refresh();
663 
664 			int di = flag_I;
665 			while (di > 0) {
666 				int dii = di;
667 				if (dii > 100000)
668 					dii = 100000;
669 				usleep(dii);
670 				di -= dii;
671 				if (poll_input())
672 					break;
673 			}
674 		}
675 	}
676 	if (!flag_b) {
677 		el_end(el);
678 		endwin();
679 	}
680 	exit(EX_OK);
681 }
682