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