xref: /illumos-gate/usr/src/cmd/fm/fmstat/common/fmstat.c (revision e3ae4b35c024af1196582063ecee3ab79367227d)
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 /*
23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*
28  * Copyright (c) 2018, Joyent, Inc.
29  */
30 
31 #include <fm/fmd_adm.h>
32 
33 #include <strings.h>
34 #include <limits.h>
35 #include <stdlib.h>
36 #include <stdarg.h>
37 #include <stdio.h>
38 #include <errno.h>
39 #include <poll.h>
40 #include <locale.h>
41 
42 #include "statcommon.h"
43 
44 #define	FMSTAT_EXIT_SUCCESS	0
45 #define	FMSTAT_EXIT_ERROR	1
46 #define	FMSTAT_EXIT_USAGE	2
47 
48 static const struct stats {
49 	fmd_stat_t module;
50 	fmd_stat_t authority;
51 	fmd_stat_t state;
52 	fmd_stat_t loadtime;
53 	fmd_stat_t snaptime;
54 	fmd_stat_t received;
55 	fmd_stat_t discarded;
56 	fmd_stat_t retried;
57 	fmd_stat_t replayed;
58 	fmd_stat_t lost;
59 	fmd_stat_t dispatched;
60 	fmd_stat_t dequeued;
61 	fmd_stat_t prdequeued;
62 	fmd_stat_t accepted;
63 	fmd_stat_t memtotal;
64 	fmd_stat_t buftotal;
65 	fmd_stat_t caseopen;
66 	fmd_stat_t casesolved;
67 	fmd_stat_t wcnt;
68 	fmd_stat_t wtime;
69 	fmd_stat_t wlentime;
70 	fmd_stat_t wlastupdate;
71 	fmd_stat_t dtime;
72 	fmd_stat_t dlastupdate;
73 } stats_template = {
74 	{ "module", FMD_TYPE_STRING },
75 	{ "authority", FMD_TYPE_STRING },
76 	{ "state", FMD_TYPE_STRING },
77 	{ "loadtime", FMD_TYPE_TIME },
78 	{ "snaptime", FMD_TYPE_TIME },
79 	{ "received", FMD_TYPE_UINT64 },
80 	{ "discarded", FMD_TYPE_UINT64 },
81 	{ "retried", FMD_TYPE_UINT64 },
82 	{ "replayed", FMD_TYPE_UINT64 },
83 	{ "lost", FMD_TYPE_UINT64 },
84 	{ "dispatched", FMD_TYPE_UINT64 },
85 	{ "dequeued", FMD_TYPE_UINT64 },
86 	{ "prdequeued", FMD_TYPE_UINT64 },
87 	{ "accepted", FMD_TYPE_UINT64 },
88 	{ "memtotal", FMD_TYPE_SIZE },
89 	{ "buftotal", FMD_TYPE_SIZE },
90 	{ "caseopen", FMD_TYPE_UINT64 },
91 	{ "casesolved", FMD_TYPE_UINT64 },
92 	{ "wcnt", FMD_TYPE_UINT32 },
93 	{ "wtime", FMD_TYPE_TIME },
94 	{ "wlentime", FMD_TYPE_TIME },
95 	{ "wlastupdate", FMD_TYPE_TIME },
96 	{ "dtime", FMD_TYPE_TIME },
97 	{ "dlastupdate", FMD_TYPE_TIME },
98 };
99 
100 static const char *g_pname;
101 static fmd_adm_t *g_adm;
102 
103 static struct modstats {
104 	char *m_name;
105 	struct modstats *m_next;
106 	struct stats m_stbuf[2];
107 	int m_stidx;
108 	int m_id;
109 	struct stats *m_old;
110 	struct stats *m_new;
111 	double m_wait;
112 	double m_svc;
113 	double m_pct_b;
114 	double m_pct_w;
115 } *g_mods;
116 
117 static uint_t timestamp_fmt = NODATE;
118 
119 #if !defined(TEXT_DOMAIN)		/* Should be defined by cc -D */
120 #define	TEXT_DOMAIN "SYS_TEST"		/* Use this only if it isn't */
121 #endif
122 
123 static void
124 vwarn(const char *format, va_list ap)
125 {
126 	int err = errno;
127 
128 	(void) fprintf(stderr, "%s: ", g_pname);
129 
130 	if (format != NULL)
131 		(void) vfprintf(stderr, format, ap);
132 
133 	errno = err; /* restore errno for fmd_adm_errmsg() */
134 
135 	if (format == NULL)
136 		(void) fprintf(stderr, "%s\n", fmd_adm_errmsg(g_adm));
137 	else if (strchr(format, '\n') == NULL)
138 		(void) fprintf(stderr, ": %s\n", fmd_adm_errmsg(g_adm));
139 }
140 
141 /*PRINTFLIKE1*/
142 void
143 warn(const char *format, ...)
144 {
145 	va_list ap;
146 
147 	va_start(ap, format);
148 	vwarn(format, ap);
149 	va_end(ap);
150 }
151 
152 /*PRINTFLIKE1*/
153 void
154 die(const char *format, ...)
155 {
156 	va_list ap;
157 
158 	va_start(ap, format);
159 	vwarn(format, ap);
160 	va_end(ap);
161 
162 	fmd_adm_close(g_adm);
163 	exit(FMSTAT_EXIT_ERROR);
164 }
165 
166 static char *
167 time2str(char *buf, size_t len, uint64_t time)
168 {
169 	static const struct unit {
170 		const char *u_name;
171 		hrtime_t u_mul;
172 	} units[] = {
173 		{ "d",	NANOSEC * (hrtime_t)(24 * 60 * 60) },
174 		{ "h",	NANOSEC * (hrtime_t)(60 * 60) },
175 		{ "m",	NANOSEC * (hrtime_t)60 },
176 		{ "s",	NANOSEC / SEC },
177 		{ "ms",	NANOSEC / MILLISEC },
178 		{ "us",	NANOSEC / MICROSEC },
179 		{ "ns",	NANOSEC / NANOSEC },
180 	};
181 
182 	const struct unit *up;
183 
184 	for (up = units; time % up->u_mul != 0; up++)
185 		continue; /* find largest unit of which 'time' is a multiple */
186 
187 	(void) snprintf(buf, len, "%llu%s", time / up->u_mul, up->u_name);
188 	return (buf);
189 }
190 
191 static char *
192 size2str(char *buf, size_t len, uint64_t size)
193 {
194 	static const char units[] = "bKMGTPE";
195 	const uint64_t scale = 1024;
196 	const char *up = units;
197 	uint64_t osize = 0;
198 
199 	/*
200 	 * Convert the input size to a round number of the appropriately
201 	 * scaled units (saved in 'size') and a remainder (saved in 'osize').
202 	 */
203 	while (size >= scale && up < (units + sizeof (units) - 2)) {
204 		up++;
205 		osize = size;
206 		size = (size + (scale / 2)) / scale;
207 	}
208 
209 	/*
210 	 * Format the result using at most one decimal place and the unit
211 	 * depending upon the amount of remainder (same as df -h algorithm).
212 	 */
213 	if (osize != 0 && (osize / scale) < 10)
214 		(void) snprintf(buf, len, "%.1f%c", (float)osize / scale, *up);
215 	else if (size != 0)
216 		(void) snprintf(buf, len, "%llu%c", size, *up);
217 	else
218 		(void) snprintf(buf, len, "0");
219 
220 	return (buf);
221 }
222 
223 static uint64_t
224 u64delta(uint64_t old, uint64_t new)
225 {
226 	return (new >= old ? (new - old) : ((UINT64_MAX - old) + new + 1));
227 }
228 
229 static struct modstats *
230 modstat_create(const char *name, id_t id)
231 {
232 	struct modstats *mp = malloc(sizeof (struct modstats));
233 
234 	if (mp == NULL)
235 		return (NULL);
236 
237 	bzero(mp, sizeof (struct modstats));
238 
239 	if (name != NULL && (mp->m_name = strdup(name)) == NULL) {
240 		free(mp);
241 		return (NULL);
242 	}
243 
244 	mp->m_id = id;
245 	mp->m_next = g_mods;
246 	g_mods = mp;
247 	return (mp);
248 }
249 
250 /*
251  * Given a statistics buffer containing event queue statistics, compute the
252  * common queue statistics for the given module and store the results in 'mp'.
253  * We set m_new and m_old for the caller, and store the compute values of
254  * m_svc, m_wait, m_pct_w, and m_pct_b there as well.  The caller must not free
255  * 'ams' until after using the results as m_new may contain pointers to it.
256  */
257 static void
258 modstat_compute(struct modstats *mp, fmd_adm_stats_t *ams)
259 {
260 	static fmd_stat_t *t_beg = (fmd_stat_t *)(&stats_template + 0);
261 	static fmd_stat_t *t_end = (fmd_stat_t *)(&stats_template + 1);
262 
263 	struct stats *old, *new;
264 	fmd_stat_t *tsp, *nsp, *sp;
265 	double elapsed, avg_w, avg_d;
266 	uint64_t delta;
267 
268 	old = mp->m_old = &mp->m_stbuf[mp->m_stidx];
269 	mp->m_stidx = 1 - mp->m_stidx;
270 	new = mp->m_new = &mp->m_stbuf[mp->m_stidx];
271 
272 	/*
273 	 * The statistics can come in any order; we compare each one to the
274 	 * template of statistics of interest, find the matching ones, and copy
275 	 * their values into the appropriate slot of the 'new' stats.
276 	 */
277 	for (nsp = ams->ams_buf; nsp < ams->ams_buf + ams->ams_len; nsp++) {
278 		for (tsp = t_beg; tsp < t_end; tsp++) {
279 			const char *p = strrchr(nsp->fmds_name, '.');
280 
281 			/*
282 			 * The fmd queue stats can either be named fmd.<name>
283 			 * or fmd.xprt.%u.<name> depending on whether we're
284 			 * looking at the module queue or the transport queue.
285 			 * So we match using the patterns fmd.* and *.<name>
286 			 * and store only the value of <name> in stats_template.
287 			 */
288 			if (p == NULL || strcmp(p + 1, tsp->fmds_name) != 0 ||
289 			    strncmp(nsp->fmds_name, "fmd.", 4) != 0)
290 				continue; /* continue until we match the stat */
291 
292 			if (tsp->fmds_type != nsp->fmds_type) {
293 				warn("%s has unexpected type (%u != %u)\n",
294 				    nsp->fmds_name, tsp->fmds_type,
295 				    nsp->fmds_type);
296 			} else {
297 				sp = (fmd_stat_t *)new + (tsp - t_beg);
298 				sp->fmds_value = nsp->fmds_value;
299 			}
300 		}
301 	}
302 
303 	/*
304 	 * Compute the elapsed time by taking the delta between 'snaptime', or
305 	 * or between snaptime and loadtime if there is no previous snapshot.
306 	 * If delta is zero, set it to 1sec so we don't divide by zero later.
307 	 */
308 	delta = u64delta(old->snaptime.fmds_value.ui64 ?
309 	    old->snaptime.fmds_value.ui64 : old->loadtime.fmds_value.ui64,
310 	    new->snaptime.fmds_value.ui64);
311 
312 	elapsed = delta ? (double)delta : (double)NANOSEC;
313 
314 	/*
315 	 * Compute average wait queue len by taking the delta in the wait queue
316 	 * len * time products (wlentime stat) and dividing by the elapsed time.
317 	 */
318 	delta = u64delta(old->wlentime.fmds_value.ui64,
319 	    new->wlentime.fmds_value.ui64);
320 
321 	if (delta != 0)
322 		mp->m_wait = (double)delta / elapsed;
323 	else
324 		mp->m_wait = 0.0;
325 
326 	/*
327 	 * Compute average wait time by taking the delta in the wait queue time
328 	 * (wtime) and dividing by the delta in the number of dispatches.
329 	 */
330 	delta = u64delta(old->dispatched.fmds_value.ui64,
331 	    new->dispatched.fmds_value.ui64);
332 
333 	if (delta != 0) {
334 		avg_w = (double)u64delta(old->wtime.fmds_value.ui64,
335 		    new->wtime.fmds_value.ui64) / (double)delta;
336 	} else
337 		avg_w = 0.0;
338 
339 	/*
340 	 * Compute average dispatch time by taking the delta in the dispatch
341 	 * time (dtime) and dividing by the delta in the number of dequeues.
342 	 */
343 	delta = u64delta(old->dequeued.fmds_value.ui64,
344 	    new->dequeued.fmds_value.ui64);
345 
346 	if (delta != 0) {
347 		avg_d = (double)u64delta(old->dtime.fmds_value.ui64,
348 		    new->dtime.fmds_value.ui64) / (double)delta;
349 	} else
350 		avg_d = 0.0;
351 
352 	/*
353 	 * Finally compute the average overall service time by adding together
354 	 * the average wait and dispatch times and converting to milliseconds.
355 	 */
356 	mp->m_svc = ((avg_w + avg_d) * (double)MILLISEC) / (double)NANOSEC;
357 
358 	/*
359 	 * Compute the %wait and %busy times by taking the delta in wait and
360 	 * busy times, dividing by the elapsed time, and multiplying by 100.
361 	 */
362 	delta = u64delta(old->wtime.fmds_value.ui64,
363 	    new->wtime.fmds_value.ui64);
364 
365 	if (delta != 0)
366 		mp->m_pct_w = ((double)delta / elapsed) * 100.0;
367 	else
368 		mp->m_pct_w = 0.0;
369 
370 	delta = u64delta(old->dtime.fmds_value.ui64,
371 	    new->dtime.fmds_value.ui64);
372 
373 	if (delta != 0)
374 		mp->m_pct_b = ((double)delta / elapsed) * 100.0;
375 	else
376 		mp->m_pct_b = 0.0;
377 }
378 
379 /*ARGSUSED*/
380 static void
381 stat_one_xprt(id_t id, void *ignored)
382 {
383 	fmd_adm_stats_t ams;
384 	struct modstats *mp;
385 
386 	if (fmd_adm_xprt_stats(g_adm, id, &ams) != 0) {
387 		warn("failed to retrieve statistics for transport %d", (int)id);
388 		return;
389 	}
390 
391 	for (mp = g_mods; mp != NULL; mp = mp->m_next) {
392 		if (mp->m_id == id)
393 			break;
394 	}
395 
396 	if (mp == NULL && (mp = modstat_create(NULL, id)) == NULL) {
397 		warn("failed to allocate memory for transport %d", (int)id);
398 		(void) fmd_adm_stats_free(g_adm, &ams);
399 		return;
400 	}
401 
402 	modstat_compute(mp, &ams);
403 
404 	(void) printf("%3d %5s %7llu %7llu %7llu %7llu "
405 	    "%4.1f %6.1f %3.0f %3.0f %s\n", (int)id,
406 	    mp->m_new->state.fmds_value.str,
407 	    u64delta(mp->m_old->prdequeued.fmds_value.ui64,
408 	    mp->m_new->prdequeued.fmds_value.ui64),
409 	    u64delta(mp->m_old->received.fmds_value.ui64,
410 	    mp->m_new->received.fmds_value.ui64),
411 	    u64delta(mp->m_old->discarded.fmds_value.ui64,
412 	    mp->m_new->discarded.fmds_value.ui64),
413 	    u64delta(mp->m_old->lost.fmds_value.ui64,
414 	    mp->m_new->lost.fmds_value.ui64),
415 	    mp->m_wait, mp->m_svc, mp->m_pct_w, mp->m_pct_b,
416 	    mp->m_new->module.fmds_value.str);
417 
418 	(void) fmd_adm_stats_free(g_adm, &ams);
419 }
420 
421 static void
422 stat_xprt(void)
423 {
424 	(void) printf("%3s %5s %7s %7s %7s %7s %4s %6s %3s %3s %s\n",
425 	    "id", "state", "ev_send", "ev_recv", "ev_drop", "ev_lost",
426 	    "wait", "svc_t", "%w", "%b", "module");
427 
428 	if (fmd_adm_xprt_iter(g_adm, stat_one_xprt, NULL) != 0)
429 		die("failed to retrieve list of transports");
430 }
431 
432 static void
433 stat_one_xprt_auth(id_t id, void *arg)
434 {
435 	const char *module = arg;
436 	fmd_adm_stats_t ams;
437 	struct modstats *mp;
438 
439 	if (fmd_adm_xprt_stats(g_adm, id, &ams) != 0) {
440 		warn("failed to retrieve statistics for transport %d", (int)id);
441 		return;
442 	}
443 
444 	for (mp = g_mods; mp != NULL; mp = mp->m_next) {
445 		if (mp->m_id == id)
446 			break;
447 	}
448 
449 	if (mp == NULL && (mp = modstat_create(NULL, id)) == NULL) {
450 		warn("failed to allocate memory for transport %d", (int)id);
451 		(void) fmd_adm_stats_free(g_adm, &ams);
452 		return;
453 	}
454 
455 	modstat_compute(mp, &ams);
456 
457 	if (module == NULL ||
458 	    strcmp(module, mp->m_new->module.fmds_value.str) == 0) {
459 		(void) printf("%3d %5s %-18s  %s\n", (int)id,
460 		    mp->m_new->state.fmds_value.str,
461 		    mp->m_new->module.fmds_value.str,
462 		    mp->m_new->authority.fmds_value.str ?
463 		    mp->m_new->authority.fmds_value.str : "-");
464 	}
465 
466 	(void) fmd_adm_stats_free(g_adm, &ams);
467 }
468 
469 static void
470 stat_xprt_auth(const char *module)
471 {
472 	(void) printf("%3s %5s %-18s  %s\n",
473 	    "id", "state", "module", "authority");
474 
475 	if (fmd_adm_xprt_iter(g_adm, stat_one_xprt_auth, (void *)module) != 0)
476 		die("failed to retrieve list of transports");
477 }
478 
479 /*ARGSUSED*/
480 static int
481 stat_one_fmd(const fmd_adm_modinfo_t *ami, void *ignored)
482 {
483 	char memsz[8], bufsz[8];
484 	fmd_adm_stats_t ams;
485 	struct modstats *mp;
486 
487 	if (fmd_adm_module_stats(g_adm, ami->ami_name, &ams) != 0) {
488 		warn("failed to retrieve statistics for %s", ami->ami_name);
489 		return (0); /* continue on to the next module */
490 	}
491 
492 	for (mp = g_mods; mp != NULL; mp = mp->m_next) {
493 		if (strcmp(mp->m_name, ami->ami_name) == 0)
494 			break;
495 	}
496 
497 	if (mp == NULL && (mp = modstat_create(ami->ami_name, 0)) == NULL) {
498 		warn("failed to allocate memory for %s", ami->ami_name);
499 		(void) fmd_adm_stats_free(g_adm, &ams);
500 		return (0);
501 	}
502 
503 	modstat_compute(mp, &ams);
504 
505 	(void) printf("%-18s %7llu %7llu %4.1f %6.1f %3.0f %3.0f "
506 	    "%5llu %5llu %6s %6s\n", ami->ami_name,
507 	    u64delta(mp->m_old->prdequeued.fmds_value.ui64,
508 	    mp->m_new->prdequeued.fmds_value.ui64),
509 	    u64delta(mp->m_old->accepted.fmds_value.ui64,
510 	    mp->m_new->accepted.fmds_value.ui64),
511 	    mp->m_wait, mp->m_svc, mp->m_pct_w, mp->m_pct_b,
512 	    mp->m_new->caseopen.fmds_value.ui64,
513 	    mp->m_new->casesolved.fmds_value.ui64,
514 	    size2str(memsz, sizeof (memsz),
515 	    mp->m_new->memtotal.fmds_value.ui64),
516 	    size2str(bufsz, sizeof (bufsz),
517 	    mp->m_new->buftotal.fmds_value.ui64));
518 
519 	(void) fmd_adm_stats_free(g_adm, &ams);
520 	return (0);
521 }
522 
523 static void
524 stat_fmd(void)
525 {
526 	(void) printf("%-18s %7s %7s %4s %6s %3s %3s %5s %5s %6s %6s\n",
527 	    "module", "ev_recv", "ev_acpt", "wait", "svc_t", "%w", "%b",
528 	    "open", "solve", "memsz", "bufsz");
529 
530 	if (fmd_adm_module_iter(g_adm, stat_one_fmd, NULL) != 0)
531 		die("failed to retrieve list of modules");
532 }
533 
534 static void
535 stat_mod(const char *name, int aflag, int zflag)
536 {
537 	fmd_adm_stats_t ams;
538 	fmd_stat_t *sp;
539 	char buf[64];
540 
541 	if (fmd_adm_stats_read(g_adm, name, &ams) != 0) {
542 		die("failed to retrieve statistics for %s",
543 		    name ? name : "fmd(8)");
544 	}
545 
546 	(void) printf("%20s %-16s %s\n", "NAME", "VALUE", "DESCRIPTION");
547 
548 	for (sp = ams.ams_buf; sp < ams.ams_buf + ams.ams_len; sp++) {
549 		if (aflag == 0 && strncmp(sp->fmds_name, "fmd.", 4) == 0)
550 			continue; /* skip fmd-internal stats unless -a used */
551 
552 		if (zflag) {
553 			switch (sp->fmds_type) {
554 			case FMD_TYPE_INT32:
555 			case FMD_TYPE_UINT32:
556 				if (sp->fmds_value.ui32 == 0)
557 					continue;
558 				break;
559 			case FMD_TYPE_INT64:
560 			case FMD_TYPE_UINT64:
561 			case FMD_TYPE_TIME:
562 			case FMD_TYPE_SIZE:
563 				if (sp->fmds_value.ui64 == 0)
564 					continue;
565 				break;
566 			case FMD_TYPE_STRING:
567 				if (sp->fmds_value.str == NULL ||
568 				    sp->fmds_value.str[0] == '\0')
569 					continue;
570 				break;
571 			}
572 		}
573 
574 		(void) printf("%20s ", sp->fmds_name);
575 
576 		switch (sp->fmds_type) {
577 		case FMD_TYPE_BOOL:
578 			(void) printf("%-16s",
579 			    sp->fmds_value.bool ? "true" : "false");
580 			break;
581 		case FMD_TYPE_INT32:
582 			(void) printf("%-16d", sp->fmds_value.i32);
583 			break;
584 		case FMD_TYPE_UINT32:
585 			(void) printf("%-16u", sp->fmds_value.ui32);
586 			break;
587 		case FMD_TYPE_INT64:
588 			(void) printf("%-16lld", sp->fmds_value.i64);
589 			break;
590 		case FMD_TYPE_UINT64:
591 			(void) printf("%-16llu", sp->fmds_value.ui64);
592 			break;
593 		case FMD_TYPE_STRING:
594 			(void) printf("%-16s", sp->fmds_value.str ?
595 			    sp->fmds_value.str : "<<null>>");
596 			break;
597 		case FMD_TYPE_TIME:
598 			(void) printf("%-16s",
599 			    time2str(buf, sizeof (buf), sp->fmds_value.ui64));
600 			break;
601 		case FMD_TYPE_SIZE:
602 			(void) printf("%-16s",
603 			    size2str(buf, sizeof (buf), sp->fmds_value.ui64));
604 			break;
605 		default:
606 			(void) snprintf(buf, sizeof (buf),
607 			    "<<type=%u>>\n", sp->fmds_type);
608 			(void) printf("%-16s", buf);
609 		}
610 
611 		(void) printf(" %s\n", sp->fmds_desc);
612 	}
613 
614 	(void) fmd_adm_stats_free(g_adm, &ams);
615 }
616 
617 /*ARGSUSED*/
618 static int
619 stat_one_serd(const fmd_adm_serdinfo_t *asi, void *ignored)
620 {
621 	char buf1[32], buf2[32], n[32];
622 
623 	(void) snprintf(n, sizeof (n), ">%llu", asi->asi_n);
624 
625 	(void) printf("%-36s %3s %5s %3u %24s %s\n",
626 	    asi->asi_name, n, time2str(buf1, sizeof (buf1), asi->asi_t),
627 	    asi->asi_count, time2str(buf2, sizeof (buf2), asi->asi_delta),
628 	    (asi->asi_flags & FMD_ADM_SERD_FIRED) ? "fire" : "pend");
629 
630 	return (0);
631 }
632 
633 static void
634 stat_mod_serd(const char *name)
635 {
636 	(void) printf("%-36s %3s %5s %3s %24s %4s\n",
637 	    "NAME", ">N", "T", "CNT", "DELTA", "STAT");
638 
639 	if (fmd_adm_serd_iter(g_adm, name, stat_one_serd, NULL) != 0)
640 		die("failed to retrieve serd engines for %s", name);
641 }
642 
643 static int
644 getint(const char *name, const char *s)
645 {
646 	long val;
647 	char *p;
648 
649 	errno = 0;
650 	val = strtol(s, &p, 10);
651 
652 	if (errno != 0 || p == s || *p != '\0' || val < 0 || val > INT_MAX) {
653 		(void) fprintf(stderr, "%s: invalid %s argument -- %s\n",
654 		    g_pname, name, s);
655 		exit(FMSTAT_EXIT_USAGE);
656 	}
657 
658 	return ((int)val);
659 }
660 
661 static uint32_t
662 getu32(const char *name, const char *s)
663 {
664 	u_longlong_t val;
665 	char *p;
666 
667 	errno = 0;
668 	val = strtoull(s, &p, 0);
669 
670 	if (errno != 0 || p == s || *p != '\0' || val > UINT32_MAX) {
671 		(void) fprintf(stderr, "%s: invalid %s argument -- %s\n",
672 		    g_pname, name, s);
673 		exit(FMSTAT_EXIT_USAGE);
674 	}
675 
676 	return ((uint32_t)val);
677 }
678 
679 static int
680 usage(FILE *fp)
681 {
682 	(void) fprintf(fp, "Usage: %s [-astTz] [-m module] "
683 	    "[-P prog] [-d d|u] [interval [count]]\n\n", g_pname);
684 
685 	(void) fprintf(fp,
686 	    "\t-a show all statistics, including those kept by fmd\n"
687 	    "\t-d display a timestamp in date (d) or unix time_t (u)\n"
688 	    "\t-m show module-specific statistics\n"
689 	    "\t-P connect to alternate fmd program\n"
690 	    "\t-s show module-specific serd engines\n"
691 	    "\t-t show transport-specific statistics\n"
692 	    "\t-T show transport modules and authorities\n"
693 	    "\t-z suppress zero-valued statistics\n");
694 
695 	return (FMSTAT_EXIT_USAGE);
696 }
697 
698 int
699 main(int argc, char *argv[])
700 {
701 	int opt_a = 0, opt_s = 0, opt_t = 0, opt_T = 0, opt_z = 0;
702 	const char *opt_m = NULL;
703 	int msec = 0, iter = 1;
704 
705 	uint32_t program;
706 	char *p;
707 	int c;
708 
709 	if ((p = strrchr(argv[0], '/')) == NULL)
710 		g_pname = argv[0];
711 	else
712 		g_pname = p + 1;
713 
714 	if ((p = getenv("FMD_PROGRAM")) != NULL)
715 		program = getu32("$FMD_PROGRAM", p);
716 	else
717 		program = FMD_ADM_PROGRAM;
718 
719 	(void) setlocale(LC_ALL, "");
720 	(void) textdomain(TEXT_DOMAIN);
721 
722 	while ((c = getopt(argc, argv, "ad:m:P:stTz")) != EOF) {
723 		switch (c) {
724 		case 'a':
725 			opt_a++;
726 			break;
727 		case 'd':
728 			if (optarg) {
729 				if (*optarg == 'u')
730 					timestamp_fmt = UDATE;
731 				else if (*optarg == 'd')
732 					timestamp_fmt = DDATE;
733 				else
734 					return (usage(stderr));
735 			} else {
736 				return (usage(stderr));
737 			}
738 			break;
739 		case 'm':
740 			opt_m = optarg;
741 			break;
742 		case 'P':
743 			program = getu32("program", optarg);
744 			break;
745 		case 's':
746 			opt_s++;
747 			break;
748 		case 't':
749 			opt_t++;
750 			break;
751 		case 'T':
752 			opt_T++;
753 			break;
754 		case 'z':
755 			opt_z++;
756 			break;
757 		default:
758 			return (usage(stderr));
759 		}
760 	}
761 
762 	if (optind < argc) {
763 		msec = getint("interval", argv[optind++]) * MILLISEC;
764 		iter = -1;
765 	}
766 
767 	if (optind < argc)
768 		iter = getint("count", argv[optind++]);
769 
770 	if (optind < argc)
771 		return (usage(stderr));
772 
773 	if (opt_t != 0 && (opt_m != NULL || opt_s != 0)) {
774 		(void) fprintf(stderr,
775 		    "%s: -t cannot be used with -m or -s\n", g_pname);
776 		return (FMSTAT_EXIT_USAGE);
777 	}
778 
779 	if (opt_t != 0 && opt_T != 0) {
780 		(void) fprintf(stderr,
781 		    "%s: -t and -T are mutually exclusive options\n", g_pname);
782 		return (FMSTAT_EXIT_USAGE);
783 	}
784 
785 	if (opt_m == NULL && opt_s != 0) {
786 		(void) fprintf(stderr,
787 		    "%s: -s requires -m <module>\n", g_pname);
788 		return (FMSTAT_EXIT_USAGE);
789 	}
790 
791 	if ((g_adm = fmd_adm_open(NULL, program, FMD_ADM_VERSION)) == NULL)
792 		die(NULL); /* fmd_adm_errmsg() has enough info */
793 
794 	while (iter < 0 || iter-- > 0) {
795 		if (timestamp_fmt != NODATE)
796 			print_timestamp(timestamp_fmt);
797 		if (opt_s)
798 			stat_mod_serd(opt_m);
799 		else if (opt_T)
800 			stat_xprt_auth(opt_m);
801 		else if (opt_a || opt_m)
802 			stat_mod(opt_m, opt_a, opt_z);
803 		else if (opt_t)
804 			stat_xprt();
805 		else
806 			stat_fmd();
807 
808 		if (iter != 0) {
809 			(void) poll(NULL, 0, msec);
810 			(void) putchar('\n');
811 		}
812 	}
813 
814 	fmd_adm_close(g_adm);
815 	return (FMSTAT_EXIT_SUCCESS);
816 }
817