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