xref: /openbsd/usr.bin/kstat/kstat.c (revision 7b6132c8)
1 /* $OpenBSD: kstat.c,v 1.14 2024/03/26 00:54:24 dlg Exp $ */
2 
3 /*
4  * Copyright (c) 2020 David Gwynne <dlg@openbsd.org>
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16 
17 #include <ctype.h>
18 #include <limits.h>
19 #include <signal.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <stddef.h>
23 #include <string.h>
24 #include <inttypes.h>
25 #include <fnmatch.h>
26 #include <fcntl.h>
27 #include <unistd.h>
28 #include <errno.h>
29 #include <err.h>
30 #include <vis.h>
31 
32 #include <sys/tree.h>
33 #include <sys/ioctl.h>
34 #include <sys/time.h>
35 #include <sys/queue.h>
36 
37 #include <sys/kstat.h>
38 
39 #ifndef roundup
40 #define roundup(x, y)		((((x)+((y)-1))/(y))*(y))
41 #endif
42 
43 #ifndef nitems
44 #define nitems(_a)		(sizeof((_a)) / sizeof((_a)[0]))
45 #endif
46 
47 #ifndef ISSET
48 #define ISSET(_i, _m)		((_i) & (_m))
49 #endif
50 
51 #ifndef SET
52 #define SET(_i, _m)		((_i) |= (_m))
53 #endif
54 
55 struct fmt_result {
56 	uint64_t		val;
57 	unsigned int		frac;
58 	unsigned int		exp;
59 };
60 
61 static void
fmt_thing(struct fmt_result * fr,uint64_t val,uint64_t chunk)62 fmt_thing(struct fmt_result *fr, uint64_t val, uint64_t chunk)
63 {
64 	unsigned int exp = 0;
65 	uint64_t rem = 0;
66 
67 	while (val > chunk) {
68 		rem = val % chunk;
69 		val /= chunk;
70 		exp++;
71 	}
72 
73 	fr->val = val;
74 	fr->exp = exp;
75 	fr->frac = (rem * 1000) / chunk;
76 }
77 
78 #define str_is_empty(_str)	(*(_str) == '\0')
79 
80 #define DEV_KSTAT "/dev/kstat"
81 
82 struct kstat_filter {
83 	TAILQ_ENTRY(kstat_filter)	 kf_entry;
84 	const char			*kf_provider;
85 	const char			*kf_name;
86 	unsigned int			 kf_flags;
87 #define KSTAT_FILTER_F_INST			(1 << 0)
88 #define KSTAT_FILTER_F_UNIT			(1 << 1)
89 	unsigned int			 kf_instance;
90 	unsigned int			 kf_unit;
91 };
92 
93 TAILQ_HEAD(kstat_filters, kstat_filter);
94 
95 struct kstat_entry {
96 	struct kstat_req	kstat;
97 	RBT_ENTRY(kstat_entry)	entry;
98 	int			serrno;
99 };
100 
101 RBT_HEAD(kstat_tree, kstat_entry);
102 
103 static inline int
kstat_cmp(const struct kstat_entry * ea,const struct kstat_entry * eb)104 kstat_cmp(const struct kstat_entry *ea, const struct kstat_entry *eb)
105 {
106 	const struct kstat_req *a = &ea->kstat;
107 	const struct kstat_req *b = &eb->kstat;
108 	int rv;
109 
110 	rv = strncmp(a->ks_provider, b->ks_provider, sizeof(a->ks_provider));
111 	if (rv != 0)
112 		return (rv);
113 	if (a->ks_instance > b->ks_instance)
114 		return (1);
115 	if (a->ks_instance < b->ks_instance)
116 		return (-1);
117 
118 	rv = strncmp(a->ks_name, b->ks_name, sizeof(a->ks_name));
119 	if (rv != 0)
120 		return (rv);
121 	if (a->ks_unit > b->ks_unit)
122 		return (1);
123 	if (a->ks_unit < b->ks_unit)
124 		return (-1);
125 
126 	return (0);
127 }
128 
129 RBT_PROTOTYPE(kstat_tree, kstat_entry, entry, kstat_cmp);
130 RBT_GENERATE(kstat_tree, kstat_entry, entry, kstat_cmp);
131 
132 static void handle_alrm(int);
133 static struct kstat_filter *
134 		kstat_filter_parse(char *);
135 static int	kstat_filter_entry(struct kstat_filters *,
136 		    const struct kstat_req *);
137 
138 static void	kstat_list(struct kstat_tree *, int, unsigned int,
139 		    struct kstat_filters *);
140 static void	kstat_print(struct kstat_tree *);
141 static void	kstat_read(struct kstat_tree *, int);
142 
143 __dead static void
usage(void)144 usage(void)
145 {
146 	extern char *__progname;
147 
148 	fprintf(stderr, "usage: %s [-w wait] "
149 	    "[name | provider:instance:name:unit] ...\n", __progname);
150 
151 	exit(1);
152 }
153 
154 int
main(int argc,char * argv[])155 main(int argc, char *argv[])
156 {
157 	struct kstat_filters kfs = TAILQ_HEAD_INITIALIZER(kfs);
158 	struct kstat_tree kt = RBT_INITIALIZER();
159 	unsigned int version;
160 	int fd;
161 	const char *errstr;
162 	int ch;
163 	struct itimerval itv;
164 	sigset_t empty, mask;
165 	int i;
166 	unsigned int wait = 0;
167 
168 	while ((ch = getopt(argc, argv, "w:")) != -1) {
169 		switch (ch) {
170 		case 'w':
171 			wait = strtonum(optarg, 1, UINT_MAX, &errstr);
172 			if (errstr != NULL)
173 				errx(1, "wait is %s: %s", errstr, optarg);
174 			break;
175 		default:
176 			usage();
177 		}
178 	}
179 
180 	argc -= optind;
181 	argv += optind;
182 
183 	for (i = 0; i < argc; i++) {
184 		struct kstat_filter *kf = kstat_filter_parse(argv[i]);
185 		TAILQ_INSERT_TAIL(&kfs, kf, kf_entry);
186 	}
187 
188 	fd = open(DEV_KSTAT, O_RDONLY);
189 	if (fd == -1)
190 		err(1, "%s", DEV_KSTAT);
191 
192 	if (ioctl(fd, KSTATIOC_VERSION, &version) == -1)
193 		err(1, "kstat version");
194 
195 	kstat_list(&kt, fd, version, &kfs);
196 	kstat_read(&kt, fd);
197 	kstat_print(&kt);
198 
199 	if (wait == 0)
200 		return (0);
201 
202 	if (signal(SIGALRM, handle_alrm) == SIG_ERR)
203 		err(1, "signal");
204 	sigemptyset(&empty);
205 	sigemptyset(&mask);
206 	sigaddset(&mask, SIGALRM);
207 	if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
208 		err(1, "sigprocmask");
209 
210 	itv.it_value.tv_sec = wait;
211 	itv.it_value.tv_usec = 0;
212 	itv.it_interval = itv.it_value;
213 	if (setitimer(ITIMER_REAL, &itv, NULL) == -1)
214 		err(1, "setitimer");
215 
216 	for (;;) {
217 		sigsuspend(&empty);
218 		kstat_read(&kt, fd);
219 		kstat_print(&kt);
220 	}
221 
222 	return (0);
223 }
224 
225 static struct kstat_filter *
kstat_filter_parse(char * arg)226 kstat_filter_parse(char *arg)
227 {
228 	struct kstat_filter *kf;
229 	const char *errstr;
230 	char *argv[4];
231 	size_t argc;
232 
233 	for (argc = 0; argc < nitems(argv); argc++) {
234 		char *s = strsep(&arg, ":");
235 		if (s == NULL)
236 			break;
237 
238 		argv[argc] = s;
239 	}
240 	if (arg != NULL)
241 		usage();
242 
243 	kf = malloc(sizeof(*kf));
244 	if (kf == NULL)
245 		err(1, NULL);
246 
247 	memset(kf, 0, sizeof(*kf));
248 
249 	switch (argc) {
250 	case 1:
251 		if (str_is_empty(argv[0]))
252 			errx(1, "empty name");
253 
254 		kf->kf_name = argv[0];
255 		break;
256 	case 4:
257 		if (!str_is_empty(argv[0]))
258 			kf->kf_provider = argv[0];
259 		if (!str_is_empty(argv[1])) {
260 			kf->kf_instance =
261 			    strtonum(argv[1], 0, 0xffffffffU, &errstr);
262 			if (errstr != NULL) {
263 				errx(1, "%s:%s:%s:%s: instance %s: %s",
264 				    argv[0], argv[1], argv[2], argv[3],
265 				    argv[1], errstr);
266 			}
267 			SET(kf->kf_flags, KSTAT_FILTER_F_INST);
268 		}
269 		if (!str_is_empty(argv[2]))
270 			kf->kf_name = argv[2];
271 		if (!str_is_empty(argv[3])) {
272 			kf->kf_unit =
273 			    strtonum(argv[3], 0, 0xffffffffU, &errstr);
274 			if (errstr != NULL) {
275 				errx(1, "%s:%s:%s:%s: unit %s: %s",
276 				    argv[0], argv[1], argv[2], argv[3],
277 				    argv[3], errstr);
278 			}
279 			SET(kf->kf_flags, KSTAT_FILTER_F_UNIT);
280 		}
281 		break;
282 	default:
283 		usage();
284 	}
285 
286 	return (kf);
287 }
288 
289 static int
kstat_filter_entry(struct kstat_filters * kfs,const struct kstat_req * ksreq)290 kstat_filter_entry(struct kstat_filters *kfs, const struct kstat_req *ksreq)
291 {
292 	struct kstat_filter *kf;
293 
294 	if (TAILQ_EMPTY(kfs))
295 		return (1);
296 
297 	TAILQ_FOREACH(kf, kfs, kf_entry) {
298 		if (kf->kf_provider != NULL) {
299 			if (fnmatch(kf->kf_provider, ksreq->ks_provider,
300 			    FNM_NOESCAPE | FNM_LEADING_DIR) == FNM_NOMATCH)
301 				continue;
302 		}
303 		if (ISSET(kf->kf_flags, KSTAT_FILTER_F_INST)) {
304 			if (kf->kf_instance != ksreq->ks_instance)
305 				continue;
306 		}
307 		if (kf->kf_name != NULL) {
308 			if (fnmatch(kf->kf_name, ksreq->ks_name,
309 			    FNM_NOESCAPE | FNM_LEADING_DIR) == FNM_NOMATCH)
310 				continue;
311 		}
312 		if (ISSET(kf->kf_flags, KSTAT_FILTER_F_UNIT)) {
313 			if (kf->kf_unit != ksreq->ks_unit)
314 				continue;
315 		}
316 
317 		return (1);
318 	}
319 
320 	return (0);
321 }
322 
323 static int
printable(int ch)324 printable(int ch)
325 {
326 	if (ch == '\0')
327 		return ('_');
328 	if (!isprint(ch))
329 		return ('~');
330 	return (ch);
331 }
332 
333 static void
hexdump(const void * d,size_t datalen)334 hexdump(const void *d, size_t datalen)
335 {
336 	const uint8_t *data = d;
337 	size_t i, j = 0;
338 
339 	for (i = 0; i < datalen; i += j) {
340 		printf("%4zu: ", i);
341 
342 		for (j = 0; j < 16 && i+j < datalen; j++)
343 			printf("%02x ", data[i + j]);
344 		while (j++ < 16)
345 			printf("   ");
346 		printf("|");
347 
348 		for (j = 0; j < 16 && i+j < datalen; j++)
349 			putchar(printable(data[i + j]));
350 		printf("|\n");
351 	}
352 }
353 
354 static void
strdump(const void * s,size_t len)355 strdump(const void *s, size_t len)
356 {
357 	const char *str = s;
358 	char dst[8];
359 	size_t i;
360 
361 	for (i = 0; i < len; i++) {
362 		char ch = str[i];
363 		if (ch == '\0')
364 			break;
365 
366 		vis(dst, ch, VIS_TAB | VIS_NL, 0);
367 		printf("%s", dst);
368 	}
369 }
370 
371 static void
strdumpnl(const void * s,size_t len)372 strdumpnl(const void *s, size_t len)
373 {
374 	strdump(s, len);
375 	printf("\n");
376 }
377 
378 static const char *si_prefixes[] = { "", "k", "M", "G", "T", "P", "E" };
379 #ifdef notyet
380 static const char *iec_prefixes[] = { "", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei" };
381 #endif
382 
383 static void
kstat_kv(const void * d,ssize_t len)384 kstat_kv(const void *d, ssize_t len)
385 {
386 	const uint8_t *buf;
387 	const struct kstat_kv *kv;
388 	ssize_t blen;
389 	void (*trailer)(const void *, size_t);
390 	double f;
391 	struct fmt_result fr;
392 
393 	if (len < (ssize_t)sizeof(*kv)) {
394 		warn("short kv (len %zu < size %zu)", len, sizeof(*kv));
395 		return;
396 	}
397 
398 	buf = d;
399 	do {
400 		kv = (const struct kstat_kv *)buf;
401 
402 		buf += sizeof(*kv);
403 		len -= sizeof(*kv);
404 
405 		blen = 0;
406 		trailer = hexdump;
407 
408 		printf("%16.16s: ", kv->kv_key);
409 
410 		switch (kv->kv_type) {
411 		case KSTAT_KV_T_NULL:
412 			printf("null");
413 			break;
414 		case KSTAT_KV_T_BOOL:
415 			printf("%s", kstat_kv_bool(kv) ? "true" : "false");
416 			break;
417 		case KSTAT_KV_T_COUNTER64:
418 		case KSTAT_KV_T_UINT64:
419 			printf("%" PRIu64, kstat_kv_u64(kv));
420 			break;
421 		case KSTAT_KV_T_INT64:
422 			printf("%" PRId64, kstat_kv_s64(kv));
423 			break;
424 		case KSTAT_KV_T_COUNTER32:
425 		case KSTAT_KV_T_UINT32:
426 			printf("%" PRIu32, kstat_kv_u32(kv));
427 			break;
428 		case KSTAT_KV_T_INT32:
429 			printf("%" PRId32, kstat_kv_s32(kv));
430 			break;
431 		case KSTAT_KV_T_COUNTER16:
432 		case KSTAT_KV_T_UINT16:
433 			printf("%" PRIu16, kstat_kv_u16(kv));
434 			break;
435 		case KSTAT_KV_T_INT16:
436 			printf("%" PRId16, kstat_kv_s16(kv));
437 			break;
438 		case KSTAT_KV_T_STR:
439 			blen = kstat_kv_len(kv);
440 			trailer = strdumpnl;
441 			break;
442 		case KSTAT_KV_T_BYTES:
443 			blen = kstat_kv_len(kv);
444 			trailer = hexdump;
445 
446 			printf("\n");
447 			break;
448 
449 		case KSTAT_KV_T_ISTR:
450 			strdump(kstat_kv_istr(kv), sizeof(kstat_kv_istr(kv)));
451 			break;
452 
453 		case KSTAT_KV_T_TEMP:
454 			f = kstat_kv_temp(kv);
455 			printf("%.2f degC", (f - 273150000.0) / 1000000.0);
456 			break;
457 
458 		case KSTAT_KV_T_FREQ:
459 			fmt_thing(&fr, kstat_kv_freq(kv), 1000);
460 			printf("%llu", fr.val);
461 			if (fr.frac > 10)
462 				printf(".%02u", fr.frac / 10);
463 			printf(" %sHz", si_prefixes[fr.exp]);
464 			break;
465 
466 		case KSTAT_KV_T_VOLTS_DC: /* uV */
467 			f = kstat_kv_volts(kv);
468 			printf("%.2f VDC", f / 1000000.0);
469 			break;
470 
471 		case KSTAT_KV_T_VOLTS_AC: /* uV */
472 			f = kstat_kv_volts(kv);
473 			printf("%.2f VAC", f / 1000000.0);
474 			break;
475 
476 		case KSTAT_KV_T_AMPS: /* uA */
477 			f = kstat_kv_amps(kv);
478 			printf("%.3f A", f / 1000000.0);
479 			break;
480 
481 		case KSTAT_KV_T_WATTS: /* uW */
482 			f = kstat_kv_watts(kv);
483 			printf("%.3f W", f / 1000000.0);
484 			break;
485 
486 		default:
487 			printf("unknown type %u, stopping\n", kv->kv_type);
488 			return;
489 		}
490 
491 		switch (kv->kv_unit) {
492 		case KSTAT_KV_U_NONE:
493 			break;
494 		case KSTAT_KV_U_PACKETS:
495 			printf(" packets");
496 			break;
497 		case KSTAT_KV_U_BYTES:
498 			printf(" bytes");
499 			break;
500 		case KSTAT_KV_U_CYCLES:
501 			printf(" cycles");
502 			break;
503 
504 		default:
505 			printf(" unit-type-%u", kv->kv_unit);
506 			break;
507 		}
508 
509 		if (blen > 0) {
510 			if (blen > len) {
511 				blen = len;
512 			}
513 
514 			(*trailer)(buf, blen);
515 		} else
516 			printf("\n");
517 
518 		blen = roundup(blen, KSTAT_KV_ALIGN);
519 		buf += blen;
520 		len -= blen;
521 	} while (len >= (ssize_t)sizeof(*kv));
522 }
523 
524 static void
kstat_list(struct kstat_tree * kt,int fd,unsigned int version,struct kstat_filters * kfs)525 kstat_list(struct kstat_tree *kt, int fd, unsigned int version,
526     struct kstat_filters *kfs)
527 {
528 	struct kstat_entry *kse;
529 	struct kstat_req *ksreq;
530 	uint64_t id = 0;
531 
532 	for (;;) {
533 		kse = malloc(sizeof(*kse));
534 		if (kse == NULL)
535 			err(1, NULL);
536 
537 		memset(kse, 0, sizeof(*kse));
538 		ksreq = &kse->kstat;
539 		ksreq->ks_version = version;
540 		ksreq->ks_id = ++id;
541 
542 		if (ioctl(fd, KSTATIOC_NFIND_ID, ksreq) == -1) {
543 			if (errno == ENOENT) {
544 				free(ksreq->ks_data);
545 				free(kse);
546 				break;
547 			}
548 		} else
549 			id = ksreq->ks_id;
550 
551 		if (!kstat_filter_entry(kfs, ksreq)) {
552 			free(ksreq->ks_data);
553 			free(kse);
554 			continue;
555 		}
556 
557 		if (RBT_INSERT(kstat_tree, kt, kse) != NULL)
558 			errx(1, "duplicate kstat entry");
559 
560 		ksreq->ks_data = malloc(ksreq->ks_datalen);
561 		if (ksreq->ks_data == NULL)
562 			err(1, "kstat data alloc");
563 	}
564 }
565 
566 static void
kstat_print(struct kstat_tree * kt)567 kstat_print(struct kstat_tree *kt)
568 {
569 	struct kstat_entry *kse;
570 	struct kstat_req *ksreq;
571 
572 	RBT_FOREACH(kse, kstat_tree, kt) {
573 		ksreq = &kse->kstat;
574 		printf("%s:%u:%s:%u\n",
575 		    ksreq->ks_provider, ksreq->ks_instance,
576 		    ksreq->ks_name, ksreq->ks_unit);
577 		if (kse->serrno != 0) {
578 			printf("\tkstat read error: %s\n",
579 			    strerror(kse->serrno));
580 			continue;
581 		}
582 		switch (ksreq->ks_type) {
583 		case KSTAT_T_RAW:
584 			hexdump(ksreq->ks_data, ksreq->ks_datalen);
585 			break;
586 		case KSTAT_T_KV:
587 			kstat_kv(ksreq->ks_data, ksreq->ks_datalen);
588 			break;
589 		default:
590 			hexdump(ksreq->ks_data, ksreq->ks_datalen);
591 			break;
592 		}
593 	}
594 
595 	fflush(stdout);
596 }
597 
598 static void
kstat_read(struct kstat_tree * kt,int fd)599 kstat_read(struct kstat_tree *kt, int fd)
600 {
601 	struct kstat_entry *kse;
602 	struct kstat_req *ksreq;
603 
604 	RBT_FOREACH(kse, kstat_tree, kt) {
605 		kse->serrno = 0;
606 		ksreq = &kse->kstat;
607 		if (ioctl(fd, KSTATIOC_FIND_ID, ksreq) == -1)
608 			kse->serrno = errno;
609 	}
610 }
611 
612 static void
handle_alrm(int signo)613 handle_alrm(int signo)
614 {
615 }
616