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 54 static int flag_a, flag_b, flag_B, flag_c, flag_C, flag_d, flag_o, flag_p, 55 flag_s; 56 static 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 67 static void usage(void) __dead2; 68 69 static const char* 70 el_prompt(void) 71 { 72 73 return ("Filter: "); 74 } 75 76 int 77 main(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 if (ld[3] > 1e0) 410 PRINTMSG(",%.1f", (double)ld[3]); 411 else 412 PRINTMSG(",%.3f", (double)ld[3]); 413 PRINTMSG(",%.0f", (double)ld[4]); 414 if (flag_s) 415 PRINTMSG(",%.0f", (double)ld[14]); 416 PRINTMSG(",%.0f", (double)ld[5] * 1024); 417 if (ld[6] > 1e3) 418 PRINTMSG(",%.0f", (double)ld[6]); 419 else if (ld[6] > 1e0) 420 PRINTMSG(",%.1f", (double)ld[6]); 421 else 422 PRINTMSG(",%.3f", (double)ld[6]); 423 424 if (flag_d) { 425 PRINTMSG(",%.0f", (double)ld[8]); 426 if (flag_s) 427 PRINTMSG(",%.0f", 428 (double)ld[15]); 429 PRINTMSG(",%.0f", (double)ld[9] * 1024); 430 if (ld[10] > 1e3) 431 PRINTMSG(",%.0f", 432 (double)ld[10]); 433 else if (ld[10] > 1e0) 434 PRINTMSG(",%.1f", 435 (double)ld[10]); 436 else 437 PRINTMSG(",%.3f", 438 (double)ld[10]); 439 } 440 441 if (flag_o) { 442 PRINTMSG(",%.0f", (double)ld[11]); 443 if (ld[12] > 1e3) 444 PRINTMSG(",%.0f", 445 (double)ld[12]); 446 else if (ld[12] > 1e0) 447 PRINTMSG(",%.1f", 448 (double)ld[12]); 449 else 450 PRINTMSG(",%.3f", 451 (double)ld[12]); 452 } 453 PRINTMSG(",%.1lf", (double)ld[7]); 454 } else { 455 PRINTMSG(" %4ju", (uintmax_t)u64); 456 PRINTMSG(" %6.0f", (double)ld[0]); 457 PRINTMSG(" %6.0f", (double)ld[1]); 458 if (flag_s) 459 PRINTMSG(" %6.0f", (double)ld[13]); 460 PRINTMSG(" %6.0f", (double)ld[2] * 1024); 461 if (ld[3] > 1e3) 462 PRINTMSG(" %6.0f", (double)ld[3]); 463 else if (ld[3] > 1e0) 464 PRINTMSG(" %6.1f", (double)ld[3]); 465 else 466 PRINTMSG(" %6.3f", (double)ld[3]); 467 PRINTMSG(" %6.0f", (double)ld[4]); 468 if (flag_s) 469 PRINTMSG(" %6.0f", (double)ld[14]); 470 PRINTMSG(" %6.0f", (double)ld[5] * 1024); 471 if (ld[6] > 1e3) 472 PRINTMSG(" %6.0f", (double)ld[6]); 473 else if (ld[6] > 1e0) 474 PRINTMSG(" %6.1f", (double)ld[6]); 475 else 476 PRINTMSG(" %6.3f", (double)ld[6]); 477 478 if (flag_d) { 479 PRINTMSG(" %6.0f", (double)ld[8]); 480 if (flag_s) 481 PRINTMSG(" %6.0f", 482 (double)ld[15]); 483 PRINTMSG(" %6.0f", 484 (double)ld[9] * 1024); 485 if (ld[10] > 1e3) 486 PRINTMSG(" %6.0f", 487 (double)ld[10]); 488 else if (ld[10] > 1e0) 489 PRINTMSG(" %6.1f", 490 (double)ld[10]); 491 else 492 PRINTMSG(" %6.3f", 493 (double)ld[10]); 494 } 495 496 if (flag_o) { 497 PRINTMSG(" %6.0f", (double)ld[11]); 498 if (ld[12] > 1e3) 499 PRINTMSG(" %6.0f", 500 (double)ld[12]); 501 else if (ld[12] > 1e0) 502 PRINTMSG(" %6.1f", 503 (double)ld[12]); 504 else 505 PRINTMSG(" %6.3f", 506 (double)ld[12]); 507 } 508 509 if (ld[7] > HIGH_PCT_BUSY_THRESH) 510 i = 3; 511 else if (ld[7] > MEDIUM_PCT_BUSY_THRESH) 512 i = 2; 513 else 514 i = 1; 515 if (!flag_b) 516 attron(COLOR_PAIR(i)); 517 PRINTMSG(" %6.1lf", (double)ld[7]); 518 if (!flag_b) { 519 attroff(COLOR_PAIR(i)); 520 PRINTMSG("|"); 521 } else 522 PRINTMSG(" "); 523 PRINTMSG(" %s", g_name); 524 if (!flag_b) 525 clrtoeol(); 526 } 527 PRINTMSG("\n"); 528 *gsq = *gsp; 529 } 530 geom_stats_snapshot_free(sp); 531 if (flag_b) { 532 /* We loop extra to make sure we get the information. */ 533 if (!loop) 534 break; 535 if (!flag_B) 536 loop = 0; 537 else 538 if (fflush(stdout) == EOF) 539 goto out; 540 usleep(flag_I); 541 continue; 542 } 543 getyx(stdscr, cury, curx); 544 getmaxyx(stdscr, maxy, maxx); 545 clrtobot(); 546 if (maxy - 1 <= cury) 547 move(maxy - 1, 0); 548 refresh(); 549 usleep(flag_I); 550 while((i = getch()) != ERR) { 551 switch (i) { 552 case '>': 553 flag_I *= 2; 554 break; 555 case '<': 556 flag_I /= 2; 557 if (flag_I < 1000) 558 flag_I = 1000; 559 break; 560 case 'c': 561 flag_c = !flag_c; 562 break; 563 case 'f': 564 move(0,0); 565 clrtoeol(); 566 refresh(); 567 line = el_gets(el, &line_len); 568 if (line == NULL) 569 err(1, "el_gets"); 570 if (line_len > 1) 571 history(hist, &hist_ev, H_ENTER, line); 572 strlcpy(tmp_f_s, line, sizeof(f_s)); 573 if ((p = strchr(tmp_f_s, '\n')) != NULL) 574 *p = '\0'; 575 /* 576 * Fix the terminal. We messed up 577 * curses idea of the screen by using 578 * libedit. 579 */ 580 clear(); 581 refresh(); 582 cbreak(); 583 noecho(); 584 nonl(); 585 if (regcomp(&tmp_f_re, tmp_f_s, REG_EXTENDED) 586 != 0) { 587 move(0, 0); 588 printw("Invalid filter"); 589 refresh(); 590 sleep(1); 591 } else { 592 strlcpy(f_s, tmp_f_s, sizeof(f_s)); 593 f_re = tmp_f_re; 594 } 595 break; 596 case 'F': 597 f_s[0] = '\0'; 598 break; 599 case 'q': 600 quit = 1; 601 break; 602 default: 603 break; 604 } 605 } 606 } 607 out: 608 if (!flag_b) { 609 el_end(el); 610 endwin(); 611 } 612 exit(EX_OK); 613 } 614 615 static void 616 usage(void) 617 { 618 fprintf(stderr, "usage: gstat [-abBcCdps] [-f filter] [-I interval]\n"); 619 exit(EX_USAGE); 620 /* NOTREACHED */ 621 } 622