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