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