xref: /illumos-gate/usr/src/cmd/fm/fmstat/common/fmstat.c (revision 7c478bd9)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <fm/fmd_adm.h>
30 
31 #include <strings.h>
32 #include <limits.h>
33 #include <stdlib.h>
34 #include <stdarg.h>
35 #include <stdio.h>
36 #include <errno.h>
37 #include <poll.h>
38 
39 #define	FMSTAT_EXIT_SUCCESS	0
40 #define	FMSTAT_EXIT_ERROR	1
41 #define	FMSTAT_EXIT_USAGE	2
42 
43 static const struct stats {
44 	fmd_stat_t loadtime;
45 	fmd_stat_t snaptime;
46 	fmd_stat_t dispatched;
47 	fmd_stat_t dequeued;
48 	fmd_stat_t prdequeued;
49 	fmd_stat_t accepted;
50 	fmd_stat_t memtotal;
51 	fmd_stat_t buftotal;
52 	fmd_stat_t caseopen;
53 	fmd_stat_t casesolved;
54 	fmd_stat_t wcnt;
55 	fmd_stat_t wtime;
56 	fmd_stat_t wlentime;
57 	fmd_stat_t wlastupdate;
58 	fmd_stat_t dtime;
59 	fmd_stat_t dlastupdate;
60 } stats_template = {
61 	{ "fmd.loadtime", FMD_TYPE_TIME },
62 	{ "fmd.snaptime", FMD_TYPE_TIME },
63 	{ "fmd.dispatched", FMD_TYPE_UINT64 },
64 	{ "fmd.dequeued", FMD_TYPE_UINT64 },
65 	{ "fmd.prdequeued", FMD_TYPE_UINT64 },
66 	{ "fmd.accepted", FMD_TYPE_UINT64 },
67 	{ "fmd.memtotal", FMD_TYPE_SIZE },
68 	{ "fmd.buftotal", FMD_TYPE_SIZE },
69 	{ "fmd.caseopen", FMD_TYPE_UINT64 },
70 	{ "fmd.casesolved", FMD_TYPE_UINT64 },
71 	{ "fmd.wcnt", FMD_TYPE_UINT32 },
72 	{ "fmd.wtime", FMD_TYPE_TIME },
73 	{ "fmd.wlentime", FMD_TYPE_TIME },
74 	{ "fmd.wlastupdate", FMD_TYPE_TIME },
75 	{ "fmd.dtime", FMD_TYPE_TIME },
76 	{ "fmd.dlastupdate", FMD_TYPE_TIME },
77 };
78 
79 static const char *g_pname;
80 static fmd_adm_t *g_adm;
81 
82 static struct modstats {
83 	char *m_name;
84 	struct modstats *m_next;
85 	struct stats m_stbuf[2];
86 	int m_stidx;
87 } *g_mods;
88 
89 static void
90 vwarn(const char *format, va_list ap)
91 {
92 	int err = errno;
93 
94 	(void) fprintf(stderr, "%s: ", g_pname);
95 
96 	if (format != NULL)
97 		(void) vfprintf(stderr, format, ap);
98 
99 	errno = err; /* restore errno for fmd_adm_errmsg() */
100 
101 	if (format == NULL)
102 		(void) fprintf(stderr, "%s\n", fmd_adm_errmsg(g_adm));
103 	else if (strchr(format, '\n') == NULL)
104 		(void) fprintf(stderr, ": %s\n", fmd_adm_errmsg(g_adm));
105 }
106 
107 /*PRINTFLIKE1*/
108 void
109 warn(const char *format, ...)
110 {
111 	va_list ap;
112 
113 	va_start(ap, format);
114 	vwarn(format, ap);
115 	va_end(ap);
116 }
117 
118 /*PRINTFLIKE1*/
119 void
120 die(const char *format, ...)
121 {
122 	va_list ap;
123 
124 	va_start(ap, format);
125 	vwarn(format, ap);
126 	va_end(ap);
127 
128 	fmd_adm_close(g_adm);
129 	exit(FMSTAT_EXIT_ERROR);
130 }
131 
132 static char *
133 time2str(char *buf, size_t len, uint64_t time)
134 {
135 	static const struct unit {
136 		const char *u_name;
137 		hrtime_t u_mul;
138 	} units[] = {
139 		{ "d",	NANOSEC * (hrtime_t)(24 * 60 * 60) },
140 		{ "h",	NANOSEC * (hrtime_t)(60 * 60) },
141 		{ "m",	NANOSEC * (hrtime_t)60 },
142 		{ "s",	NANOSEC / SEC },
143 		{ "ms",	NANOSEC / MILLISEC },
144 		{ "us",	NANOSEC / MICROSEC },
145 		{ "ns",	NANOSEC / NANOSEC },
146 	};
147 
148 	const struct unit *up;
149 
150 	for (up = units; time % up->u_mul != 0; up++)
151 		continue; /* find largest unit of which 'time' is a multiple */
152 
153 	(void) snprintf(buf, len, "%llu%s", time / up->u_mul, up->u_name);
154 	return (buf);
155 }
156 
157 static char *
158 size2str(char *buf, size_t len, uint64_t size)
159 {
160 	static const char units[] = "bKMGTPE";
161 	const uint64_t scale = 1024;
162 	const char *up = units;
163 	uint64_t osize = 0;
164 
165 	/*
166 	 * Convert the input size to a round number of the appropriately
167 	 * scaled units (saved in 'size') and a remainder (saved in 'osize').
168 	 */
169 	while (size >= scale && up < (units + sizeof (units) - 2)) {
170 		up++;
171 		osize = size;
172 		size = (size + (scale / 2)) / scale;
173 	}
174 
175 	/*
176 	 * Format the result using at most one decimal place and the unit
177 	 * depending upon the amount of remainder (same as df -h algorithm).
178 	 */
179 	if (osize != 0 && (osize / scale) < 10)
180 		(void) snprintf(buf, len, "%.1f%c", (float)osize / scale, *up);
181 	else if (size != 0)
182 		(void) snprintf(buf, len, "%llu%c", size, *up);
183 	else
184 		(void) snprintf(buf, len, "0");
185 
186 	return (buf);
187 }
188 
189 static uint64_t
190 u64delta(uint64_t old, uint64_t new)
191 {
192 	return (new >= old ? (new - old) : ((UINT64_MAX - old) + new + 1));
193 }
194 
195 /*ARGSUSED*/
196 static int
197 stat_one_fmd(const fmd_adm_modinfo_t *ami, void *ignored)
198 {
199 	static fmd_stat_t *t_beg = (fmd_stat_t *)(&stats_template + 0);
200 	static fmd_stat_t *t_end = (fmd_stat_t *)(&stats_template + 1);
201 
202 	fmd_stat_t *tsp, *nsp, *sp;
203 	struct stats *old, *new;
204 	char memsz[8], bufsz[8];
205 	double elapsed, wait, avg_w, avg_d, svc, pct_b, pct_w;
206 	uint64_t delta;
207 
208 	fmd_adm_stats_t ams;
209 	struct modstats *mp;
210 	char *name;
211 
212 	/*
213 	 * Take a snapshot of the statistics for this module and then set up
214 	 * 'old' and 'new' to point to the rotating persistent m_stbuf structs.
215 	 * If no matching module is found on our g_mods list, add a new module.
216 	 */
217 	if (fmd_adm_module_stats(g_adm, ami->ami_name, &ams) != 0) {
218 		warn("failed to retrieve statistics for %s", ami->ami_name);
219 		return (0); /* continue on to the next module */
220 	}
221 
222 	for (mp = g_mods; mp != NULL; mp = mp->m_next) {
223 		if (strcmp(mp->m_name, ami->ami_name) == 0)
224 			break;
225 	}
226 
227 	if (mp == NULL) {
228 		if ((mp = malloc(sizeof (struct modstats))) == NULL ||
229 		    (name = strdup(ami->ami_name)) == NULL) {
230 			warn("failed to allocate memory for %s", ami->ami_name);
231 			(void) fmd_adm_stats_free(g_adm, &ams);
232 			free(mp);
233 			return (0);
234 		}
235 
236 		bzero(mp, sizeof (struct modstats));
237 		mp->m_name = name;
238 		mp->m_next = g_mods;
239 		g_mods = mp;
240 	}
241 
242 	old = &mp->m_stbuf[mp->m_stidx];
243 	mp->m_stidx = 1 - mp->m_stidx;
244 	new = &mp->m_stbuf[mp->m_stidx];
245 
246 	/*
247 	 * The statistics can come in any order; we compare each one to the
248 	 * template of statistics of interest, find the matching ones, and copy
249 	 * their values into the appropriate slot of the 'new' stats.
250 	 */
251 	for (nsp = ams.ams_buf; nsp < ams.ams_buf + ams.ams_len; nsp++) {
252 		for (tsp = t_beg; tsp < t_end; tsp++) {
253 			if (strcmp(tsp->fmds_name, nsp->fmds_name) != 0)
254 				continue; /* continue until we match the name */
255 
256 			if (tsp->fmds_type != nsp->fmds_type) {
257 				warn("%s has unexpected type (%u != %u)\n",
258 				    tsp->fmds_name, tsp->fmds_type,
259 				    nsp->fmds_type);
260 			} else {
261 				sp = (fmd_stat_t *)new + (tsp - t_beg);
262 				sp->fmds_value = nsp->fmds_value;
263 			}
264 		}
265 	}
266 
267 	/*
268 	 * Compute the elapsed time by taking the delta between 'snaptime', or
269 	 * or between snaptime and loadtime if there is no previous snapshot.
270 	 * If delta is zero, set it to 1sec so we don't divide by zero later.
271 	 */
272 	delta = u64delta(old->snaptime.fmds_value.ui64 ?
273 	    old->snaptime.fmds_value.ui64 : old->loadtime.fmds_value.ui64,
274 	    new->snaptime.fmds_value.ui64);
275 
276 	elapsed = delta ? (double)delta : (double)NANOSEC;
277 
278 	/*
279 	 * Compute average wait queue len by taking the delta in the wait queue
280 	 * len * time products (wlentime stat) and dividing by the elapsed time.
281 	 */
282 	delta = u64delta(old->wlentime.fmds_value.ui64,
283 	    new->wlentime.fmds_value.ui64);
284 
285 	if (delta != 0)
286 		wait = (double)delta / elapsed;
287 	else
288 		wait = 0.0;
289 
290 	/*
291 	 * Compute average wait time by taking the delta in the wait queue time
292 	 * (wtime) and dividing by the delta in the number of dispatches.
293 	 */
294 	delta = u64delta(old->dispatched.fmds_value.ui64,
295 	    new->dispatched.fmds_value.ui64);
296 
297 	if (delta != 0) {
298 		avg_w = (double)u64delta(old->wtime.fmds_value.ui64,
299 		    new->wtime.fmds_value.ui64) / (double)delta;
300 	} else
301 		avg_w = 0.0;
302 
303 	/*
304 	 * Compute average dispatch time by taking the delta in the dispatch
305 	 * time (dtime) and dividing by the delta in the number of dequeues.
306 	 */
307 	delta = u64delta(old->dequeued.fmds_value.ui64,
308 	    new->dequeued.fmds_value.ui64);
309 
310 	if (delta != 0) {
311 		avg_d = (double)u64delta(old->dtime.fmds_value.ui64,
312 		    new->dtime.fmds_value.ui64) / (double)delta;
313 	} else
314 		avg_d = 0.0;
315 
316 	/*
317 	 * Finally compute the average overall service time by adding together
318 	 * the average wait and dispatch times and converting to milliseconds.
319 	 */
320 	svc = ((avg_w + avg_d) * (double)MILLISEC) / (double)NANOSEC;
321 
322 	/*
323 	 * Compute the %wait and %busy times by taking the delta in wait and
324 	 * busy times, dividing by the elapsed time, and multiplying by 100.
325 	 */
326 	delta = u64delta(old->wtime.fmds_value.ui64,
327 	    new->wtime.fmds_value.ui64);
328 
329 	if (delta != 0)
330 		pct_w = ((double)delta / elapsed) * 100.0;
331 	else
332 		pct_w = 0.0;
333 
334 	delta = u64delta(old->dtime.fmds_value.ui64,
335 	    new->dtime.fmds_value.ui64);
336 
337 	if (delta != 0)
338 		pct_b = ((double)delta / elapsed) * 100.0;
339 	else
340 		pct_b = 0.0;
341 
342 	/*
343 	 * Print the formatted line of statistics for this module based on
344 	 * our calculations, and then free the statistics we sampled.
345 	 */
346 	(void) printf("%-18s %7llu %7llu %4.1f %6.1f %3.0f %3.0f "
347 	    "%5llu %5llu %6s %6s\n", ami->ami_name,
348 	    u64delta(old->prdequeued.fmds_value.ui64,
349 	    new->prdequeued.fmds_value.ui64),
350 	    u64delta(old->accepted.fmds_value.ui64,
351 	    new->accepted.fmds_value.ui64),
352 	    wait, svc, pct_w, pct_b,
353 	    new->caseopen.fmds_value.ui64,
354 	    new->casesolved.fmds_value.ui64,
355 	    size2str(memsz, sizeof (memsz), new->memtotal.fmds_value.ui64),
356 	    size2str(bufsz, sizeof (bufsz), new->buftotal.fmds_value.ui64));
357 
358 	(void) fmd_adm_stats_free(g_adm, &ams);
359 	return (0);
360 }
361 
362 static void
363 stat_fmd(void)
364 {
365 	(void) printf("%-18s %7s %7s %4s %6s %3s %3s %5s %5s %6s %6s\n",
366 	    "module", "ev_recv", "ev_acpt", "wait", "svc_t", "%w", "%b",
367 	    "open", "solve", "memsz", "bufsz");
368 
369 	if (fmd_adm_module_iter(g_adm, stat_one_fmd, NULL) != 0)
370 		die("failed to retrieve list of modules");
371 }
372 
373 static void
374 stat_mod(const char *name, int aflag, int zflag)
375 {
376 	fmd_adm_stats_t ams;
377 	fmd_stat_t *sp;
378 	char buf[64];
379 
380 	if (fmd_adm_stats_read(g_adm, name, &ams) != 0) {
381 		die("failed to retrieve statistics for %s",
382 		    name ? name : "fmd(1M)");
383 	}
384 
385 	(void) printf("%20s %-16s %s\n", "NAME", "VALUE", "DESCRIPTION");
386 
387 	for (sp = ams.ams_buf; sp < ams.ams_buf + ams.ams_len; sp++) {
388 		if (aflag == 0 && strncmp(sp->fmds_name, "fmd.", 4) == 0)
389 			continue; /* skip fmd-internal stats unless -a used */
390 
391 		if (zflag) {
392 			switch (sp->fmds_type) {
393 			case FMD_TYPE_INT32:
394 			case FMD_TYPE_UINT32:
395 				if (sp->fmds_value.ui32 == 0)
396 					continue;
397 				break;
398 			case FMD_TYPE_INT64:
399 			case FMD_TYPE_UINT64:
400 			case FMD_TYPE_TIME:
401 			case FMD_TYPE_SIZE:
402 				if (sp->fmds_value.ui64 == 0)
403 					continue;
404 				break;
405 			case FMD_TYPE_STRING:
406 				if (sp->fmds_value.str == NULL ||
407 				    sp->fmds_value.str[0] == '\0')
408 					continue;
409 				break;
410 			}
411 		}
412 
413 		(void) printf("%20s ", sp->fmds_name);
414 
415 		switch (sp->fmds_type) {
416 		case FMD_TYPE_BOOL:
417 			(void) printf("%-16s",
418 			    sp->fmds_value.bool ? "true" : "false");
419 			break;
420 		case FMD_TYPE_INT32:
421 			(void) printf("%-16d", sp->fmds_value.i32);
422 			break;
423 		case FMD_TYPE_UINT32:
424 			(void) printf("%-16u", sp->fmds_value.ui32);
425 			break;
426 		case FMD_TYPE_INT64:
427 			(void) printf("%-16lld", sp->fmds_value.i64);
428 			break;
429 		case FMD_TYPE_UINT64:
430 			(void) printf("%-16llu", sp->fmds_value.ui64);
431 			break;
432 		case FMD_TYPE_STRING:
433 			(void) printf("%-16s", sp->fmds_value.str ?
434 			    sp->fmds_value.str : "<<null>>");
435 			break;
436 		case FMD_TYPE_TIME:
437 			(void) printf("%-16s",
438 			    time2str(buf, sizeof (buf), sp->fmds_value.ui64));
439 			break;
440 		case FMD_TYPE_SIZE:
441 			(void) printf("%-16s",
442 			    size2str(buf, sizeof (buf), sp->fmds_value.ui64));
443 			break;
444 		default:
445 			(void) snprintf(buf, sizeof (buf),
446 			    "<<type=%u>>\n", sp->fmds_type);
447 			(void) printf("%-16s", buf);
448 		}
449 
450 		(void) printf(" %s\n", sp->fmds_desc);
451 	}
452 
453 	(void) fmd_adm_stats_free(g_adm, &ams);
454 }
455 
456 /*ARGSUSED*/
457 static int
458 stat_one_serd(const fmd_adm_serdinfo_t *asi, void *ignored)
459 {
460 	char buf1[32], buf2[32], n[32];
461 
462 	(void) snprintf(n, sizeof (n), ">%llu", asi->asi_n);
463 
464 	(void) printf("%-36s %3s %5s %3u %24s %s\n",
465 	    asi->asi_name, n, time2str(buf1, sizeof (buf1), asi->asi_t),
466 	    asi->asi_count, time2str(buf2, sizeof (buf2), asi->asi_delta),
467 	    (asi->asi_flags & FMD_ADM_SERD_FIRED) ? "fire" : "pend");
468 
469 	return (0);
470 }
471 
472 static void
473 stat_mod_serd(const char *name)
474 {
475 	(void) printf("%-36s %3s %5s %3s %24s %4s\n",
476 	    "NAME", ">N", "T", "CNT", "DELTA", "STAT");
477 
478 	if (fmd_adm_serd_iter(g_adm, name, stat_one_serd, NULL) != 0)
479 		die("failed to retrieve serd engines for %s", name);
480 }
481 
482 static int
483 getint(const char *name, const char *s)
484 {
485 	long val;
486 	char *p;
487 
488 	errno = 0;
489 	val = strtol(s, &p, 10);
490 
491 	if (errno != 0 || p == s || *p != '\0' || val < 0 || val > INT_MAX) {
492 		(void) fprintf(stderr, "%s: invalid %s argument -- %s\n",
493 		    g_pname, name, s);
494 		exit(FMSTAT_EXIT_USAGE);
495 	}
496 
497 	return ((int)val);
498 }
499 
500 static uint32_t
501 getu32(const char *name, const char *s)
502 {
503 	u_longlong_t val;
504 	char *p;
505 
506 	errno = 0;
507 	val = strtoull(s, &p, 0);
508 
509 	if (errno != 0 || p == s || *p != '\0' || val > UINT32_MAX) {
510 		(void) fprintf(stderr, "%s: invalid %s argument -- %s\n",
511 		    g_pname, name, s);
512 		exit(FMSTAT_EXIT_USAGE);
513 	}
514 
515 	return ((uint32_t)val);
516 }
517 
518 static int
519 usage(FILE *fp)
520 {
521 	(void) fprintf(fp, "Usage: %s [-asz] [-m module] "
522 	    "[-P prog] [interval [count]]\n\n", g_pname);
523 
524 	(void) fprintf(fp,
525 	    "\t-a show all statistics, including those kept by fmd\n"
526 	    "\t-m show module-specific statistics\n"
527 	    "\t-P connect to alternate fmd program\n"
528 	    "\t-s show module-specific serd engines\n"
529 	    "\t-z suppress zero-valued statistics\n");
530 
531 	return (FMSTAT_EXIT_USAGE);
532 }
533 
534 int
535 main(int argc, char *argv[])
536 {
537 	int opt_a = 0, opt_s = 0, opt_z = 0;
538 	const char *opt_m = NULL;
539 	int msec = 0, iter = 1;
540 
541 	uint32_t program;
542 	char *p;
543 	int c;
544 
545 	if ((p = strrchr(argv[0], '/')) == NULL)
546 		g_pname = argv[0];
547 	else
548 		g_pname = p + 1;
549 
550 	if ((p = getenv("FMD_PROGRAM")) != NULL)
551 		program = getu32("$FMD_PROGRAM", p);
552 	else
553 		program = FMD_ADM_PROGRAM;
554 
555 	while ((c = getopt(argc, argv, "am:P:sz")) != EOF) {
556 		switch (c) {
557 		case 'a':
558 			opt_a++;
559 			break;
560 		case 'm':
561 			opt_m = optarg;
562 			break;
563 		case 'P':
564 			program = getu32("program", optarg);
565 			break;
566 		case 's':
567 			opt_s++;
568 			break;
569 		case 'z':
570 			opt_z++;
571 			break;
572 		default:
573 			return (usage(stderr));
574 		}
575 	}
576 
577 	if (optind < argc) {
578 		msec = getint("interval", argv[optind++]) * MILLISEC;
579 		iter = -1;
580 	}
581 
582 	if (optind < argc)
583 		iter = getint("count", argv[optind++]);
584 
585 	if (optind < argc)
586 		return (usage(stderr));
587 
588 	if (opt_m == NULL && opt_s != 0) {
589 		(void) fprintf(stderr,
590 		    "%s: -s requires -m <module>\n", g_pname);
591 		return (FMSTAT_EXIT_USAGE);
592 	}
593 
594 	if ((g_adm = fmd_adm_open(NULL, program, FMD_ADM_VERSION)) == NULL)
595 		die(NULL); /* fmd_adm_errmsg() has enough info */
596 
597 	while (iter < 0 || iter-- > 0) {
598 		if (opt_s)
599 			stat_mod_serd(opt_m);
600 		else if (opt_a || opt_m)
601 			stat_mod(opt_m, opt_a, opt_z);
602 		else
603 			stat_fmd();
604 
605 		if (iter != 0) {
606 			(void) poll(NULL, 0, msec);
607 			(void) putchar('\n');
608 		}
609 	}
610 
611 	fmd_adm_close(g_adm);
612 	return (FMSTAT_EXIT_SUCCESS);
613 }
614