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