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
54static int flag_a, flag_b, flag_B, flag_c, flag_C, flag_d, flag_o, flag_p,
55	   flag_s;
56static 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
67static void usage(void) __dead2;
68
69static const char*
70el_prompt(void)
71{
72
73	return ("Filter: ");
74}
75
76int
77main(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	}
587out:
588	if (!flag_b) {
589		el_end(el);
590		endwin();
591	}
592	exit(EX_OK);
593}
594
595static void
596usage(void)
597{
598	fprintf(stderr, "usage: gstat [-abBcCdps] [-f filter] [-I interval]\n");
599	exit(EX_USAGE);
600        /* NOTREACHED */
601}
602