xref: /freebsd/usr.sbin/gstat/gstat.c (revision eb69d1f144a6fcc765d1b9d44a5ae8082353e70b)
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_d, flag_o, flag_p, flag_s;
57 static int flag_I = 1000000;
58 
59 #define PRINTMSG(...) do {						\
60 		if ((flag_b && !loop) || (flag_B))			\
61 			printf(__VA_ARGS__);				\
62 		else if (!flag_b)					\
63 			printw(__VA_ARGS__);				\
64 	} while(0)
65 
66 static void usage(void);
67 
68 static const char*
69 el_prompt(void)
70 {
71 
72 	return ("Filter: ");
73 }
74 
75 int
76 main(int argc, char **argv)
77 {
78 	int error, i, quit;
79 	int curx, cury, maxx, maxy, line_len, loop, max_flen;
80 	struct devstat *gsp, *gsq;
81 	void *sp, *sq;
82 	double dt;
83 	struct timespec tp, tq;
84 	struct gmesh gmp;
85 	struct gprovider *pp;
86 	struct gconsumer *cp;
87 	struct gident *gid;
88 	regex_t f_re, tmp_f_re;
89 	short cf, cb;
90 	char *p;
91 	char f_s[100], pf_s[100], tmp_f_s[100];
92 	const char *line;
93 	long double ld[16];
94 	uint64_t u64;
95 	EditLine *el;
96 	History *hist;
97 	HistEvent hist_ev;
98 
99 	hist = NULL;
100 	el = NULL;
101 	maxx = -1;
102 	curx = -1;
103 	loop = 1;
104 	/* Turn on batch mode if output is not tty. */
105 	if (!isatty(fileno(stdout)))
106 		flag_b = 1;
107 
108 	f_s[0] = '\0';
109 	while ((i = getopt(argc, argv, "abBdcf:I:ops")) != -1) {
110 		switch (i) {
111 		case 'a':
112 			flag_a = 1;
113 			break;
114 		case 'b':
115 			flag_b = 1;
116 			break;
117 		case 'B':
118 			flag_B = 1;
119 			flag_b = 1;
120 			break;
121 		case 'c':
122 			flag_c = 1;
123 			break;
124 		case 'd':
125 			flag_d = 1;
126 			break;
127 		case 'f':
128 			if (strlen(optarg) > sizeof(f_s) - 1)
129 				errx(EX_USAGE, "Filter string too long");
130 			if (regcomp(&f_re, optarg, REG_EXTENDED) != 0)
131 				errx(EX_USAGE,
132 				    "Invalid filter - see re_format(7)");
133 			strlcpy(f_s, optarg, sizeof(f_s));
134 			break;
135 		case 'o':
136 			flag_o = 1;
137 			break;
138 		case 'I':
139 			p = NULL;
140 			i = strtoul(optarg, &p, 0);
141 			if (p == optarg || errno == EINVAL ||
142 			    errno == ERANGE) {
143 				errx(1, "Invalid argument to -I");
144 			} else if (!strcmp(p, "s"))
145 				i *= 1000000;
146 			else if (!strcmp(p, "ms"))
147 				i *= 1000;
148 			else if (!strcmp(p, "us"))
149 				i *= 1;
150 			flag_I = i;
151 			break;
152 		case 'p':
153 			flag_p = 1;
154 			break;
155 		case 's':
156 			flag_s = 1;
157 			break;
158 		case '?':
159 		default:
160 			usage();
161 		}
162 	}
163 	argc -= optind;
164 	argv += optind;
165 	if (argc != 0)
166 		usage();
167 
168 	i = geom_gettree(&gmp);
169 	if (i != 0)
170 		err(1, "geom_gettree = %d", i);
171 	error = geom_stats_open();
172 	if (error)
173 		err(1, "geom_stats_open()");
174 	sq = NULL;
175 	sq = geom_stats_snapshot_get();
176 	if (sq == NULL)
177 		err(1, "geom_stats_snapshot()");
178 	if (!flag_b) {
179 		/* Setup libedit */
180 		hist = history_init();
181 		if (hist == NULL)
182 			err(EX_SOFTWARE, "history_init()");
183 		history(hist, &hist_ev, H_SETSIZE, 100);
184 		el = el_init("gstat", stdin, stdout, stderr);
185 		if (el == NULL)
186 			err(EX_SOFTWARE, "el_init");
187 		el_set(el, EL_EDITOR, "emacs");
188 		el_set(el, EL_SIGNAL, 1);
189 		el_set(el, EL_HIST, history, hist);
190 		el_set(el, EL_PROMPT, el_prompt);
191 		if (f_s[0] != '\0')
192 			history(hist, &hist_ev, H_ENTER, f_s);
193 		/* Setup curses */
194 		initscr();
195 		start_color();
196 		use_default_colors();
197 		pair_content(0, &cf, &cb);
198 		init_pair(1, COLOR_GREEN, cb);
199 		init_pair(2, COLOR_MAGENTA, cb);
200 		init_pair(3, COLOR_RED, cb);
201 		cbreak();
202 		noecho();
203 		nonl();
204 		nodelay(stdscr, 1);
205 		intrflush(stdscr, FALSE);
206 		keypad(stdscr, TRUE);
207 	}
208 	geom_stats_snapshot_timestamp(sq, &tq);
209 	for (quit = 0; !quit;) {
210 		sp = geom_stats_snapshot_get();
211 		if (sp == NULL)
212 			err(1, "geom_stats_snapshot()");
213 		geom_stats_snapshot_timestamp(sp, &tp);
214 		dt = tp.tv_sec - tq.tv_sec;
215 		dt += (tp.tv_nsec - tq.tv_nsec) * 1e-9;
216 		tq = tp;
217 
218 		geom_stats_snapshot_reset(sp);
219 		geom_stats_snapshot_reset(sq);
220 		if (!flag_b)
221 			move(0,0);
222 		PRINTMSG("dT: %5.3fs  w: %.3fs", dt, (float)flag_I / 1000000);
223 		if (f_s[0] != '\0') {
224 			PRINTMSG("  filter: ");
225 			if (!flag_b) {
226 				getyx(stdscr, cury, curx);
227 				getmaxyx(stdscr, maxy, maxx);
228 			}
229 			strlcpy(pf_s, f_s, sizeof(pf_s));
230 			max_flen = maxx - curx - 1;
231 			if ((int)strlen(f_s) > max_flen && max_flen >= 0) {
232 				if (max_flen > 3)
233 					pf_s[max_flen - 3] = '.';
234 				if (max_flen > 2)
235 					pf_s[max_flen - 2] = '.';
236 				if (max_flen > 1)
237 					pf_s[max_flen - 1] = '.';
238 				pf_s[max_flen] = '\0';
239 			}
240 			PRINTMSG("%s", pf_s);
241 		}
242 		PRINTMSG("\n");
243 		PRINTMSG(" L(q)  ops/s   ");
244 		if (flag_s) {
245 			PRINTMSG(" r/s     kB   kBps   ms/r   ");
246 			PRINTMSG(" w/s     kB   kBps   ms/w   ");
247 		}
248 		else {
249 			PRINTMSG(" r/s   kBps   ms/r   ");
250 			PRINTMSG(" w/s   kBps   ms/w   ");
251 		}
252 		if (flag_d) {
253 			if (flag_s)
254 				PRINTMSG(" d/s     kB   kBps   ms/d   ");
255 			else
256 				PRINTMSG(" d/s   kBps   ms/d   ");
257 		}
258 		if (flag_o)
259 			PRINTMSG(" o/s   ms/o   ");
260 		PRINTMSG("%%busy Name\n");
261 		for (;;) {
262 			gsp = geom_stats_snapshot_next(sp);
263 			gsq = geom_stats_snapshot_next(sq);
264 			if (gsp == NULL || gsq == NULL)
265 				break;
266 			if (gsp->id == NULL)
267 				continue;
268 			gid = geom_lookupid(&gmp, gsp->id);
269 			if (gid == NULL) {
270 				geom_deletetree(&gmp);
271 				i = geom_gettree(&gmp);
272 				if (i != 0)
273 					err(1, "geom_gettree = %d", i);
274 				gid = geom_lookupid(&gmp, gsp->id);
275 			}
276 			if (gid == NULL)
277 				continue;
278 			if (gid->lg_what == ISCONSUMER && !flag_c)
279 				continue;
280 			if (flag_p && gid->lg_what == ISPROVIDER &&
281 			   ((struct gprovider *)(gid->lg_ptr))->lg_geom->lg_rank != 1)
282 				continue;
283 			/* Do not print past end of window */
284 			if (!flag_b) {
285 				getyx(stdscr, cury, curx);
286 				if (curx > 0)
287 					continue;
288 			}
289 			if ((gid->lg_what == ISPROVIDER
290 			    || gid->lg_what == ISCONSUMER) && f_s[0] != '\0') {
291 				pp = gid->lg_ptr;
292 				if ((regexec(&f_re, pp->lg_name, 0, NULL, 0)
293 				     != 0))
294 				  continue;
295 			}
296 			if (gsp->sequence0 != gsp->sequence1) {
297 				PRINTMSG("*\n");
298 				continue;
299 			}
300 			devstat_compute_statistics(gsp, gsq, dt,
301 			    DSM_QUEUE_LENGTH, &u64,
302 			    DSM_TRANSFERS_PER_SECOND, &ld[0],
303 
304 			    DSM_TRANSFERS_PER_SECOND_READ, &ld[1],
305 			    DSM_MB_PER_SECOND_READ, &ld[2],
306 			    DSM_MS_PER_TRANSACTION_READ, &ld[3],
307 
308 			    DSM_TRANSFERS_PER_SECOND_WRITE, &ld[4],
309 			    DSM_MB_PER_SECOND_WRITE, &ld[5],
310 			    DSM_MS_PER_TRANSACTION_WRITE, &ld[6],
311 
312 			    DSM_BUSY_PCT, &ld[7],
313 
314 			    DSM_TRANSFERS_PER_SECOND_FREE, &ld[8],
315 			    DSM_MB_PER_SECOND_FREE, &ld[9],
316 			    DSM_MS_PER_TRANSACTION_FREE, &ld[10],
317 
318 			    DSM_TRANSFERS_PER_SECOND_OTHER, &ld[11],
319 			    DSM_MS_PER_TRANSACTION_OTHER, &ld[12],
320 
321 			    DSM_KB_PER_TRANSFER_READ, &ld[13],
322 			    DSM_KB_PER_TRANSFER_WRITE, &ld[14],
323 			    DSM_KB_PER_TRANSFER_FREE, &ld[15],
324 
325 			    DSM_NONE);
326 
327 			if (flag_a && ld[7] < 0.1) {
328 				*gsq = *gsp;
329 				continue;
330 			}
331 
332 			PRINTMSG(" %4ju", (uintmax_t)u64);
333 			PRINTMSG(" %6.0f", (double)ld[0]);
334 			PRINTMSG(" %6.0f", (double)ld[1]);
335 			if (flag_s)
336 				PRINTMSG(" %6.0f", (double)ld[13]);
337 			PRINTMSG(" %6.0f", (double)ld[2] * 1024);
338 			if (ld[3] > 1e3)
339 				PRINTMSG(" %6.0f", (double)ld[3]);
340 			else
341 				PRINTMSG(" %6.1f", (double)ld[3]);
342 			PRINTMSG(" %6.0f", (double)ld[4]);
343 			if (flag_s)
344 				PRINTMSG(" %6.0f", (double)ld[14]);
345 			PRINTMSG(" %6.0f", (double)ld[5] * 1024);
346 			if (ld[6] > 1e3)
347 				PRINTMSG(" %6.0f", (double)ld[6]);
348 			else
349 				PRINTMSG(" %6.1f", (double)ld[6]);
350 
351 			if (flag_d) {
352 				PRINTMSG(" %6.0f", (double)ld[8]);
353 				if (flag_s)
354 					PRINTMSG(" %6.0f", (double)ld[15]);
355 				PRINTMSG(" %6.0f", (double)ld[9] * 1024);
356 				if (ld[10] > 1e3)
357 					PRINTMSG(" %6.0f", (double)ld[10]);
358 				else
359 					PRINTMSG(" %6.1f", (double)ld[10]);
360 			}
361 
362 			if (flag_o) {
363 				PRINTMSG(" %6.0f", (double)ld[11]);
364 				if (ld[12] > 1e3)
365 					PRINTMSG(" %6.0f", (double)ld[12]);
366 				else
367 					PRINTMSG(" %6.1f", (double)ld[12]);
368 			}
369 
370 			if (ld[7] > 80)
371 				i = 3;
372 			else if (ld[7] > 50)
373 				i = 2;
374 			else
375 				i = 1;
376 			if (!flag_b)
377 				attron(COLOR_PAIR(i));
378 			PRINTMSG(" %6.1lf", (double)ld[7]);
379 			if (!flag_b) {
380 				attroff(COLOR_PAIR(i));
381 				PRINTMSG("|");
382 			} else
383 				PRINTMSG(" ");
384 			if (gid == NULL) {
385 				PRINTMSG(" ??");
386 			} else if (gid->lg_what == ISPROVIDER) {
387 				pp = gid->lg_ptr;
388 				PRINTMSG(" %s", pp->lg_name);
389 			} else if (gid->lg_what == ISCONSUMER) {
390 				cp = gid->lg_ptr;
391 				PRINTMSG(" %s/%s/%s",
392 				    cp->lg_geom->lg_class->lg_name,
393 				    cp->lg_geom->lg_name,
394 				    cp->lg_provider->lg_name);
395 			}
396 			if (!flag_b)
397 				clrtoeol();
398 			PRINTMSG("\n");
399 			*gsq = *gsp;
400 		}
401 		geom_stats_snapshot_free(sp);
402 		if (flag_b) {
403 			/* We loop extra to make sure we get the information. */
404 			if (!loop)
405 				break;
406 			if (!flag_B)
407 				loop = 0;
408 			else
409 				fflush(stdout);
410 			usleep(flag_I);
411 			continue;
412 		}
413 		getyx(stdscr, cury, curx);
414 		getmaxyx(stdscr, maxy, maxx);
415 		clrtobot();
416 		if (maxy - 1 <= cury)
417 			move(maxy - 1, 0);
418 		refresh();
419 		usleep(flag_I);
420 		while((i = getch()) != ERR) {
421 			switch (i) {
422 			case '>':
423 				flag_I *= 2;
424 				break;
425 			case '<':
426 				flag_I /= 2;
427 				if (flag_I < 1000)
428 					flag_I = 1000;
429 				break;
430 			case 'c':
431 				flag_c = !flag_c;
432 				break;
433 			case 'f':
434 				move(0,0);
435 				clrtoeol();
436 				refresh();
437 				line = el_gets(el, &line_len);
438 				if (line == NULL)
439 					err(1, "el_gets");
440 				if (line_len > 1)
441 					history(hist, &hist_ev, H_ENTER, line);
442 				strlcpy(tmp_f_s, line, sizeof(f_s));
443 				if ((p = strchr(tmp_f_s, '\n')) != NULL)
444 					*p = '\0';
445 				/*
446 				 * Fix the terminal.  We messed up
447 				 * curses idea of the screen by using
448 				 * libedit.
449 				 */
450 				clear();
451 				refresh();
452 				cbreak();
453 				noecho();
454 				nonl();
455 				if (regcomp(&tmp_f_re, tmp_f_s, REG_EXTENDED)
456 				    != 0) {
457 					move(0, 0);
458 					printw("Invalid filter");
459 					refresh();
460 					sleep(1);
461 				} else {
462 					strlcpy(f_s, tmp_f_s, sizeof(f_s));
463 					f_re = tmp_f_re;
464 				}
465 				break;
466 			case 'F':
467 				f_s[0] = '\0';
468 				break;
469 			case 'q':
470 				quit = 1;
471 				break;
472 			default:
473 				break;
474 			}
475 		}
476 	}
477 
478 	if (!flag_b) {
479 		el_end(el);
480 		endwin();
481 	}
482 	exit(EX_OK);
483 }
484 
485 static void
486 usage(void)
487 {
488 	fprintf(stderr, "usage: gstat [-abBcdps] [-f filter] [-I interval]\n");
489 	exit(EX_USAGE);
490         /* NOTREACHED */
491 }
492