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