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 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 93 use_kb(void) 94 { 95 byte_scale = 1024; 96 byte_unit = "k"; 97 } 98 99 static void 100 use_mb(void) 101 { 102 byte_scale = 1; 103 byte_unit = "M"; 104 } 105 106 static void 107 use_gb(void) 108 { 109 byte_scale = 1.0/1024; 110 byte_unit = "G"; 111 } 112 113 static void 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 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 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 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 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 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* 242 el_prompt(void) 243 { 244 245 return ("Filter: "); 246 } 247 248 static 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 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 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