1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2010 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #include <stdio.h> 27 #include <kstat.h> 28 #include <stdlib.h> 29 #include <string.h> 30 #include <strings.h> 31 #include <errno.h> 32 #include <limits.h> 33 #include <sys/types.h> 34 #include <time.h> 35 #include <sys/time.h> 36 #include <sys/uio.h> 37 #include <sys/vnode.h> 38 #include <sys/vfs.h> 39 #include <sys/statvfs.h> 40 #include <sys/fstyp.h> 41 #include <sys/fsid.h> 42 #include <sys/mnttab.h> 43 #include <values.h> 44 #include <poll.h> 45 #include <ctype.h> 46 #include <libintl.h> 47 #include <locale.h> 48 #include <signal.h> 49 50 #include "statcommon.h" 51 52 /* 53 * For now, parsable output is turned off. Once we gather feedback and 54 * stablize the output format, we'll turn it back on. This prevents 55 * the situation where users build tools which depend on a specific 56 * format before we declare the output stable. 57 */ 58 #define PARSABLE_OUTPUT 0 59 60 #if PARSABLE_OUTPUT 61 #define OPTIONS "FPT:afginv" 62 #else 63 #define OPTIONS "FT:afginv" 64 #endif 65 66 /* Time stamp values */ 67 #define NODATE 0 /* Default: No time stamp */ 68 #define DDATE 1 /* Standard date format */ 69 #define UDATE 2 /* Internal representation of Unix time */ 70 71 #define RETRY_DELAY 250 /* Timeout for poll() */ 72 #define HEADERLINES 12 /* Number of lines between display headers */ 73 74 #define LBUFSZ 64 /* Generic size for local buffer */ 75 76 /* 77 * The following are used for the nicenum() function 78 */ 79 #define KILO_VAL 1024 80 #define ONE_INDEX 3 81 82 #define NENTITY_INIT 1 /* Initial number of entities to allocate */ 83 84 /* 85 * We need to have a mechanism for an old/previous and new/current vopstat 86 * structure. We only need two per entity and we can swap between them. 87 */ 88 #define VS_SIZE 2 /* Size of vopstat array */ 89 #define CUR_INDEX (vs_i) 90 #define PREV_INDEX ((vs_i == 0) ? 1 : 0) /* Opposite of CUR_INDEX */ 91 #define BUMP_INDEX() vs_i = ((vs_i == 0) ? 1 : 0) 92 93 /* 94 * An "entity" is anything we're collecting statistics on, it could 95 * be a mountpoint or an FS-type. 96 * e_name is the name of the entity (e.g. mount point or FS-type) 97 * e_ksname is the name of the associated kstat 98 * e_vs is an array of vopstats. This is used to keep track of "previous" 99 * and "current" vopstats. 100 */ 101 typedef struct entity { 102 char *e_name; /* name of entity */ 103 vopstats_t *e_vs; /* Array of vopstats */ 104 ulong_t e_fsid; /* fsid for ENTYPE_MNTPT only */ 105 int e_type; /* type of entity */ 106 char e_ksname[KSTAT_STRLEN]; /* kstat name */ 107 } entity_t; 108 109 /* Types of entities (e_type) */ 110 #define ENTYPE_UNKNOWN 0 /* UNKNOWN must be zero since we calloc() */ 111 #define ENTYPE_FSTYPE 1 112 #define ENTYPE_MNTPT 2 113 114 /* If more sub-one units are added, make sure to adjust ONE_INDEX above */ 115 static char units[] = "num KMGTPE"; 116 117 char *cmdname; /* name of this command */ 118 int caught_cont = 0; /* have caught a SIGCONT */ 119 120 static uint_t timestamp_fmt = NODATE; /* print timestamp with stats */ 121 122 static int vs_i = 0; /* Index of current vs[] slot */ 123 124 static void 125 usage() 126 { 127 (void) fprintf(stderr, gettext( 128 "Usage: %s [-a|f|i|n|v] [-T d|u] {-F | {fstype | fspath}...} " 129 "[interval [count]]\n"), cmdname); 130 exit(2); 131 } 132 133 /* 134 * Given a 64-bit number and a starting unit (e.g., n - nanoseconds), 135 * convert the number to a 5-character representation including any 136 * decimal point and single-character unit. Put that representation 137 * into the array "buf" (which had better be big enough). 138 */ 139 char * 140 nicenum(uint64_t num, char unit, char *buf) 141 { 142 uint64_t n = num; 143 int unit_index; 144 int index; 145 char u; 146 147 /* If the user passed in a NUL/zero unit, use the blank value for 1 */ 148 if (unit == '\0') 149 unit = ' '; 150 151 unit_index = 0; 152 while (units[unit_index] != unit) { 153 unit_index++; 154 if (unit_index > sizeof (units) - 1) { 155 (void) sprintf(buf, "??"); 156 return (buf); 157 } 158 } 159 160 index = 0; 161 while (n >= KILO_VAL) { 162 n = (n + (KILO_VAL / 2)) / KILO_VAL; /* Round up or down */ 163 index++; 164 unit_index++; 165 } 166 167 if (unit_index >= sizeof (units) - 1) { 168 (void) sprintf(buf, "??"); 169 return (buf); 170 } 171 172 u = units[unit_index]; 173 174 if (unit_index == ONE_INDEX) { 175 (void) sprintf(buf, "%llu", (u_longlong_t)n); 176 } else if (n < 10 && (num & (num - 1)) != 0) { 177 (void) sprintf(buf, "%.2f%c", 178 (double)num / (1ULL << 10 * index), u); 179 } else if (n < 100 && (num & (num - 1)) != 0) { 180 (void) sprintf(buf, "%.1f%c", 181 (double)num / (1ULL << 10 * index), u); 182 } else { 183 (void) sprintf(buf, "%llu%c", (u_longlong_t)n, u); 184 } 185 186 return (buf); 187 } 188 189 190 #define RAWVAL(ptr, member) ((ptr)->member.value.ui64) 191 #define DELTA(member) \ 192 (newvsp->member.value.ui64 - (oldvsp ? oldvsp->member.value.ui64 : 0)) 193 194 #define PRINTSTAT(isnice, nicestring, rawstring, rawval, unit, buf) \ 195 (isnice) ? \ 196 (void) printf((nicestring), nicenum(rawval, unit, buf)) \ 197 : \ 198 (void) printf((rawstring), (rawval)) 199 200 /* Values for display flag */ 201 #define DISP_HEADER 0x1 202 #define DISP_RAW 0x2 203 204 /* 205 * The policy for dealing with multiple flags is dealt with here. 206 * Currently, if we are displaying raw output, then don't allow 207 * headers to be printed. 208 */ 209 int 210 dispflag_policy(int printhdr, int dispflag) 211 { 212 /* If we're not displaying raw output, then allow headers to print */ 213 if ((dispflag & DISP_RAW) == 0) { 214 if (printhdr) { 215 dispflag |= DISP_HEADER; 216 } 217 } 218 219 return (dispflag); 220 } 221 222 static void 223 dflt_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag) 224 { 225 int niceflag = ((dispflag & DISP_RAW) == 0); 226 longlong_t nnewfile; 227 longlong_t nnamerm; 228 longlong_t nnamechg; 229 longlong_t nattrret; 230 longlong_t nattrchg; 231 longlong_t nlookup; 232 longlong_t nreaddir; 233 longlong_t ndataread; 234 longlong_t ndatawrite; 235 longlong_t readthruput; 236 longlong_t writethruput; 237 char buf[LBUFSZ]; 238 239 nnewfile = DELTA(ncreate) + DELTA(nmkdir) + DELTA(nsymlink); 240 nnamerm = DELTA(nremove) + DELTA(nrmdir); 241 nnamechg = DELTA(nrename) + DELTA(nlink) + DELTA(nsymlink); 242 nattrret = DELTA(ngetattr) + DELTA(naccess) + 243 DELTA(ngetsecattr) + DELTA(nfid); 244 nattrchg = DELTA(nsetattr) + DELTA(nsetsecattr) + DELTA(nspace); 245 nlookup = DELTA(nlookup); 246 nreaddir = DELTA(nreaddir); 247 ndataread = DELTA(nread); 248 ndatawrite = DELTA(nwrite); 249 readthruput = DELTA(read_bytes); 250 writethruput = DELTA(write_bytes); 251 252 if (dispflag & DISP_HEADER) { 253 (void) printf( 254 " new name name attr attr lookup rddir read read write write\n" 255 " file remov chng get set ops ops ops bytes ops bytes\n"); 256 } 257 258 PRINTSTAT(niceflag, "%5s ", "%lld:", nnewfile, ' ', buf); 259 PRINTSTAT(niceflag, "%5s ", "%lld:", nnamerm, ' ', buf); 260 PRINTSTAT(niceflag, "%5s ", "%lld:", nnamechg, ' ', buf); 261 PRINTSTAT(niceflag, "%5s ", "%lld:", nattrret, ' ', buf); 262 PRINTSTAT(niceflag, "%5s ", "%lld:", nattrchg, ' ', buf); 263 PRINTSTAT(niceflag, " %5s ", "%lld:", nlookup, ' ', buf); 264 PRINTSTAT(niceflag, "%5s ", "%lld:", nreaddir, ' ', buf); 265 PRINTSTAT(niceflag, "%5s ", "%lld:", ndataread, ' ', buf); 266 PRINTSTAT(niceflag, "%5s ", "%lld:", readthruput, ' ', buf); 267 PRINTSTAT(niceflag, "%5s ", "%lld:", ndatawrite, ' ', buf); 268 PRINTSTAT(niceflag, "%5s ", "%lld:", writethruput, ' ', buf); 269 (void) printf("%s\n", name); 270 } 271 272 static void 273 io_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag) 274 { 275 int niceflag = ((dispflag & DISP_RAW) == 0); 276 char buf[LBUFSZ]; 277 278 if (dispflag & DISP_HEADER) { 279 (void) printf( 280 " read read write write rddir rddir rwlock rwulock\n" 281 " ops bytes ops bytes ops bytes ops ops\n"); 282 } 283 284 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nread), ' ', buf); 285 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(read_bytes), ' ', buf); 286 287 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nwrite), ' ', buf); 288 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(write_bytes), ' ', buf); 289 290 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nreaddir), ' ', buf); 291 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(readdir_bytes), ' ', buf); 292 293 PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(nrwlock), ' ', buf); 294 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nrwunlock), ' ', buf); 295 296 (void) printf("%s\n", name); 297 } 298 299 static void 300 vm_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag) 301 { 302 int niceflag = ((dispflag & DISP_RAW) == 0); 303 char buf[LBUFSZ]; 304 305 if (dispflag & DISP_HEADER) { 306 (void) printf(" map addmap delmap getpag putpag pagio\n"); 307 } 308 309 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nmap), ' ', buf); 310 PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(naddmap), ' ', buf); 311 PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(ndelmap), ' ', buf); 312 PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(ngetpage), ' ', buf); 313 PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(nputpage), ' ', buf); 314 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(npageio), ' ', buf); 315 (void) printf("%s\n", name); 316 } 317 318 static void 319 attr_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag) 320 { 321 int niceflag = ((dispflag & DISP_RAW) == 0); 322 char buf[LBUFSZ]; 323 324 if (dispflag & DISP_HEADER) { 325 (void) printf("getattr setattr getsec setsec\n"); 326 } 327 328 PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(ngetattr), ' ', buf); 329 PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(nsetattr), ' ', buf); 330 PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(ngetsecattr), ' ', buf); 331 PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(nsetsecattr), ' ', buf); 332 333 (void) printf("%s\n", name); 334 } 335 336 static void 337 naming_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag) 338 { 339 int niceflag = ((dispflag & DISP_RAW) == 0); 340 char buf[LBUFSZ]; 341 342 if (dispflag & DISP_HEADER) { 343 (void) printf( 344 "lookup creat remov link renam mkdir rmdir rddir symlnk rdlnk\n"); 345 } 346 347 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nlookup), ' ', buf); 348 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(ncreate), ' ', buf); 349 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nremove), ' ', buf); 350 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nlink), ' ', buf); 351 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nrename), ' ', buf); 352 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nmkdir), ' ', buf); 353 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nrmdir), ' ', buf); 354 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nreaddir), ' ', buf); 355 PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(nsymlink), ' ', buf); 356 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nreadlink), ' ', buf); 357 (void) printf("%s\n", name); 358 } 359 360 361 #define PRINT_VOPSTAT_CMN(niceflag, vop) \ 362 if (niceflag) \ 363 (void) printf("%10s ", #vop); \ 364 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(n##vop), ' ', buf); 365 366 #define PRINT_VOPSTAT(niceflag, vop) \ 367 PRINT_VOPSTAT_CMN(niceflag, vop); \ 368 if (niceflag) \ 369 (void) printf("\n"); 370 371 #define PRINT_VOPSTAT_IO(niceflag, vop) \ 372 PRINT_VOPSTAT_CMN(niceflag, vop); \ 373 PRINTSTAT(niceflag, " %5s\n", "%lld:", \ 374 DELTA(vop##_bytes), ' ', buf); 375 376 static void 377 vop_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag) 378 { 379 int niceflag = ((dispflag & DISP_RAW) == 0); 380 char buf[LBUFSZ]; 381 382 if (niceflag) { 383 (void) printf("%s\n", name); 384 (void) printf(" operation #ops bytes\n"); 385 } 386 387 PRINT_VOPSTAT(niceflag, open); 388 PRINT_VOPSTAT(niceflag, close); 389 PRINT_VOPSTAT_IO(niceflag, read); 390 PRINT_VOPSTAT_IO(niceflag, write); 391 PRINT_VOPSTAT(niceflag, ioctl); 392 PRINT_VOPSTAT(niceflag, setfl); 393 PRINT_VOPSTAT(niceflag, getattr); 394 PRINT_VOPSTAT(niceflag, setattr); 395 PRINT_VOPSTAT(niceflag, access); 396 PRINT_VOPSTAT(niceflag, lookup); 397 PRINT_VOPSTAT(niceflag, create); 398 PRINT_VOPSTAT(niceflag, remove); 399 PRINT_VOPSTAT(niceflag, link); 400 PRINT_VOPSTAT(niceflag, rename); 401 PRINT_VOPSTAT(niceflag, mkdir); 402 PRINT_VOPSTAT(niceflag, rmdir); 403 PRINT_VOPSTAT_IO(niceflag, readdir); 404 PRINT_VOPSTAT(niceflag, symlink); 405 PRINT_VOPSTAT(niceflag, readlink); 406 PRINT_VOPSTAT(niceflag, fsync); 407 PRINT_VOPSTAT(niceflag, inactive); 408 PRINT_VOPSTAT(niceflag, fid); 409 PRINT_VOPSTAT(niceflag, rwlock); 410 PRINT_VOPSTAT(niceflag, rwunlock); 411 PRINT_VOPSTAT(niceflag, seek); 412 PRINT_VOPSTAT(niceflag, cmp); 413 PRINT_VOPSTAT(niceflag, frlock); 414 PRINT_VOPSTAT(niceflag, space); 415 PRINT_VOPSTAT(niceflag, realvp); 416 PRINT_VOPSTAT(niceflag, getpage); 417 PRINT_VOPSTAT(niceflag, putpage); 418 PRINT_VOPSTAT(niceflag, map); 419 PRINT_VOPSTAT(niceflag, addmap); 420 PRINT_VOPSTAT(niceflag, delmap); 421 PRINT_VOPSTAT(niceflag, poll); 422 PRINT_VOPSTAT(niceflag, dump); 423 PRINT_VOPSTAT(niceflag, pathconf); 424 PRINT_VOPSTAT(niceflag, pageio); 425 PRINT_VOPSTAT(niceflag, dumpctl); 426 PRINT_VOPSTAT(niceflag, dispose); 427 PRINT_VOPSTAT(niceflag, getsecattr); 428 PRINT_VOPSTAT(niceflag, setsecattr); 429 PRINT_VOPSTAT(niceflag, shrlock); 430 PRINT_VOPSTAT(niceflag, vnevent); 431 PRINT_VOPSTAT(niceflag, reqzcbuf); 432 PRINT_VOPSTAT(niceflag, retzcbuf); 433 434 if (niceflag) { 435 /* Make it easier on the eyes */ 436 (void) printf("\n"); 437 } else { 438 (void) printf("%s\n", name); 439 } 440 } 441 442 443 /* 444 * Retrieve the vopstats. If kspp (pointer to kstat_t pointer) is non-NULL, 445 * then pass it back to the caller. 446 * 447 * Returns 0 on success, non-zero on failure. 448 */ 449 int 450 get_vopstats(kstat_ctl_t *kc, char *ksname, vopstats_t *vsp, kstat_t **kspp) 451 { 452 kstat_t *ksp; 453 454 if (ksname == NULL || *ksname == 0) 455 return (1); 456 457 errno = 0; 458 /* wait for a possibly up-to-date chain */ 459 while (kstat_chain_update(kc) == -1) { 460 if (errno == EAGAIN) { 461 errno = 0; 462 (void) poll(NULL, 0, RETRY_DELAY); 463 continue; 464 } 465 perror("kstat_chain_update"); 466 exit(1); 467 } 468 469 if ((ksp = kstat_lookup(kc, NULL, -1, ksname)) == NULL) { 470 return (1); 471 } 472 473 if (kstat_read(kc, ksp, vsp) == -1) { 474 return (1); 475 } 476 477 if (kspp) 478 *kspp = ksp; 479 480 return (0); 481 } 482 483 /* 484 * Given a file system type name, determine if it's part of the 485 * exception list of file systems that are not to be displayed. 486 */ 487 int 488 is_exception(char *fsname) 489 { 490 char **xlp; /* Pointer into the exception list */ 491 492 static char *exception_list[] = { 493 "specfs", 494 "fifofs", 495 "fd", 496 "swapfs", 497 "ctfs", 498 "objfs", 499 "nfsdyn", 500 NULL 501 }; 502 503 for (xlp = &exception_list[0]; *xlp != NULL; xlp++) { 504 if (strcmp(fsname, *xlp) == 0) 505 return (1); 506 } 507 508 return (0); 509 } 510 511 /* 512 * Plain and simple, build an array of names for fstypes 513 * Returns 0, if it encounters a problem. 514 */ 515 int 516 build_fstype_list(char ***fstypep) 517 { 518 int i; 519 int nfstype; 520 char buf[FSTYPSZ + 1]; 521 522 if ((nfstype = sysfs(GETNFSTYP)) < 0) { 523 perror("sysfs(GETNFSTYP)"); 524 return (0); 525 } 526 527 if ((*fstypep = calloc(nfstype, sizeof (char *))) == NULL) { 528 perror("calloc() fstypes"); 529 return (0); 530 } 531 532 for (i = 1; i < nfstype; i++) { 533 if (sysfs(GETFSTYP, i, buf) < 0) { 534 perror("sysfs(GETFSTYP)"); 535 return (0); 536 } 537 538 if (buf[0] == 0) 539 continue; 540 541 /* If this is part of the exception list, move on */ 542 if (is_exception(buf)) 543 continue; 544 545 if (((*fstypep)[i] = strdup(buf)) == NULL) { 546 perror("strdup() fstype name"); 547 return (0); 548 } 549 } 550 551 return (i); 552 } 553 554 /* 555 * After we're done with getopts(), process the rest of the 556 * operands. We have three cases and this is the priority: 557 * 558 * 1) [ operand... ] interval count 559 * 2) [ operand... ] interval 560 * 3) [ operand... ] 561 * 562 * The trick is that any of the operands might start with a number or even 563 * be made up exclusively of numbers (and we have to handle negative numbers 564 * in case a user/script gets out of line). If we find two operands at the 565 * end of the list then we claim case 1. If we find only one operand at the 566 * end made up only of number, then we claim case 2. Otherwise, case 3. 567 * BTW, argc, argv don't change. 568 */ 569 int 570 parse_operands( 571 int argc, 572 char **argv, 573 int optind, 574 long *interval, 575 long *count, 576 entity_t **entityp) /* Array of stat-able entities */ 577 { 578 int nentities = 0; /* Number of entities found */ 579 int out_of_range; /* Set if 2nd-to-last operand out-of-range */ 580 581 if (argc == optind) 582 return (nentities); /* None found, returns 0 */ 583 /* 584 * We know exactly what the maximum number of entities is going 585 * to be: argc - optind 586 */ 587 if ((*entityp = calloc((argc - optind), sizeof (entity_t))) == NULL) { 588 perror("calloc() entities"); 589 return (-1); 590 } 591 592 for (/* void */; argc > optind; optind++) { 593 char *endptr; 594 595 /* If we have more than two operands left to process */ 596 if ((argc - optind) > 2) { 597 (*entityp)[nentities++].e_name = strdup(argv[optind]); 598 continue; 599 } 600 601 /* If we're here, then we only have one or two operands left */ 602 errno = 0; 603 out_of_range = 0; 604 *interval = strtol(argv[optind], &endptr, 10); 605 if (*endptr && !isdigit((int)*endptr)) { 606 /* Operand was not a number */ 607 (*entityp)[nentities++].e_name = strdup(argv[optind]); 608 continue; 609 } else if (errno == ERANGE || *interval <= 0 || 610 *interval > MAXLONG) { 611 /* Operand was a number, just out of range */ 612 out_of_range++; 613 } 614 615 /* 616 * The last operand we saw was a number. If it happened to 617 * be the last operand, then it is the interval... 618 */ 619 if ((argc - optind) == 1) { 620 /* ...but we need to check the range. */ 621 if (out_of_range) { 622 (void) fprintf(stderr, gettext( 623 "interval must be between 1 and " 624 "%ld (inclusive)\n"), MAXLONG); 625 return (-1); 626 } else { 627 /* 628 * The value of the interval is valid. Set 629 * count to something really big so it goes 630 * virtually forever. 631 */ 632 *count = MAXLONG; 633 break; 634 } 635 } 636 637 /* 638 * At this point, we *might* have the interval, but if the 639 * next operand isn't a number, then we don't have either 640 * the interval nor the count. Both must be set to the 641 * defaults. In that case, both the current and the previous 642 * operands are stat-able entities. 643 */ 644 errno = 0; 645 *count = strtol(argv[optind + 1], &endptr, 10); 646 if (*endptr && !isdigit((int)*endptr)) { 647 /* 648 * Faked out! The last operand wasn't a number so 649 * the current and previous operands should be 650 * stat-able entities. We also need to reset interval. 651 */ 652 *interval = 0; 653 (*entityp)[nentities++].e_name = strdup(argv[optind++]); 654 (*entityp)[nentities++].e_name = strdup(argv[optind++]); 655 } else if (out_of_range || errno == ERANGE || *count <= 0) { 656 (void) fprintf(stderr, gettext( 657 "Both interval and count must be between 1 " 658 "and %ld (inclusive)\n"), MAXLONG); 659 return (-1); 660 } 661 break; /* Done! */ 662 } 663 return (nentities); 664 } 665 666 /* 667 * set_mntpt() looks at the entity's name (e_name) and finds its 668 * mountpoint. To do this, we need to build a list of mountpoints 669 * from /etc/mnttab. We only need to do this once and we don't do it 670 * if we don't need to look at any mountpoints. 671 * Returns 0 on success, non-zero if it couldn't find a mount-point. 672 */ 673 int 674 set_mntpt(entity_t *ep) 675 { 676 static struct mnt { 677 struct mnt *m_next; 678 char *m_mntpt; 679 ulong_t m_fsid; /* From statvfs(), set only as needed */ 680 } *mnt_list = NULL; /* Linked list of mount-points */ 681 struct mnt *mntp; 682 struct statvfs64 statvfsbuf; 683 char *original_name = ep->e_name; 684 char path[PATH_MAX]; 685 686 if (original_name == NULL) /* Shouldn't happen */ 687 return (1); 688 689 /* We only set up mnt_list the first time this is called */ 690 if (mnt_list == NULL) { 691 FILE *fp; 692 struct mnttab mnttab; 693 694 if ((fp = fopen(MNTTAB, "r")) == NULL) { 695 perror(MNTTAB); 696 return (1); 697 } 698 resetmnttab(fp); 699 /* 700 * We insert at the front of the list so that when we 701 * search entries we'll have the last mounted entries 702 * first in the list so that we can match the longest 703 * mountpoint. 704 */ 705 while (getmntent(fp, &mnttab) == 0) { 706 if ((mntp = malloc(sizeof (*mntp))) == NULL) { 707 perror("malloc() mount list"); 708 return (1); 709 } 710 mntp->m_mntpt = strdup(mnttab.mnt_mountp); 711 mntp->m_next = mnt_list; 712 mnt_list = mntp; 713 } 714 (void) fclose(fp); 715 } 716 717 if (realpath(original_name, path) == NULL) { 718 perror(original_name); 719 return (1); 720 } 721 722 /* 723 * Now that we have the path, walk through the mnt_list and 724 * look for the first (best) match. 725 */ 726 for (mntp = mnt_list; mntp; mntp = mntp->m_next) { 727 if (strncmp(path, mntp->m_mntpt, strlen(mntp->m_mntpt)) == 0) { 728 if (mntp->m_fsid == 0) { 729 if (statvfs64(mntp->m_mntpt, &statvfsbuf)) { 730 /* Can't statvfs so no match */ 731 continue; 732 } else { 733 mntp->m_fsid = statvfsbuf.f_fsid; 734 } 735 } 736 737 if (ep->e_fsid != mntp->m_fsid) { 738 /* No match - Move on */ 739 continue; 740 } 741 742 break; 743 } 744 } 745 746 if (mntp == NULL) { 747 (void) fprintf(stderr, gettext( 748 "Can't find mount point for %s\n"), path); 749 return (1); 750 } 751 752 ep->e_name = strdup(mntp->m_mntpt); 753 free(original_name); 754 return (0); 755 } 756 757 /* 758 * We have an array of entities that are potentially stat-able. Using 759 * the name (e_name) of the entity, attempt to construct a ksname suitable 760 * for use by kstat_lookup(3kstat) and fill it into the e_ksname member. 761 * 762 * We check the e_name against the list of file system types. If there is 763 * no match then test to see if the path is valid. If the path is valid, 764 * then determine the mountpoint. 765 */ 766 void 767 set_ksnames(entity_t *entities, int nentities, char **fstypes, int nfstypes) 768 { 769 int i, j; 770 struct statvfs64 statvfsbuf; 771 772 for (i = 0; i < nentities; i++) { 773 entity_t *ep = &entities[i]; 774 775 /* Check the name against the list of fstypes */ 776 for (j = 1; j < nfstypes; j++) { 777 if (fstypes[j] && ep->e_name && 778 strcmp(ep->e_name, fstypes[j]) == 0) { 779 /* It's a file system type */ 780 ep->e_type = ENTYPE_FSTYPE; 781 (void) snprintf(ep->e_ksname, KSTAT_STRLEN, 782 "%s%s", VOPSTATS_STR, ep->e_name); 783 /* Now allocate the vopstats array */ 784 ep->e_vs = calloc(VS_SIZE, sizeof (vopstats_t)); 785 if (entities[i].e_vs == NULL) { 786 perror("calloc() fstype vopstats"); 787 exit(1); 788 } 789 break; 790 } 791 } 792 if (j < nfstypes) /* Found it! */ 793 continue; 794 795 /* 796 * If the entity in the exception list of fstypes, then 797 * null out the entry so it isn't displayed and move along. 798 */ 799 if (is_exception(ep->e_name)) { 800 ep->e_ksname[0] = 0; 801 continue; 802 } 803 804 /* If we didn't find it, see if it's a path */ 805 if (ep->e_name == NULL || statvfs64(ep->e_name, &statvfsbuf)) { 806 /* Error - Make sure the entry is nulled out */ 807 ep->e_ksname[0] = 0; 808 continue; 809 } 810 (void) snprintf(ep->e_ksname, KSTAT_STRLEN, "%s%lx", 811 VOPSTATS_STR, statvfsbuf.f_fsid); 812 ep->e_fsid = statvfsbuf.f_fsid; 813 if (set_mntpt(ep)) { 814 (void) fprintf(stderr, 815 gettext("Can't determine type of \"%s\"\n"), 816 ep->e_name ? ep->e_name : gettext("<NULL>")); 817 } else { 818 ep->e_type = ENTYPE_MNTPT; 819 } 820 821 /* Now allocate the vopstats array */ 822 ep->e_vs = calloc(VS_SIZE, sizeof (vopstats_t)); 823 if (entities[i].e_vs == NULL) { 824 perror("calloc() vopstats array"); 825 exit(1); 826 } 827 } 828 } 829 830 /* 831 * The idea is that 'dspfunc' should only be modified from the default 832 * once since the display options are mutually exclusive. If 'dspfunc' 833 * only contains the default display function, then all is good and we 834 * can set it to the new display function. Otherwise, bail. 835 */ 836 void 837 set_dispfunc( 838 void (**dspfunc)(char *, vopstats_t *, vopstats_t *, int), 839 void (*newfunc)(char *, vopstats_t *, vopstats_t *, int)) 840 { 841 if (*dspfunc != dflt_display) { 842 (void) fprintf(stderr, gettext( 843 "%s: Display options -{a|f|i|n|v} are mutually exclusive\n"), 844 cmdname); 845 usage(); 846 } 847 *dspfunc = newfunc; 848 } 849 850 int 851 main(int argc, char *argv[]) 852 { 853 int c; 854 int i, j; /* Generic counters */ 855 int nentities_found; 856 int linesout = 0; /* Keeps track of lines printed */ 857 int printhdr = 0; /* Print a header? 0 = no, 1 = yes */ 858 int nfstypes; /* Number of fstypes */ 859 int dispflag = 0; /* Flags for display control */ 860 long count = 0; /* Number of iterations for display */ 861 int forever; /* Run forever */ 862 long interval = 0; 863 boolean_t fstypes_only = B_FALSE; /* Display fstypes only */ 864 char **fstypes; /* Array of names of all fstypes */ 865 int nentities; /* Number of stat-able entities */ 866 entity_t *entities; /* Array of stat-able entities */ 867 kstat_ctl_t *kc; 868 void (*dfunc)(char *, vopstats_t *, vopstats_t *, int) = dflt_display; 869 hrtime_t start_n; /* Start time */ 870 hrtime_t period_n; /* Interval in nanoseconds */ 871 872 extern int optind; 873 874 (void) setlocale(LC_ALL, ""); 875 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ 876 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ 877 #endif 878 (void) textdomain(TEXT_DOMAIN); 879 880 /* Don't let buffering interfere with piped output. */ 881 (void) setvbuf(stdout, NULL, _IOLBF, 0); 882 883 cmdname = argv[0]; 884 while ((c = getopt(argc, argv, OPTIONS)) != EOF) { 885 switch (c) { 886 887 default: 888 usage(); 889 break; 890 891 case 'F': /* Only display available FStypes */ 892 fstypes_only = B_TRUE; 893 break; 894 895 #if PARSABLE_OUTPUT 896 case 'P': /* Parsable output */ 897 dispflag |= DISP_RAW; 898 break; 899 #endif /* PARSABLE_OUTPUT */ 900 901 case 'T': /* Timestamp */ 902 if (optarg) { 903 if (strcmp(optarg, "u") == 0) { 904 timestamp_fmt = UDATE; 905 } else if (strcmp(optarg, "d") == 0) { 906 timestamp_fmt = DDATE; 907 } 908 } 909 910 /* If it was never set properly... */ 911 if (timestamp_fmt == NODATE) { 912 (void) fprintf(stderr, gettext("%s: -T option " 913 "requires either 'u' or 'd'\n"), cmdname); 914 usage(); 915 } 916 break; 917 918 case 'a': 919 set_dispfunc(&dfunc, attr_display); 920 break; 921 922 case 'f': 923 set_dispfunc(&dfunc, vop_display); 924 break; 925 926 case 'i': 927 set_dispfunc(&dfunc, io_display); 928 break; 929 930 case 'n': 931 set_dispfunc(&dfunc, naming_display); 932 break; 933 934 case 'v': 935 set_dispfunc(&dfunc, vm_display); 936 break; 937 } 938 } 939 940 #if PARSABLE_OUTPUT 941 if ((dispflag & DISP_RAW) && (timestamp_fmt != NODATE)) { 942 (void) fprintf(stderr, gettext( 943 "-P and -T options are mutually exclusive\n")); 944 usage(); 945 } 946 #endif /* PARSABLE_OUTPUT */ 947 948 /* Gather the list of filesystem types */ 949 if ((nfstypes = build_fstype_list(&fstypes)) == 0) { 950 (void) fprintf(stderr, 951 gettext("Can't build list of fstypes\n")); 952 exit(1); 953 } 954 955 nentities = parse_operands( 956 argc, argv, optind, &interval, &count, &entities); 957 forever = count == MAXLONG; 958 period_n = (hrtime_t)interval * NANOSEC; 959 960 if (nentities == -1) /* Set of operands didn't parse properly */ 961 usage(); 962 963 if ((nentities == 0) && (fstypes_only == B_FALSE)) { 964 (void) fprintf(stderr, gettext( 965 "Must specify -F or at least one fstype or mount point\n")); 966 usage(); 967 } 968 969 if ((nentities > 0) && (fstypes_only == B_TRUE)) { 970 (void) fprintf(stderr, gettext( 971 "Cannot use -F with fstypes or mount points\n")); 972 usage(); 973 } 974 975 /* 976 * If we had no operands (except for interval/count) and we 977 * requested FStypes only (-F), then fill in the entities[] 978 * array with all available fstypes. 979 */ 980 if ((nentities == 0) && (fstypes_only == B_TRUE)) { 981 if ((entities = calloc(nfstypes, sizeof (entity_t))) == NULL) { 982 perror("calloc() fstype stats"); 983 exit(1); 984 } 985 986 for (i = 1; i < nfstypes; i++) { 987 if (fstypes[i]) { 988 entities[nentities].e_name = strdup(fstypes[i]); 989 nentities++; 990 } 991 } 992 } 993 994 set_ksnames(entities, nentities, fstypes, nfstypes); 995 996 if ((kc = kstat_open()) == NULL) { 997 perror("kstat_open"); 998 exit(1); 999 } 1000 1001 /* Set start time */ 1002 start_n = gethrtime(); 1003 1004 /* Initial timestamp */ 1005 if (timestamp_fmt != NODATE) { 1006 print_timestamp(timestamp_fmt); 1007 linesout++; 1008 } 1009 1010 /* 1011 * The following loop walks through the entities[] list to "prime 1012 * the pump" 1013 */ 1014 for (j = 0, printhdr = 1; j < nentities; j++) { 1015 entity_t *ent = &entities[j]; 1016 vopstats_t *vsp = &ent->e_vs[CUR_INDEX]; 1017 kstat_t *ksp = NULL; 1018 1019 if (get_vopstats(kc, ent->e_ksname, vsp, &ksp) == 0) { 1020 (*dfunc)(ent->e_name, NULL, vsp, 1021 dispflag_policy(printhdr, dispflag)); 1022 linesout++; 1023 } else { 1024 /* 1025 * If we can't find it the first time through, then 1026 * get rid of it. 1027 */ 1028 entities[j].e_ksname[0] = 0; 1029 1030 /* 1031 * If we're only displaying FStypes (-F) then don't 1032 * complain about any file systems that might not 1033 * be loaded. Otherwise, let the user know that 1034 * he chose poorly. 1035 */ 1036 if (fstypes_only == B_FALSE) { 1037 (void) fprintf(stderr, gettext( 1038 "No statistics available for %s\n"), 1039 entities[j].e_name); 1040 } 1041 } 1042 printhdr = 0; 1043 } 1044 1045 if (count > 1) 1046 /* Set up signal handler for SIGCONT */ 1047 if (signal(SIGCONT, cont_handler) == SIG_ERR) 1048 fail(1, "signal failed"); 1049 1050 1051 BUMP_INDEX(); /* Swap the previous/current indices */ 1052 i = 1; 1053 while (forever || i++ <= count) { 1054 /* 1055 * No telling how many lines will be printed in any interval. 1056 * There should be a minimum of HEADERLINES between any 1057 * header. If we exceed that, no big deal. 1058 */ 1059 if (linesout > HEADERLINES) { 1060 linesout = 0; 1061 printhdr = 1; 1062 } 1063 /* Have a kip */ 1064 sleep_until(&start_n, period_n, forever, &caught_cont); 1065 1066 if (timestamp_fmt != NODATE) { 1067 print_timestamp(timestamp_fmt); 1068 linesout++; 1069 } 1070 1071 for (j = 0, nentities_found = 0; j < nentities; j++) { 1072 entity_t *ent = &entities[j]; 1073 1074 /* 1075 * If this entry has been cleared, don't attempt 1076 * to process it. 1077 */ 1078 if (ent->e_ksname[0] == 0) { 1079 continue; 1080 } 1081 1082 if (get_vopstats(kc, ent->e_ksname, 1083 &ent->e_vs[CUR_INDEX], NULL) == 0) { 1084 (*dfunc)(ent->e_name, &ent->e_vs[PREV_INDEX], 1085 &ent->e_vs[CUR_INDEX], 1086 dispflag_policy(printhdr, dispflag)); 1087 linesout++; 1088 nentities_found++; 1089 } else { 1090 if (ent->e_type == ENTYPE_MNTPT) { 1091 (void) printf(gettext( 1092 "<<mount point no longer " 1093 "available: %s>>\n"), ent->e_name); 1094 } else if (ent->e_type == ENTYPE_FSTYPE) { 1095 (void) printf(gettext( 1096 "<<file system module no longer " 1097 "loaded: %s>>\n"), ent->e_name); 1098 } else { 1099 (void) printf(gettext( 1100 "<<%s no longer available>>\n"), 1101 ent->e_name); 1102 } 1103 /* Disable this so it doesn't print again */ 1104 ent->e_ksname[0] = 0; 1105 } 1106 printhdr = 0; /* Always shut this off */ 1107 } 1108 BUMP_INDEX(); /* Bump the previous/current indices */ 1109 1110 /* 1111 * If the entities we were observing are no longer there 1112 * (file system modules unloaded, file systems unmounted) 1113 * then we're done. 1114 */ 1115 if (nentities_found == 0) 1116 break; 1117 } 1118 1119 return (0); 1120 } 1121