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