xref: /netbsd-src/usr.sbin/envstat/envstat.c (revision 7fa608457b817eca6e0977b37f758ae064f3c99c)
1 /* $NetBSD: envstat.c,v 1.57 2007/11/03 23:05:22 xtraeme Exp $ */
2 
3 /*-
4  * Copyright (c) 2007 Juan Romero Pardines.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 /*
29  * TODO
30  *
31  *  o Some checks should be added to ensure that the user does not
32  *    set unwanted values for the critical limits.
33  */
34 
35 #include <sys/cdefs.h>
36 #ifndef lint
37 __RCSID("$NetBSD: envstat.c,v 1.57 2007/11/03 23:05:22 xtraeme Exp $");
38 #endif /* not lint */
39 
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <stdbool.h>
43 #include <string.h>
44 #include <unistd.h>
45 #include <fcntl.h>
46 #include <err.h>
47 #include <errno.h>
48 #include <syslog.h>
49 #include <prop/proplib.h>
50 #include <sys/envsys.h>
51 
52 #include "envstat.h"
53 
54 #define _PATH_DEV_SYSMON	"/dev/sysmon"
55 
56 #define ENVSYS_DFLAG	0x00000001	/* list registered devices */
57 #define ENVSYS_FFLAG	0x00000002	/* show temp in farenheit */
58 #define ENVSYS_LFLAG	0x00000004	/* list sensors */
59 #define ENVSYS_XFLAG	0x00000008	/* externalize dictionary */
60 #define ENVSYS_IFLAG 	0x00000010	/* skips invalid sensors */
61 #define ENVSYS_SFLAG	0x00000020	/* removes all properties set */
62 
63 struct envsys_sensor {
64 	bool	invalid;
65 	bool	visible;
66 	bool	percentage;
67 	int32_t	cur_value;
68 	int32_t	max_value;
69 	int32_t	min_value;
70 	int32_t	avg_value;
71 	int32_t critcap_value;
72 	int32_t	critmin_value;
73 	int32_t	critmax_value;
74 	char	desc[ENVSYS_DESCLEN];
75 	char	type[ENVSYS_DESCLEN];
76 	char	drvstate[ENVSYS_DESCLEN];
77 	char	battcap[ENVSYS_DESCLEN];
78 	char 	dvname[ENVSYS_DESCLEN];
79 };
80 
81 static unsigned int interval, flags, width;
82 static char *mydevname, *sensors;
83 static struct envsys_sensor *gesen;
84 static size_t gnelems, newsize;
85 
86 static int parse_dictionary(int);
87 static int send_dictionary(FILE *, int);
88 static int find_sensors(prop_array_t, const char *);
89 static void print_sensors(struct envsys_sensor *, size_t, const char *);
90 static int check_sensors(struct envsys_sensor *, char *, size_t);
91 static int usage(void);
92 
93 
94 int main(int argc, char **argv)
95 {
96 	prop_dictionary_t dict;
97 	int c, fd, rval;
98 	char *endptr, *configfile = NULL;
99 	FILE *cf;
100 
101 	rval = flags = interval = width = 0;
102 	newsize = gnelems = 0;
103 	gesen = NULL;
104 
105 	setprogname(argv[0]);
106 
107 	while ((c = getopt(argc, argv, "c:Dd:fIi:lrSs:w:x")) != -1) {
108 		switch (c) {
109 		case 'c':	/* configuration file */
110 			configfile = strdup(optarg);
111 			if (configfile == NULL)
112 				err(EXIT_FAILURE, "strdup");
113 			break;
114 		case 'D':	/* list registered devices */
115 			flags |= ENVSYS_DFLAG;
116 			break;
117 		case 'd':	/* show sensors of a specific device */
118 			mydevname = strdup(optarg);
119 			if (mydevname == NULL)
120 				err(EXIT_FAILURE, "strdup");
121 			break;
122 		case 'f':	/* display temperature in Farenheit */
123 			flags |= ENVSYS_FFLAG;
124 			break;
125 		case 'I':	/* Skips invalid sensors */
126 			flags |= ENVSYS_IFLAG;
127 			break;
128 		case 'i':	/* wait time between intervals */
129 			interval = (unsigned int)strtoul(optarg, &endptr, 10);
130 			if (*endptr != '\0')
131 				errx(EXIT_FAILURE, "bad interval '%s'", optarg);
132 			break;
133 		case 'l':	/* list sensors */
134 			flags |= ENVSYS_LFLAG;
135 			break;
136 		case 'r':
137 			/*
138 			 * This flag doesn't do anything... it's only here for
139 			 * compatibility with the old implementation.
140 			 */
141 			break;
142 		case 'S':
143 			flags |= ENVSYS_SFLAG;
144 			break;
145 		case 's':	/* only show specified sensors */
146 			sensors = strdup(optarg);
147 			if (sensors == NULL)
148 				err(EXIT_FAILURE, "strdup");
149 			break;
150 		case 'w':	/* width value for the lines */
151 			width = strtoul(optarg, &endptr, 10);
152 			if (*endptr != '\0')
153 				errx(EXIT_FAILURE, "bad width '%s'", optarg);
154 			break;
155 		case 'x':	/* print the dictionary in raw format */
156 			flags |= ENVSYS_XFLAG;
157 			break;
158 		case '?':
159 		default:
160 			usage();
161 			/* NOTREACHED */
162 		}
163 	}
164 
165 	argc -= optind;
166 	argv += optind;
167 
168 	if (argc > 0)
169 		usage();
170 
171 	if ((fd = open(_PATH_DEV_SYSMON, O_RDONLY)) == -1)
172 		err(EXIT_FAILURE, "%s", _PATH_DEV_SYSMON);
173 
174 	if (flags & ENVSYS_XFLAG) {
175 		rval = prop_dictionary_recv_ioctl(fd,
176 						  ENVSYS_GETDICTIONARY,
177 						  &dict);
178 		if (rval)
179 			errx(EXIT_FAILURE, "%s", strerror(rval));
180 
181 		config_dict_dump(dict);
182 
183 	} else if (flags & ENVSYS_SFLAG) {
184 		(void)close(fd);
185 
186 		if ((fd = open(_PATH_DEV_SYSMON, O_RDWR)) == -1)
187 			err(EXIT_FAILURE, "%s", _PATH_DEV_SYSMON);
188 
189 		dict = prop_dictionary_create();
190 		if (!dict)
191 			err(EXIT_FAILURE, "prop_dictionary_create");
192 
193 		rval = prop_dictionary_set_bool(dict,
194 						"envsys-remove-props",
195 					        true);
196 		if (!rval)
197 			err(EXIT_FAILURE, "prop_dict_set_bool");
198 
199 		rval = prop_dictionary_send_ioctl(dict, fd, ENVSYS_REMOVEPROPS);
200 		if (rval)
201 			warnx("%s", strerror(rval));
202 
203 	} else if (configfile) {
204 		/*
205 		 * Parse the configuration file.
206 		 */
207 		if ((cf = fopen(configfile, "r")) == NULL) {
208 			syslog(LOG_ERR, "fopen failed: %s", strerror(errno));
209 			errx(EXIT_FAILURE, "%s", strerror(errno));
210 		}
211 
212 		rval = send_dictionary(cf, fd);
213 		(void)fclose(cf);
214 
215 #define MISSING_FLAG()					\
216 do {							\
217 	if (sensors && !mydevname)			\
218 		errx(EXIT_FAILURE, "-s requires -d");	\
219 } while (/* CONSTCOND */ 0)
220 
221 	} else if (interval) {
222 		MISSING_FLAG();
223 		for (;;) {
224 			rval = parse_dictionary(fd);
225 			if (rval)
226 				break;
227 
228 			(void)fflush(stdout);
229 			(void)sleep(interval);
230 		}
231 	} else {
232 		MISSING_FLAG();
233 		rval = parse_dictionary(fd);
234 	}
235 
236 	if (sensors)
237 		free(sensors);
238 	if (mydevname)
239 		free(mydevname);
240 	(void)close(fd);
241 
242 	return rval ? EXIT_FAILURE : EXIT_SUCCESS;
243 }
244 
245 static int
246 send_dictionary(FILE *cf, int fd)
247 {
248 	prop_dictionary_t kdict, udict;
249 	int error = 0;
250 
251 	error = prop_dictionary_recv_ioctl(fd, ENVSYS_GETDICTIONARY, &kdict);
252       	if (error)
253 		return error;
254 
255 	config_parse(cf, kdict);
256 
257 	/*
258 	 * Dictionary built by the parser from the configuration file.
259 	 */
260 	udict = config_dict_parsed();
261 
262 	/*
263 	 * Close the read only descriptor and open a new one read write.
264 	 */
265 	(void)close(fd);
266 	if ((fd = open(_PATH_DEV_SYSMON, O_RDWR)) == -1) {
267 		error = errno;
268 		warn("%s", _PATH_DEV_SYSMON);
269 		return error;
270 	}
271 
272 	/*
273 	 * Send our dictionary to the kernel then.
274 	 */
275 	error = prop_dictionary_send_ioctl(udict, fd, ENVSYS_SETDICTIONARY);
276 	if (error)
277 		warnx("%s", strerror(error));
278 
279 	prop_object_release(udict);
280 	return error;
281 }
282 
283 static int
284 parse_dictionary(int fd)
285 {
286 	prop_array_t array;
287 	prop_dictionary_t dict;
288 	prop_object_iterator_t iter;
289 	prop_object_t obj;
290 	const char *dnp = NULL;
291 	int rval = 0;
292 
293 	/* receive dictionary from kernel */
294 	rval = prop_dictionary_recv_ioctl(fd, ENVSYS_GETDICTIONARY, &dict);
295 	if (rval)
296 		return rval;
297 
298 	if (prop_dictionary_count(dict) == 0) {
299 		warnx("no drivers registered");
300 		goto out;
301 	}
302 
303 	if (mydevname) {
304 		obj = prop_dictionary_get(dict, mydevname);
305 		if (prop_object_type(obj) != PROP_TYPE_ARRAY) {
306 			warnx("unknown device `%s'", mydevname);
307 			rval = EINVAL;
308 			goto out;
309 		}
310 
311 		rval = find_sensors(obj, mydevname);
312 		if (rval)
313 			goto out;
314 
315 		if ((flags & ENVSYS_LFLAG) == 0)
316 			print_sensors(gesen, gnelems, mydevname);
317 		if (interval)
318 			(void)printf("\n");
319 	} else {
320 		iter = prop_dictionary_iterator(dict);
321 		if (iter == NULL) {
322 			rval = EINVAL;
323 			goto out;
324 		}
325 
326 		/* iterate over the dictionary returned by the kernel */
327 		while ((obj = prop_object_iterator_next(iter)) != NULL) {
328 
329 			array = prop_dictionary_get_keysym(dict, obj);
330 			if (prop_object_type(array) != PROP_TYPE_ARRAY) {
331 				warnx("no sensors found");
332 				rval = EINVAL;
333 				goto out;
334 			}
335 
336 			dnp = prop_dictionary_keysym_cstring_nocopy(obj);
337 
338 			if (flags & ENVSYS_DFLAG) {
339 				(void)printf("%s\n", dnp);
340 				continue;
341 			} else {
342 				(void)printf("[%s]\n", dnp);
343 				rval = find_sensors(array, dnp);
344 				if (rval)
345 					goto out;
346 			}
347 
348 			if ((flags & ENVSYS_LFLAG) == 0)
349 				print_sensors(gesen, gnelems, dnp);
350 			if (interval)
351 				(void)printf("\n");
352 		}
353 
354 		prop_object_iterator_release(iter);
355 	}
356 
357 out:
358 	if (gesen) {
359 		free(gesen);
360 		gesen = NULL;
361 		gnelems = 0;
362 		newsize = 0;
363 	}
364 	prop_object_release(dict);
365 	return rval;
366 }
367 
368 static int
369 find_sensors(prop_array_t array, const char *dvname)
370 {
371 	prop_object_iterator_t iter;
372 	prop_object_t obj, obj1;
373 	prop_string_t state, desc = NULL;
374 	struct envsys_sensor *esen = NULL;
375 	int rval = 0;
376 	char *str = NULL;
377 
378 	newsize += prop_array_count(array) * sizeof(*gesen);
379 	esen = realloc(gesen, newsize);
380 	if (esen == NULL) {
381 		if (gesen)
382 			free(gesen);
383 		gesen = NULL;
384 		return ENOMEM;
385 	}
386 	gesen = esen;
387 
388 	iter = prop_array_iterator(array);
389 	if (!iter)
390 		return EINVAL;
391 
392 	/* iterate over the array of dictionaries */
393 	while ((obj = prop_object_iterator_next(iter)) != NULL) {
394 
395 		/* copy device name */
396 		(void)strlcpy(gesen[gnelems].dvname, dvname,
397 		    sizeof(gesen[gnelems].dvname));
398 
399 		gesen[gnelems].visible = false;
400 
401 		/* check sensor's state */
402 		state = prop_dictionary_get(obj, "state");
403 
404 		/* mark invalid sensors */
405 		if (prop_string_equals_cstring(state, "invalid"))
406 			gesen[gnelems].invalid = true;
407 		else
408 			gesen[gnelems].invalid = false;
409 
410 		/* description string */
411 		desc = prop_dictionary_get(obj, "description");
412 		if (desc) {
413 			/* copy description */
414 			(void)strlcpy(gesen[gnelems].desc,
415 			    prop_string_cstring_nocopy(desc),
416 		    	    sizeof(gesen[gnelems].desc));
417 		} else
418 			continue;
419 
420 		/* type string */
421 		obj1  = prop_dictionary_get(obj, "type");
422 		/* copy type */
423 		(void)strlcpy(gesen[gnelems].type,
424 		    prop_string_cstring_nocopy(obj1),
425 		    sizeof(gesen[gnelems].type));
426 
427 		/* get current drive state string */
428 		obj1 = prop_dictionary_get(obj, "drive-state");
429 		if (obj1)
430 			(void)strlcpy(gesen[gnelems].drvstate,
431 			    prop_string_cstring_nocopy(obj1),
432 			    sizeof(gesen[gnelems].drvstate));
433 
434 		/* get current battery capacity string */
435 		obj1 = prop_dictionary_get(obj, "battery-capacity");
436 		if (obj1)
437 			(void)strlcpy(gesen[gnelems].battcap,
438 			    prop_string_cstring_nocopy(obj1),
439 			    sizeof(gesen[gnelems].battcap));
440 
441 		/* get current value */
442 		obj1 = prop_dictionary_get(obj, "cur-value");
443 		gesen[gnelems].cur_value = prop_number_integer_value(obj1);
444 
445 		/* get max value */
446 		obj1 = prop_dictionary_get(obj, "max-value");
447 		if (obj1)
448 			gesen[gnelems].max_value =
449 			    prop_number_integer_value(obj1);
450 		else
451 			gesen[gnelems].max_value = 0;
452 
453 		/* get min value */
454 		obj1 = prop_dictionary_get(obj, "min-value");
455 		if (obj1)
456 			gesen[gnelems].min_value =
457 			    prop_number_integer_value(obj1);
458 		else
459 			gesen[gnelems].min_value = 0;
460 
461 		/* get avg value */
462 		obj1 = prop_dictionary_get(obj, "avg-value");
463 		if (obj1)
464 			gesen[gnelems].avg_value =
465 			    prop_number_integer_value(obj1);
466 		else
467 			gesen[gnelems].avg_value = 0;
468 
469 		/* get percentage flag */
470 		obj1 = prop_dictionary_get(obj, "want-percentage");
471 		if (obj1)
472 			gesen[gnelems].percentage = prop_bool_true(obj1);
473 
474 		/* get critical max value if available */
475 		obj1 = prop_dictionary_get(obj, "critical-max");
476 		if (obj1) {
477 			gesen[gnelems].critmax_value =
478 			    prop_number_integer_value(obj1);
479 		} else
480 			gesen[gnelems].critmax_value = 0;
481 
482 		/* get critical min value if available */
483 		obj1 = prop_dictionary_get(obj, "critical-min");
484 		if (obj1) {
485 			gesen[gnelems].critmin_value =
486 			    prop_number_integer_value(obj1);
487 		} else
488 			gesen[gnelems].critmin_value = 0;
489 
490 		/* get critical capacity value if available */
491 		obj1 = prop_dictionary_get(obj, "critical-capacity");
492 		if (obj1) {
493 			gesen[gnelems].critcap_value =
494 			    prop_number_integer_value(obj1);
495 		} else
496 			gesen[gnelems].critcap_value = 0;
497 
498 		/* pass to the next struct and increase the counter */
499 		gnelems++;
500 
501 		/* print sensor names if -l was given */
502 		if (flags & ENVSYS_LFLAG) {
503 			if (width)
504 				(void)printf("%*s\n", width,
505 				    prop_string_cstring_nocopy(desc));
506 			else
507 				(void)printf("%s\n",
508 				    prop_string_cstring_nocopy(desc));
509 		}
510 	}
511 
512 	/* free memory */
513 	prop_object_iterator_release(iter);
514 
515 	/*
516 	 * if -s was specified, we need a way to mark if a sensor
517 	 * was found.
518 	 */
519 	if (sensors) {
520 		str = strdup(sensors);
521 		if (!str)
522 			return ENOMEM;
523 
524 		rval = check_sensors(gesen, str, gnelems);
525 		free(str);
526 	}
527 
528 	return rval;
529 }
530 
531 static int
532 check_sensors(struct envsys_sensor *es, char *str, size_t nelems)
533 {
534 	int i;
535 	char *sname;
536 
537 	sname = strtok(str, ",");
538 	while (sname) {
539 		for (i = 0; i < nelems; i++) {
540 			if (strcmp(sname, es[i].desc) == 0) {
541 				es[i].visible = true;
542 				break;
543 			}
544 		}
545 		if (i >= nelems) {
546 			if (mydevname) {
547 				warnx("unknown sensor `%s' for device `%s'",
548 				    sname, mydevname);
549 				return EINVAL;
550 			}
551 		}
552 		sname = strtok(NULL, ",");
553 	}
554 
555 	/* check if all sensors were ok, and error out if not */
556 	for (i = 0; i < nelems; i++) {
557 		if (es[i].visible)
558 			return 0;
559 	}
560 
561 	warnx("no sensors selected to display");
562 	return EINVAL;
563 }
564 
565 static void
566 print_sensors(struct envsys_sensor *es, size_t nelems, const char *dvname)
567 {
568 	size_t maxlen = 0;
569 	double temp = 0;
570 	const char *invalid = "N/A";
571 	const char *degrees = NULL;
572 	int i;
573 
574 	/* find the longest description */
575 	for (i = 0; i < nelems; i++) {
576 		if (strlen(es[i].desc) > maxlen)
577 			maxlen = strlen(es[i].desc);
578 	}
579 
580 	if (width)
581 		maxlen = width;
582 
583 	/* print the sensors */
584 	for (i = 0; i < nelems; i++) {
585 		/* skip sensors that don't belong to device 'dvname' */
586 		if (strcmp(es[i].dvname, dvname))
587 			continue;
588 
589 		/* skip sensors that were not marked as visible */
590 		if (sensors && !es[i].visible)
591 			continue;
592 
593 		/* Do not print invalid sensors if -I is set */
594 		if ((flags & ENVSYS_IFLAG) && es[i].invalid)
595 			continue;
596 
597 		(void)printf("%s%*.*s", mydevname ? "" : "  ", (int)maxlen,
598 		    (int)maxlen, es[i].desc);
599 
600 		if (es[i].invalid) {
601 			(void)printf(": %10s\n", invalid);
602 			continue;
603 		}
604 
605 		/*
606 		 * Indicator and Battery charge sensors.
607 		 */
608 		if ((strcmp(es[i].type, "Indicator") == 0) ||
609 		    (strcmp(es[i].type, "Battery charge") == 0)) {
610 
611 			(void)printf(": %10s", es[i].cur_value ? "ON" : "OFF");
612 
613 /* converts the value to degC or degF */
614 #define CONVERTTEMP(a, b, c)					\
615 do {								\
616 	if (b) 							\
617 		(a) = ((b) / 1000000.0) - 273.15;		\
618 	if (flags & ENVSYS_FFLAG) {				\
619 		if (b)						\
620 			(a) = (9.0 / 5.0) * (a) + 32.0;		\
621 		(c) = "degF";					\
622 	} else							\
623 		(c) = "degC";					\
624 } while (/* CONSTCOND */ 0)
625 
626 
627 		/* temperatures */
628 		} else if (strcmp(es[i].type, "Temperature") == 0) {
629 
630 			CONVERTTEMP(temp, es[i].cur_value, degrees);
631 			(void)printf(": %10.3f %s", temp, degrees);
632 
633 			if (es[i].critmax_value || es[i].critmin_value)
634 				(void)printf("  ");
635 
636 			if (es[i].critmax_value) {
637 				CONVERTTEMP(temp, es[i].critmax_value, degrees);
638 				(void)printf("max: %8.3f %s  ", temp, degrees);
639 			}
640 
641 			if (es[i].critmin_value) {
642 				CONVERTTEMP(temp, es[i].critmin_value, degrees);
643 				(void)printf("min: %8.3f %s", temp, degrees);
644 			}
645 #undef CONVERTTEMP
646 
647 		/* fans */
648 		} else if (strcmp(es[i].type, "Fan") == 0) {
649 
650 			(void)printf(": %10u RPM", es[i].cur_value);
651 
652 			if (es[i].critmax_value || es[i].critmin_value)
653 				(void)printf("   ");
654 			if (es[i].critmax_value)
655 				(void)printf("max: %8u RPM   ",
656 				    es[i].critmax_value);
657 			if (es[i].critmin_value)
658 				(void)printf("min: %8u RPM",
659 				    es[i].critmin_value);
660 
661 		/* integers */
662 		} else if (strcmp(es[i].type, "Integer") == 0) {
663 
664 			(void)printf(": %10d", es[i].cur_value);
665 
666 		/* drives  */
667 		} else if (strcmp(es[i].type, "Drive") == 0) {
668 
669 			(void)printf(": %10s", es[i].drvstate);
670 
671 		/* Battery capacity */
672 		} else if (strcmp(es[i].type, "Battery capacity") == 0) {
673 
674 			(void)printf(": %10s", es[i].battcap);
675 
676 		/* everything else */
677 		} else {
678 			const char *type;
679 
680 			if (strcmp(es[i].type, "Voltage DC") == 0)
681 				type = "V";
682 			else if (strcmp(es[i].type, "Voltage AC") == 0)
683 				type = "VAC";
684 			else if (strcmp(es[i].type, "Ampere") == 0)
685 				type = "A";
686 			else if (strcmp(es[i].type, "Watts") == 0)
687 				type = "W";
688 			else if (strcmp(es[i].type, "Ohms") == 0)
689 				type = "Ohms";
690 			else if (strcmp(es[i].type, "Watt hour") == 0)
691 				type = "Wh";
692 			else if (strcmp(es[i].type, "Ampere hour") == 0)
693 				type = "Ah";
694 			else
695 				type = NULL;
696 
697 			(void)printf(": %10.3f %s",
698 			    es[i].cur_value / 1000000.0, type);
699 
700 			if (es[i].percentage && es[i].max_value) {
701 				(void)printf(" (%5.2f%%)",
702 				    (es[i].cur_value * 100.0) /
703 				    es[i].max_value);
704 			}
705 
706 			if (es[i].critcap_value) {
707 				(void)printf(" critical (%5.2f%%)",
708 				    (es[i].critcap_value * 100.0) /
709 				    es[i].max_value);
710 			}
711 
712 			if (es[i].critmax_value || es[i].critmin_value)
713 				(void)printf("     ");
714 			if (es[i].critmax_value)
715 				(void)printf("max: %8.3f %s     ",
716 				    es[i].critmax_value / 1000000.0,
717 				    type);
718 			if (es[i].critmin_value)
719 				(void)printf("min: %8.3f %s",
720 				    es[i].critmin_value / 1000000.0,
721 				    type);
722 
723 		}
724 
725 		(void)printf("\n");
726 	}
727 }
728 
729 static int
730 usage(void)
731 {
732 	(void)fprintf(stderr, "Usage: %s [-DfIlrSx] ", getprogname());
733 	(void)fprintf(stderr, "[-c file] [-d device] [-i interval] ");
734 	(void)fprintf(stderr, "[-s sensor,...] [-w width]\n");
735 	exit(EXIT_FAILURE);
736 	/* NOTREACHED */
737 }
738