xref: /freebsd/usr.sbin/gstat/gstat.c (revision 7937bfbc0ca53fe7cdd0d54414f9296e273a518e)
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 <curses.h>
39 #include <devstat.h>
40 #include <err.h>
41 #include <errno.h>
42 #include <fcntl.h>
43 #include <histedit.h>
44 #include <libgeom.h>
45 #include <paths.h>
46 #include <regex.h>
47 #include <stdint.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <sysexits.h>
52 #include <unistd.h>
53 
54 static int flag_a, flag_b, flag_B, flag_c, flag_C, flag_d, flag_o, flag_p,
55 	   flag_s;
56 static int flag_I = 1000000;
57 
58 #define HIGH_PCT_BUSY_THRESH 80
59 #define MEDIUM_PCT_BUSY_THRESH 50
60 #define PRINTMSG(...) do {						\
61 		if ((flag_b && !loop) || (flag_B))			\
62 			printf(__VA_ARGS__);				\
63 		else if (!flag_b)					\
64 			printw(__VA_ARGS__);				\
65 	} while(0)
66 
67 static void usage(void) __dead2;
68 
69 static const char*
70 el_prompt(void)
71 {
72 
73 	return ("Filter: ");
74 }
75 
76 int
77 main(int argc, char **argv)
78 {
79 	int error, i, quit;
80 	int curx, cury, maxx, maxy, line_len, loop, max_flen, head_printed;
81 	struct devstat *gsp, *gsq;
82 	void *sp, *sq;
83 	double dt;
84 	struct timespec tp, tq;
85 	struct gmesh gmp;
86 	struct gprovider *pp;
87 	struct gconsumer *cp;
88 	struct gident *gid;
89 	regex_t f_re, tmp_f_re;
90 	short cf, cb;
91 	char *p;
92 	char f_s[100], pf_s[100], tmp_f_s[100];
93 	char ts[100], g_name[4096];
94 	const char *line;
95 	long double ld[16];
96 	uint64_t u64;
97 	EditLine *el;
98 	History *hist;
99 	HistEvent hist_ev;
100 
101 	hist = NULL;
102 	el = NULL;
103 	maxx = -1;
104 	curx = -1;
105 	loop = 1;
106 	/* Turn on batch mode if output is not tty. */
107 	if (!isatty(fileno(stdout)))
108 		flag_b = 1;
109 
110 	f_s[0] = '\0';
111 	while ((i = getopt(argc, argv, "abBdcCf:I:ops")) != -1) {
112 		switch (i) {
113 		case 'a':
114 			flag_a = 1;
115 			break;
116 		case 'b':
117 			flag_b = 1;
118 			break;
119 		case 'B':
120 			flag_B = 1;
121 			flag_b = 1;
122 			break;
123 		case 'c':
124 			flag_c = 1;
125 			break;
126 		case 'C':
127 			flag_C = 1;
128 			/* csv out implies repeating batch mode */
129 			flag_b = 1;
130 			flag_B = 1;
131 			head_printed = 0;
132 			break;
133 		case 'd':
134 			flag_d = 1;
135 			break;
136 		case 'f':
137 			if (strlen(optarg) > sizeof(f_s) - 1)
138 				errx(EX_USAGE, "Filter string too long");
139 			if (regcomp(&f_re, optarg, REG_EXTENDED) != 0)
140 				errx(EX_USAGE,
141 				    "Invalid filter - see re_format(7)");
142 			strlcpy(f_s, optarg, sizeof(f_s));
143 			break;
144 		case 'o':
145 			flag_o = 1;
146 			break;
147 		case 'I':
148 			p = NULL;
149 			i = strtoul(optarg, &p, 0);
150 			if (p == optarg || errno == EINVAL ||
151 			    errno == ERANGE) {
152 				errx(1, "Invalid argument to -I");
153 			} else if (!strcmp(p, "s"))
154 				i *= 1000000;
155 			else if (!strcmp(p, "ms"))
156 				i *= 1000;
157 			else if (!strcmp(p, "us"))
158 				i *= 1;
159 			flag_I = i;
160 			break;
161 		case 'p':
162 			flag_p = 1;
163 			break;
164 		case 's':
165 			flag_s = 1;
166 			break;
167 		case '?':
168 		default:
169 			usage();
170 		}
171 	}
172 	argc -= optind;
173 	argv += optind;
174 	if (argc != 0)
175 		usage();
176 
177 	i = geom_gettree(&gmp);
178 	if (i != 0)
179 		err(1, "geom_gettree = %d", i);
180 	error = geom_stats_open();
181 	if (error)
182 		err(1, "geom_stats_open()");
183 	sq = NULL;
184 	sq = geom_stats_snapshot_get();
185 	if (sq == NULL)
186 		err(1, "geom_stats_snapshot()");
187 	if (!flag_b) {
188 		/* Setup libedit */
189 		hist = history_init();
190 		if (hist == NULL)
191 			err(EX_SOFTWARE, "history_init()");
192 		history(hist, &hist_ev, H_SETSIZE, 100);
193 		el = el_init("gstat", stdin, stdout, stderr);
194 		if (el == NULL)
195 			err(EX_SOFTWARE, "el_init");
196 		el_set(el, EL_EDITOR, "emacs");
197 		el_set(el, EL_SIGNAL, 1);
198 		el_set(el, EL_HIST, history, hist);
199 		el_set(el, EL_PROMPT, el_prompt);
200 		if (f_s[0] != '\0')
201 			history(hist, &hist_ev, H_ENTER, f_s);
202 		/* Setup curses */
203 		initscr();
204 		start_color();
205 		use_default_colors();
206 		pair_content(0, &cf, &cb);
207 		init_pair(1, COLOR_GREEN, cb);
208 		init_pair(2, COLOR_MAGENTA, cb);
209 		init_pair(3, COLOR_RED, cb);
210 		cbreak();
211 		noecho();
212 		nonl();
213 		nodelay(stdscr, 1);
214 		intrflush(stdscr, FALSE);
215 		keypad(stdscr, TRUE);
216 	}
217 	geom_stats_snapshot_timestamp(sq, &tq);
218 	for (quit = 0; !quit;) {
219 		sp = geom_stats_snapshot_get();
220 		if (sp == NULL)
221 			err(1, "geom_stats_snapshot()");
222 		geom_stats_snapshot_timestamp(sp, &tp);
223 		dt = tp.tv_sec - tq.tv_sec;
224 		dt += (tp.tv_nsec - tq.tv_nsec) * 1e-9;
225 		tq = tp;
226 		if (flag_C) { /* set timestamp string */
227 			(void)strftime(ts,sizeof(ts),
228 					"%F %T",localtime(&tq.tv_sec));
229 			(void)snprintf(ts,sizeof(ts),
230 					"%s.%.9ld",ts,tq.tv_nsec);
231 		}
232 
233 		geom_stats_snapshot_reset(sp);
234 		geom_stats_snapshot_reset(sq);
235 		if (!flag_b)
236 			move(0,0);
237 		if (!flag_C)
238 			PRINTMSG("dT: %5.3fs  w: %.3fs", dt,
239 					(float)flag_I / 1000000);
240 		if (!flag_C && f_s[0] != '\0') {
241 			PRINTMSG("  filter: ");
242 			if (!flag_b) {
243 				getyx(stdscr, cury, curx);
244 				getmaxyx(stdscr, maxy, maxx);
245 			}
246 			strlcpy(pf_s, f_s, sizeof(pf_s));
247 			max_flen = maxx - curx - 1;
248 			if ((int)strlen(f_s) > max_flen && max_flen >= 0) {
249 				if (max_flen > 3)
250 					pf_s[max_flen - 3] = '.';
251 				if (max_flen > 2)
252 					pf_s[max_flen - 2] = '.';
253 				if (max_flen > 1)
254 					pf_s[max_flen - 1] = '.';
255 				pf_s[max_flen] = '\0';
256 			}
257 			PRINTMSG("%s", pf_s);
258 		}
259 		if (!flag_C) {
260 			PRINTMSG("\n");
261 			PRINTMSG(" L(q)  ops/s   ");
262 			if (flag_s) {
263 				PRINTMSG(" r/s     kB   kBps   ms/r   ");
264 				PRINTMSG(" w/s     kB   kBps   ms/w   ");
265 			}
266 			else {
267 				PRINTMSG(" r/s   kBps   ms/r   ");
268 				PRINTMSG(" w/s   kBps   ms/w   ");
269 			}
270 			if (flag_d) {
271 				if (flag_s) {
272 					PRINTMSG(" d/s     kB   kBps");
273 					PRINTMSG("   ms/d   ");
274 				} else
275 					PRINTMSG(" d/s   kBps   ms/d   ");
276 			}
277 			if (flag_o)
278 				PRINTMSG(" o/s   ms/o   ");
279 			PRINTMSG("%%busy Name\n");
280 		} else if (flag_C && !head_printed) {
281 			PRINTMSG("timestamp,name,q-depth,total_ops/s");
282 			if (flag_s) {
283 				PRINTMSG(",read/s,read_sz-KiB");
284 				PRINTMSG(",read-KiB/s,ms/read");
285 				PRINTMSG(",write/s,write_sz-KiB");
286 				PRINTMSG(",write-KiB/s,ms/write");
287 			} else {
288 				PRINTMSG(",read/s,read-KiB/s,ms/read");
289 				PRINTMSG(",write/s,write-KiB/s,ms/write");
290 			}
291 			if (flag_d) {
292 				if (flag_s) {
293 					PRINTMSG(",delete/s,delete-sz-KiB");
294 					PRINTMSG(",delete-KiB/s,ms/delete");
295 				} else {
296 					PRINTMSG(",delete/s,delete-KiB/s");
297 					PRINTMSG(",ms/delete");
298 				}
299 			}
300 			if (flag_o)
301 				PRINTMSG(",other/s,ms/other");
302 			PRINTMSG(",%%busy\n");
303 			head_printed = 1;
304 		}
305 		for (;;) {
306 			gsp = geom_stats_snapshot_next(sp);
307 			gsq = geom_stats_snapshot_next(sq);
308 			if (gsp == NULL || gsq == NULL)
309 				break;
310 			if (gsp->id == NULL)
311 				continue;
312 			gid = geom_lookupid(&gmp, gsp->id);
313 			if (gid == NULL) {
314 				geom_deletetree(&gmp);
315 				i = geom_gettree(&gmp);
316 				if (i != 0)
317 					err(1, "geom_gettree = %d", i);
318 				gid = geom_lookupid(&gmp, gsp->id);
319 			}
320 			if (gid == NULL)
321 				continue;
322 			if (gid->lg_what == ISCONSUMER && !flag_c)
323 				continue;
324 			if (flag_p && gid->lg_what == ISPROVIDER &&
325 			   ((struct gprovider *)
326 			    (gid->lg_ptr))->lg_geom->lg_rank != 1)
327 				continue;
328 			/* Do not print past end of window */
329 			if (!flag_b) {
330 				getyx(stdscr, cury, curx);
331 				if (curx > 0)
332 					continue;
333 			}
334 			if ((gid->lg_what == ISPROVIDER
335 			    || gid->lg_what == ISCONSUMER) && f_s[0] != '\0') {
336 				pp = gid->lg_ptr;
337 				if ((regexec(&f_re, pp->lg_name, 0, NULL, 0)
338 				     != 0))
339 				  continue;
340 			}
341 			if (gsp->sequence0 != gsp->sequence1) {
342 				/*
343 				 * it is ok to skip entire line silently
344 				 * for CSV output
345 				 */
346 				if (!flag_C)
347 					PRINTMSG("*\n");
348 				continue;
349 			}
350 			devstat_compute_statistics(gsp, gsq, dt,
351 			    DSM_QUEUE_LENGTH, &u64,
352 			    DSM_TRANSFERS_PER_SECOND, &ld[0],
353 
354 			    DSM_TRANSFERS_PER_SECOND_READ, &ld[1],
355 			    DSM_MB_PER_SECOND_READ, &ld[2],
356 			    DSM_MS_PER_TRANSACTION_READ, &ld[3],
357 
358 			    DSM_TRANSFERS_PER_SECOND_WRITE, &ld[4],
359 			    DSM_MB_PER_SECOND_WRITE, &ld[5],
360 			    DSM_MS_PER_TRANSACTION_WRITE, &ld[6],
361 
362 			    DSM_BUSY_PCT, &ld[7],
363 
364 			    DSM_TRANSFERS_PER_SECOND_FREE, &ld[8],
365 			    DSM_MB_PER_SECOND_FREE, &ld[9],
366 			    DSM_MS_PER_TRANSACTION_FREE, &ld[10],
367 
368 			    DSM_TRANSFERS_PER_SECOND_OTHER, &ld[11],
369 			    DSM_MS_PER_TRANSACTION_OTHER, &ld[12],
370 
371 			    DSM_KB_PER_TRANSFER_READ, &ld[13],
372 			    DSM_KB_PER_TRANSFER_WRITE, &ld[14],
373 			    DSM_KB_PER_TRANSFER_FREE, &ld[15],
374 
375 			    DSM_NONE);
376 
377 			if (flag_a && ld[7] < 0.1) {
378 				*gsq = *gsp;
379 				continue;
380 			}
381 
382 			/* store name for geom device */
383 			if (gid == NULL) {
384 				(void)snprintf(g_name, sizeof(g_name), "??");
385 			} else if (gid->lg_what == ISPROVIDER) {
386 				pp = gid->lg_ptr;
387 				(void)snprintf(g_name, sizeof(g_name), "%s",
388 						pp->lg_name);
389 			} else if (gid->lg_what == ISCONSUMER) {
390 				cp = gid->lg_ptr;
391 				(void)snprintf(g_name, sizeof(g_name),
392 					"%s/%s/%s",
393 					cp->lg_geom->lg_class->lg_name,
394 				   	cp->lg_geom->lg_name,
395 				    	cp->lg_provider->lg_name);
396 			}
397 
398 			if (flag_C) {
399 				PRINTMSG("%s", ts); /* timestamp */
400 				PRINTMSG(",%s", g_name); /* print name */
401 				PRINTMSG(",%ju", (uintmax_t)u64);
402 				PRINTMSG(",%.0f", (double)ld[0]);
403 				PRINTMSG(",%.0f", (double)ld[1]);
404 				if (flag_s)
405 					PRINTMSG(",%.0f", (double)ld[13]);
406 				PRINTMSG(",%.0f", (double)ld[2] * 1024);
407 				if (ld[3] > 1e3)
408 					PRINTMSG(",%.0f", (double)ld[3]);
409 				else if (ld[3] > 1e0)
410 					PRINTMSG(",%.1f", (double)ld[3]);
411 				else
412 					PRINTMSG(",%.3f", (double)ld[3]);
413 				PRINTMSG(",%.0f", (double)ld[4]);
414 				if (flag_s)
415 					PRINTMSG(",%.0f", (double)ld[14]);
416 				PRINTMSG(",%.0f", (double)ld[5] * 1024);
417 				if (ld[6] > 1e3)
418 					PRINTMSG(",%.0f", (double)ld[6]);
419 				else if (ld[6] > 1e0)
420 					PRINTMSG(",%.1f", (double)ld[6]);
421 				else
422 					PRINTMSG(",%.3f", (double)ld[6]);
423 
424 				if (flag_d) {
425 					PRINTMSG(",%.0f", (double)ld[8]);
426 					if (flag_s)
427 						PRINTMSG(",%.0f",
428 								(double)ld[15]);
429 					PRINTMSG(",%.0f", (double)ld[9] * 1024);
430 					if (ld[10] > 1e3)
431 						PRINTMSG(",%.0f",
432 								(double)ld[10]);
433 					else if (ld[10] > 1e0)
434 						PRINTMSG(",%.1f",
435 								(double)ld[10]);
436 					else
437 						PRINTMSG(",%.3f",
438 								(double)ld[10]);
439 				}
440 
441 				if (flag_o) {
442 					PRINTMSG(",%.0f", (double)ld[11]);
443 					if (ld[12] > 1e3)
444 						PRINTMSG(",%.0f",
445 								(double)ld[12]);
446 					else if (ld[12] > 1e0)
447 						PRINTMSG(",%.1f",
448 								(double)ld[12]);
449 					else
450 						PRINTMSG(",%.3f",
451 								(double)ld[12]);
452 				}
453 				PRINTMSG(",%.1lf", (double)ld[7]);
454 			} else {
455 				PRINTMSG(" %4ju", (uintmax_t)u64);
456 				PRINTMSG(" %6.0f", (double)ld[0]);
457 				PRINTMSG(" %6.0f", (double)ld[1]);
458 				if (flag_s)
459 					PRINTMSG(" %6.0f", (double)ld[13]);
460 				PRINTMSG(" %6.0f", (double)ld[2] * 1024);
461 				if (ld[3] > 1e3)
462 					PRINTMSG(" %6.0f", (double)ld[3]);
463 				else if (ld[3] > 1e0)
464 					PRINTMSG(" %6.1f", (double)ld[3]);
465 				else
466 					PRINTMSG(" %6.3f", (double)ld[3]);
467 				PRINTMSG(" %6.0f", (double)ld[4]);
468 				if (flag_s)
469 					PRINTMSG(" %6.0f", (double)ld[14]);
470 				PRINTMSG(" %6.0f", (double)ld[5] * 1024);
471 				if (ld[6] > 1e3)
472 					PRINTMSG(" %6.0f", (double)ld[6]);
473 				else if (ld[6] > 1e0)
474 					PRINTMSG(" %6.1f", (double)ld[6]);
475 				else
476 					PRINTMSG(" %6.3f", (double)ld[6]);
477 
478 				if (flag_d) {
479 					PRINTMSG(" %6.0f", (double)ld[8]);
480 					if (flag_s)
481 						PRINTMSG(" %6.0f",
482 								(double)ld[15]);
483 					PRINTMSG(" %6.0f",
484 							(double)ld[9] * 1024);
485 					if (ld[10] > 1e3)
486 						PRINTMSG(" %6.0f",
487 								(double)ld[10]);
488 					else if (ld[10] > 1e0)
489 						PRINTMSG(" %6.1f",
490 								(double)ld[10]);
491 					else
492 						PRINTMSG(" %6.3f",
493 								(double)ld[10]);
494 				}
495 
496 				if (flag_o) {
497 					PRINTMSG(" %6.0f", (double)ld[11]);
498 					if (ld[12] > 1e3)
499 						PRINTMSG(" %6.0f",
500 								(double)ld[12]);
501 					else if (ld[12] > 1e0)
502 						PRINTMSG(" %6.1f",
503 								(double)ld[12]);
504 					else
505 						PRINTMSG(" %6.3f",
506 								(double)ld[12]);
507 				}
508 
509 				if (ld[7] > HIGH_PCT_BUSY_THRESH)
510 					i = 3;
511 				else if (ld[7] > MEDIUM_PCT_BUSY_THRESH)
512 					i = 2;
513 				else
514 					i = 1;
515 				if (!flag_b)
516 					attron(COLOR_PAIR(i));
517 				PRINTMSG(" %6.1lf", (double)ld[7]);
518 				if (!flag_b) {
519 					attroff(COLOR_PAIR(i));
520 					PRINTMSG("|");
521 				} else
522 					PRINTMSG(" ");
523 				PRINTMSG(" %s", g_name);
524 				if (!flag_b)
525 					clrtoeol();
526 			}
527 			PRINTMSG("\n");
528 			*gsq = *gsp;
529 		}
530 		geom_stats_snapshot_free(sp);
531 		if (flag_b) {
532 			/* We loop extra to make sure we get the information. */
533 			if (!loop)
534 				break;
535 			if (!flag_B)
536 				loop = 0;
537 			else
538 				if (fflush(stdout) == EOF)
539 					goto out;
540 			usleep(flag_I);
541 			continue;
542 		}
543 		getyx(stdscr, cury, curx);
544 		getmaxyx(stdscr, maxy, maxx);
545 		clrtobot();
546 		if (maxy - 1 <= cury)
547 			move(maxy - 1, 0);
548 		refresh();
549 		usleep(flag_I);
550 		while((i = getch()) != ERR) {
551 			switch (i) {
552 			case '>':
553 				flag_I *= 2;
554 				break;
555 			case '<':
556 				flag_I /= 2;
557 				if (flag_I < 1000)
558 					flag_I = 1000;
559 				break;
560 			case 'c':
561 				flag_c = !flag_c;
562 				break;
563 			case 'f':
564 				move(0,0);
565 				clrtoeol();
566 				refresh();
567 				line = el_gets(el, &line_len);
568 				if (line == NULL)
569 					err(1, "el_gets");
570 				if (line_len > 1)
571 					history(hist, &hist_ev, H_ENTER, line);
572 				strlcpy(tmp_f_s, line, sizeof(f_s));
573 				if ((p = strchr(tmp_f_s, '\n')) != NULL)
574 					*p = '\0';
575 				/*
576 				 * Fix the terminal.  We messed up
577 				 * curses idea of the screen by using
578 				 * libedit.
579 				 */
580 				clear();
581 				refresh();
582 				cbreak();
583 				noecho();
584 				nonl();
585 				if (regcomp(&tmp_f_re, tmp_f_s, REG_EXTENDED)
586 				    != 0) {
587 					move(0, 0);
588 					printw("Invalid filter");
589 					refresh();
590 					sleep(1);
591 				} else {
592 					strlcpy(f_s, tmp_f_s, sizeof(f_s));
593 					f_re = tmp_f_re;
594 				}
595 				break;
596 			case 'F':
597 				f_s[0] = '\0';
598 				break;
599 			case 'q':
600 				quit = 1;
601 				break;
602 			default:
603 				break;
604 			}
605 		}
606 	}
607 out:
608 	if (!flag_b) {
609 		el_end(el);
610 		endwin();
611 	}
612 	exit(EX_OK);
613 }
614 
615 static void
616 usage(void)
617 {
618 	fprintf(stderr, "usage: gstat [-abBcCdps] [-f filter] [-I interval]\n");
619 	exit(EX_USAGE);
620         /* NOTREACHED */
621 }
622