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