1/*-
2 * Copyright (c) 2003 Poul-Henning Kamp
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. The names of the authors may not be used to endorse or promote
14 *    products derived from this software without specific prior written
15 *    permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 *
29 * $FreeBSD$
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_c, flag_d, flag_o, flag_p;
55static int flag_I = 1000000;
56
57#define PRINTMSG(...) do {						\
58		if (flag_b && !loop)					\
59			printf(__VA_ARGS__);				\
60		else if (!flag_b)					\
61			printw(__VA_ARGS__);				\
62	} while(0)
63
64static void usage(void);
65
66static const char*
67el_prompt(void)
68{
69
70	return ("Filter: ");
71}
72
73int
74main(int argc, char **argv)
75{
76	int error, i, quit;
77	int curx, cury, maxx, maxy, line_len, loop, max_flen;
78	struct devstat *gsp, *gsq;
79	void *sp, *sq;
80	double dt;
81	struct timespec tp, tq;
82	struct gmesh gmp;
83	struct gprovider *pp;
84	struct gconsumer *cp;
85	struct gident *gid;
86	regex_t f_re, tmp_f_re;
87	short cf, cb;
88	char *p;
89	char f_s[100], pf_s[100], tmp_f_s[100];
90	const char *line;
91	long double ld[13];
92	uint64_t u64;
93	EditLine *el;
94	History *hist;
95	HistEvent hist_ev;
96
97	hist = NULL;
98	el = NULL;
99	maxx = -1;
100	curx = -1;
101	loop = 1;
102	/* Turn on batch mode if output is not tty. */
103	if (!isatty(fileno(stdout)))
104		flag_b = 1;
105
106	f_s[0] = '\0';
107	while ((i = getopt(argc, argv, "abdcf:I:op")) != -1) {
108		switch (i) {
109		case 'a':
110			flag_a = 1;
111			break;
112		case 'b':
113			flag_b = 1;
114			break;
115		case 'c':
116			flag_c = 1;
117			break;
118		case 'd':
119			flag_d = 1;
120			break;
121		case 'f':
122			if (strlen(optarg) > sizeof(f_s) - 1)
123				errx(EX_USAGE, "Filter string too long");
124			if (regcomp(&f_re, optarg, REG_EXTENDED) != 0)
125				errx(EX_USAGE,
126				    "Invalid filter - see re_format(7)");
127			strncpy(f_s, optarg, sizeof(f_s));
128			break;
129		case 'o':
130			flag_o = 1;
131			break;
132		case 'I':
133			p = NULL;
134			i = strtoul(optarg, &p, 0);
135			if (p == optarg || errno == EINVAL ||
136			    errno == ERANGE) {
137				errx(1, "Invalid argument to -I");
138			} else if (!strcmp(p, "s"))
139				i *= 1000000;
140			else if (!strcmp(p, "ms"))
141				i *= 1000;
142			else if (!strcmp(p, "us"))
143				i *= 1;
144			flag_I = i;
145			break;
146		case 'p':
147			flag_p = 1;
148			break;
149		case '?':
150		default:
151			usage();
152		}
153	}
154	argc -= optind;
155	argv += optind;
156	if (argc != 0)
157		usage();
158
159	i = geom_gettree(&gmp);
160	if (i != 0)
161		err(1, "geom_gettree = %d", i);
162	error = geom_stats_open();
163	if (error)
164		err(1, "geom_stats_open()");
165	sq = NULL;
166	sq = geom_stats_snapshot_get();
167	if (sq == NULL)
168		err(1, "geom_stats_snapshot()");
169	if (!flag_b) {
170		/* Setup curses */
171		initscr();
172		start_color();
173		use_default_colors();
174		pair_content(0, &cf, &cb);
175		init_pair(1, COLOR_GREEN, cb);
176		init_pair(2, COLOR_MAGENTA, cb);
177		init_pair(3, COLOR_RED, cb);
178		cbreak();
179		noecho();
180		nonl();
181		nodelay(stdscr, 1);
182		intrflush(stdscr, FALSE);
183		keypad(stdscr, TRUE);
184		/* Setup libedit */
185		hist = history_init();
186		if (hist == NULL)
187			err(EX_SOFTWARE, "history_init()");
188		history(hist, &hist_ev, H_SETSIZE, 100);
189		el = el_init("gstat", stdin, stdout, stderr);
190		if (el == NULL)
191			err(EX_SOFTWARE, "el_init");
192		el_set(el, EL_EDITOR, "emacs");
193		el_set(el, EL_SIGNAL, 1);
194		el_set(el, EL_HIST, history, hist);
195		el_set(el, EL_PROMPT, el_prompt);
196		if (f_s[0] != '\0')
197			history(hist, &hist_ev, H_ENTER, f_s);
198	}
199	geom_stats_snapshot_timestamp(sq, &tq);
200	for (quit = 0; !quit;) {
201		sp = geom_stats_snapshot_get();
202		if (sp == NULL)
203			err(1, "geom_stats_snapshot()");
204		geom_stats_snapshot_timestamp(sp, &tp);
205		dt = tp.tv_sec - tq.tv_sec;
206		dt += (tp.tv_nsec - tq.tv_nsec) * 1e-9;
207		tq = tp;
208
209		geom_stats_snapshot_reset(sp);
210		geom_stats_snapshot_reset(sq);
211		move(0,0);
212		PRINTMSG("dT: %5.3fs  w: %.3fs", dt, (float)flag_I / 1000000);
213		if (f_s[0] != '\0') {
214			PRINTMSG("  filter: ");
215			if (!flag_b) {
216				getyx(stdscr, cury, curx);
217				getmaxyx(stdscr, maxy, maxx);
218			}
219			strncpy(pf_s, f_s, sizeof(pf_s));
220			max_flen = maxx - curx - 1;
221			if ((int)strlen(f_s) > max_flen && max_flen >= 0) {
222				if (max_flen > 3)
223					pf_s[max_flen - 3] = '.';
224				if (max_flen > 2)
225					pf_s[max_flen - 2] = '.';
226				if (max_flen > 1)
227					pf_s[max_flen - 1] = '.';
228				pf_s[max_flen] = '\0';
229			}
230			PRINTMSG("%s", pf_s);
231		}
232		PRINTMSG("\n");
233		PRINTMSG(" L(q)  ops/s   ");
234		PRINTMSG(" r/s   kBps   ms/r   ");
235		PRINTMSG(" w/s   kBps   ms/w   ");
236		if (flag_d)
237			PRINTMSG(" d/s   kBps   ms/d   ");
238		if (flag_o)
239			PRINTMSG(" o/s   ms/o   ");
240		PRINTMSG("%%busy Name\n");
241		for (;;) {
242			gsp = geom_stats_snapshot_next(sp);
243			gsq = geom_stats_snapshot_next(sq);
244			if (gsp == NULL || gsq == NULL)
245				break;
246			if (gsp->id == NULL)
247				continue;
248			gid = geom_lookupid(&gmp, gsp->id);
249			if (gid == NULL) {
250				geom_deletetree(&gmp);
251				i = geom_gettree(&gmp);
252				if (i != 0)
253					err(1, "geom_gettree = %d", i);
254				gid = geom_lookupid(&gmp, gsp->id);
255			}
256			if (gid == NULL)
257				continue;
258			if (gid->lg_what == ISCONSUMER && !flag_c)
259				continue;
260			if (flag_p && gid->lg_what == ISPROVIDER &&
261			   ((struct gprovider *)(gid->lg_ptr))->lg_geom->lg_rank != 1)
262				continue;
263			/* Do not print past end of window */
264			if (!flag_b) {
265				getyx(stdscr, cury, curx);
266				if (curx > 0)
267					continue;
268			}
269			if ((gid->lg_what == ISPROVIDER
270			    || gid->lg_what == ISCONSUMER) && f_s[0] != '\0') {
271				pp = gid->lg_ptr;
272				if ((regexec(&f_re, pp->lg_name, 0, NULL, 0)
273				     != 0))
274				  continue;
275			}
276			if (gsp->sequence0 != gsp->sequence1) {
277				PRINTMSG("*\n");
278				continue;
279			}
280			devstat_compute_statistics(gsp, gsq, dt,
281			    DSM_QUEUE_LENGTH, &u64,
282			    DSM_TRANSFERS_PER_SECOND, &ld[0],
283
284			    DSM_TRANSFERS_PER_SECOND_READ, &ld[1],
285			    DSM_MB_PER_SECOND_READ, &ld[2],
286			    DSM_MS_PER_TRANSACTION_READ, &ld[3],
287
288			    DSM_TRANSFERS_PER_SECOND_WRITE, &ld[4],
289			    DSM_MB_PER_SECOND_WRITE, &ld[5],
290			    DSM_MS_PER_TRANSACTION_WRITE, &ld[6],
291
292			    DSM_BUSY_PCT, &ld[7],
293
294			    DSM_TRANSFERS_PER_SECOND_FREE, &ld[8],
295			    DSM_MB_PER_SECOND_FREE, &ld[9],
296			    DSM_MS_PER_TRANSACTION_FREE, &ld[10],
297
298			    DSM_TRANSFERS_PER_SECOND_OTHER, &ld[11],
299			    DSM_MS_PER_TRANSACTION_OTHER, &ld[12],
300
301			    DSM_NONE);
302
303			if (flag_a && ld[7] < 0.1) {
304				*gsq = *gsp;
305				continue;
306			}
307
308			PRINTMSG(" %4ju", (uintmax_t)u64);
309			PRINTMSG(" %6.0f", (double)ld[0]);
310			PRINTMSG(" %6.0f", (double)ld[1]);
311			PRINTMSG(" %6.0f", (double)ld[2] * 1024);
312			if (ld[3] > 1e3)
313				PRINTMSG(" %6.0f", (double)ld[3]);
314			else
315				PRINTMSG(" %6.1f", (double)ld[3]);
316			PRINTMSG(" %6.0f", (double)ld[4]);
317			PRINTMSG(" %6.0f", (double)ld[5] * 1024);
318			if (ld[6] > 1e3)
319				PRINTMSG(" %6.0f", (double)ld[6]);
320			else
321				PRINTMSG(" %6.1f", (double)ld[6]);
322
323			if (flag_d) {
324				PRINTMSG(" %6.0f", (double)ld[8]);
325				PRINTMSG(" %6.0f", (double)ld[9] * 1024);
326				if (ld[10] > 1e3)
327					PRINTMSG(" %6.0f", (double)ld[10]);
328				else
329					PRINTMSG(" %6.1f", (double)ld[10]);
330			}
331
332			if (flag_o) {
333				PRINTMSG(" %6.0f", (double)ld[11]);
334				if (ld[12] > 1e3)
335					PRINTMSG(" %6.0f", (double)ld[12]);
336				else
337					PRINTMSG(" %6.1f", (double)ld[12]);
338			}
339
340			if (ld[7] > 80)
341				i = 3;
342			else if (ld[7] > 50)
343				i = 2;
344			else
345				i = 1;
346			if (!flag_b)
347				attron(COLOR_PAIR(i));
348			PRINTMSG(" %6.1lf", (double)ld[7]);
349			if (!flag_b) {
350				attroff(COLOR_PAIR(i));
351				PRINTMSG("|");
352			} else
353				PRINTMSG(" ");
354			if (gid == NULL) {
355				PRINTMSG(" ??");
356			} else if (gid->lg_what == ISPROVIDER) {
357				pp = gid->lg_ptr;
358				PRINTMSG(" %s", pp->lg_name);
359			} else if (gid->lg_what == ISCONSUMER) {
360				cp = gid->lg_ptr;
361				PRINTMSG(" %s/%s/%s",
362				    cp->lg_geom->lg_class->lg_name,
363				    cp->lg_geom->lg_name,
364				    cp->lg_provider->lg_name);
365			}
366			if (!flag_b)
367				clrtoeol();
368			PRINTMSG("\n");
369			*gsq = *gsp;
370		}
371		geom_stats_snapshot_free(sp);
372		if (flag_b) {
373			/* We loop extra to make sure we get the information. */
374			if (!loop)
375				break;
376			loop = 0;
377			usleep(flag_I);
378			continue;
379		}
380		getyx(stdscr, cury, curx);
381		getmaxyx(stdscr, maxy, maxx);
382		clrtobot();
383		if (maxy - 1 <= cury)
384			move(maxy - 1, 0);
385		refresh();
386		usleep(flag_I);
387		while((i = getch()) != ERR) {
388			switch (i) {
389			case '>':
390				flag_I *= 2;
391				break;
392			case '<':
393				flag_I /= 2;
394				if (flag_I < 1000)
395					flag_I = 1000;
396				break;
397			case 'c':
398				flag_c = !flag_c;
399				break;
400			case 'f':
401				move(0,0);
402				clrtoeol();
403				refresh();
404				line = el_gets(el, &line_len);
405				if (line == NULL)
406					err(1, "el_gets");
407				if (line_len > 1)
408					history(hist, &hist_ev, H_ENTER, line);
409				strncpy(tmp_f_s, line, sizeof(f_s));
410				if ((p = strchr(tmp_f_s, '\n')) != NULL)
411					*p = '\0';
412				/*
413				 * We have to clear since we messed up
414				 * curses idea of the screen by using
415				 * libedit.
416				 */
417				clear();
418				refresh();
419				if (regcomp(&tmp_f_re, tmp_f_s, REG_EXTENDED)
420				    != 0) {
421					move(0, 0);
422					printw("Invalid filter");
423					refresh();
424					sleep(1);
425				} else {
426					strncpy(f_s, tmp_f_s, sizeof(f_s));
427					f_re = tmp_f_re;
428				}
429				break;
430			case 'F':
431				f_s[0] = '\0';
432				break;
433			case 'q':
434				quit = 1;
435				break;
436			default:
437				break;
438			}
439		}
440	}
441
442	if (!flag_b) {
443		endwin();
444		el_end(el);
445	}
446	exit(EX_OK);
447}
448
449static void
450usage(void)
451{
452	fprintf(stderr, "usage: gstat [-abcdp] [-f filter] [-I interval]\n");
453	exit(EX_USAGE);
454        /* NOTREACHED */
455}
456