xref: /freebsd/usr.sbin/gstat/gstat.c (revision 783d3ff6d7fae619db8a7990b8a6387de0c677b5)
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
410 					PRINTMSG(",%.1f", (double)ld[3]);
411 				PRINTMSG(",%.0f", (double)ld[4]);
412 				if (flag_s)
413 					PRINTMSG(",%.0f", (double)ld[14]);
414 				PRINTMSG(",%.0f", (double)ld[5] * 1024);
415 				if (ld[6] > 1e3)
416 					PRINTMSG(",%.0f", (double)ld[6]);
417 				else
418 					PRINTMSG(",%.1f", (double)ld[6]);
419 
420 				if (flag_d) {
421 					PRINTMSG(",%.0f", (double)ld[8]);
422 					if (flag_s)
423 						PRINTMSG(",%.0f",
424 								(double)ld[15]);
425 					PRINTMSG(",%.0f", (double)ld[9] * 1024);
426 					if (ld[10] > 1e3)
427 						PRINTMSG(",%.0f",
428 								(double)ld[10]);
429 					else
430 						PRINTMSG(",%.1f",
431 								(double)ld[10]);
432 				}
433 
434 				if (flag_o) {
435 					PRINTMSG(",%.0f", (double)ld[11]);
436 					if (ld[12] > 1e3)
437 						PRINTMSG(",%.0f",
438 								(double)ld[12]);
439 					else
440 						PRINTMSG(",%.1f",
441 								(double)ld[12]);
442 				}
443 				PRINTMSG(",%.1lf", (double)ld[7]);
444 			} else {
445 				PRINTMSG(" %4ju", (uintmax_t)u64);
446 				PRINTMSG(" %6.0f", (double)ld[0]);
447 				PRINTMSG(" %6.0f", (double)ld[1]);
448 				if (flag_s)
449 					PRINTMSG(" %6.0f", (double)ld[13]);
450 				PRINTMSG(" %6.0f", (double)ld[2] * 1024);
451 				if (ld[3] > 1e3)
452 					PRINTMSG(" %6.0f", (double)ld[3]);
453 				else
454 					PRINTMSG(" %6.1f", (double)ld[3]);
455 				PRINTMSG(" %6.0f", (double)ld[4]);
456 				if (flag_s)
457 					PRINTMSG(" %6.0f", (double)ld[14]);
458 				PRINTMSG(" %6.0f", (double)ld[5] * 1024);
459 				if (ld[6] > 1e3)
460 					PRINTMSG(" %6.0f", (double)ld[6]);
461 				else
462 					PRINTMSG(" %6.1f", (double)ld[6]);
463 
464 				if (flag_d) {
465 					PRINTMSG(" %6.0f", (double)ld[8]);
466 					if (flag_s)
467 						PRINTMSG(" %6.0f",
468 								(double)ld[15]);
469 					PRINTMSG(" %6.0f",
470 							(double)ld[9] * 1024);
471 					if (ld[10] > 1e3)
472 						PRINTMSG(" %6.0f",
473 								(double)ld[10]);
474 					else
475 						PRINTMSG(" %6.1f",
476 								(double)ld[10]);
477 				}
478 
479 				if (flag_o) {
480 					PRINTMSG(" %6.0f", (double)ld[11]);
481 					if (ld[12] > 1e3)
482 						PRINTMSG(" %6.0f",
483 								(double)ld[12]);
484 					else
485 						PRINTMSG(" %6.1f",
486 								(double)ld[12]);
487 				}
488 
489 				if (ld[7] > HIGH_PCT_BUSY_THRESH)
490 					i = 3;
491 				else if (ld[7] > MEDIUM_PCT_BUSY_THRESH)
492 					i = 2;
493 				else
494 					i = 1;
495 				if (!flag_b)
496 					attron(COLOR_PAIR(i));
497 				PRINTMSG(" %6.1lf", (double)ld[7]);
498 				if (!flag_b) {
499 					attroff(COLOR_PAIR(i));
500 					PRINTMSG("|");
501 				} else
502 					PRINTMSG(" ");
503 				PRINTMSG(" %s", g_name);
504 				if (!flag_b)
505 					clrtoeol();
506 			}
507 			PRINTMSG("\n");
508 			*gsq = *gsp;
509 		}
510 		geom_stats_snapshot_free(sp);
511 		if (flag_b) {
512 			/* We loop extra to make sure we get the information. */
513 			if (!loop)
514 				break;
515 			if (!flag_B)
516 				loop = 0;
517 			else
518 				if (fflush(stdout) == EOF)
519 					goto out;
520 			usleep(flag_I);
521 			continue;
522 		}
523 		getyx(stdscr, cury, curx);
524 		getmaxyx(stdscr, maxy, maxx);
525 		clrtobot();
526 		if (maxy - 1 <= cury)
527 			move(maxy - 1, 0);
528 		refresh();
529 		usleep(flag_I);
530 		while((i = getch()) != ERR) {
531 			switch (i) {
532 			case '>':
533 				flag_I *= 2;
534 				break;
535 			case '<':
536 				flag_I /= 2;
537 				if (flag_I < 1000)
538 					flag_I = 1000;
539 				break;
540 			case 'c':
541 				flag_c = !flag_c;
542 				break;
543 			case 'f':
544 				move(0,0);
545 				clrtoeol();
546 				refresh();
547 				line = el_gets(el, &line_len);
548 				if (line == NULL)
549 					err(1, "el_gets");
550 				if (line_len > 1)
551 					history(hist, &hist_ev, H_ENTER, line);
552 				strlcpy(tmp_f_s, line, sizeof(f_s));
553 				if ((p = strchr(tmp_f_s, '\n')) != NULL)
554 					*p = '\0';
555 				/*
556 				 * Fix the terminal.  We messed up
557 				 * curses idea of the screen by using
558 				 * libedit.
559 				 */
560 				clear();
561 				refresh();
562 				cbreak();
563 				noecho();
564 				nonl();
565 				if (regcomp(&tmp_f_re, tmp_f_s, REG_EXTENDED)
566 				    != 0) {
567 					move(0, 0);
568 					printw("Invalid filter");
569 					refresh();
570 					sleep(1);
571 				} else {
572 					strlcpy(f_s, tmp_f_s, sizeof(f_s));
573 					f_re = tmp_f_re;
574 				}
575 				break;
576 			case 'F':
577 				f_s[0] = '\0';
578 				break;
579 			case 'q':
580 				quit = 1;
581 				break;
582 			default:
583 				break;
584 			}
585 		}
586 	}
587 out:
588 	if (!flag_b) {
589 		el_end(el);
590 		endwin();
591 	}
592 	exit(EX_OK);
593 }
594 
595 static void
596 usage(void)
597 {
598 	fprintf(stderr, "usage: gstat [-abBcCdps] [-f filter] [-I interval]\n");
599 	exit(EX_USAGE);
600         /* NOTREACHED */
601 }
602