xref: /openbsd-src/usr.bin/kstat/kstat.c (revision 25c4e8bd056e974b28f4a0ffd39d76c190a56013)
1 /* $OpenBSD: kstat.c,v 1.11 2022/07/10 19:51:37 kn 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 #define str_is_empty(_str)	(*(_str) == '\0')
56 
57 #define DEV_KSTAT "/dev/kstat"
58 
59 struct kstat_filter {
60 	TAILQ_ENTRY(kstat_filter)	 kf_entry;
61 	const char			*kf_provider;
62 	const char			*kf_name;
63 	unsigned int			 kf_flags;
64 #define KSTAT_FILTER_F_INST			(1 << 0)
65 #define KSTAT_FILTER_F_UNIT			(1 << 1)
66 	unsigned int			 kf_instance;
67 	unsigned int			 kf_unit;
68 };
69 
70 TAILQ_HEAD(kstat_filters, kstat_filter);
71 
72 struct kstat_entry {
73 	struct kstat_req	kstat;
74 	RBT_ENTRY(kstat_entry)	entry;
75 	int			serrno;
76 };
77 
78 RBT_HEAD(kstat_tree, kstat_entry);
79 
80 static inline int
81 kstat_cmp(const struct kstat_entry *ea, const struct kstat_entry *eb)
82 {
83 	const struct kstat_req *a = &ea->kstat;
84 	const struct kstat_req *b = &eb->kstat;
85 	int rv;
86 
87 	rv = strncmp(a->ks_provider, b->ks_provider, sizeof(a->ks_provider));
88 	if (rv != 0)
89 		return (rv);
90 	if (a->ks_instance > b->ks_instance)
91 		return (1);
92 	if (a->ks_instance < b->ks_instance)
93 		return (-1);
94 
95 	rv = strncmp(a->ks_name, b->ks_name, sizeof(a->ks_name));
96 	if (rv != 0)
97 		return (rv);
98 	if (a->ks_unit > b->ks_unit)
99 		return (1);
100 	if (a->ks_unit < b->ks_unit)
101 		return (-1);
102 
103 	return (0);
104 }
105 
106 RBT_PROTOTYPE(kstat_tree, kstat_entry, entry, kstat_cmp);
107 RBT_GENERATE(kstat_tree, kstat_entry, entry, kstat_cmp);
108 
109 static void handle_alrm(int);
110 static struct kstat_filter *
111 		kstat_filter_parse(char *);
112 static int	kstat_filter_entry(struct kstat_filters *,
113 		    const struct kstat_req *);
114 
115 static void	kstat_list(struct kstat_tree *, int, unsigned int,
116 		    struct kstat_filters *);
117 static void	kstat_print(struct kstat_tree *);
118 static void	kstat_read(struct kstat_tree *, int);
119 
120 __dead static void
121 usage(void)
122 {
123 	extern char *__progname;
124 
125 	fprintf(stderr, "usage: %s [-w wait] "
126 	    "[name | provider:instance:name:unit] ...\n", __progname);
127 
128 	exit(1);
129 }
130 
131 int
132 main(int argc, char *argv[])
133 {
134 	struct kstat_filters kfs = TAILQ_HEAD_INITIALIZER(kfs);
135 	struct kstat_tree kt = RBT_INITIALIZER();
136 	unsigned int version;
137 	int fd;
138 	const char *errstr;
139 	int ch;
140 	struct itimerval itv;
141 	sigset_t empty, mask;
142 	int i;
143 	unsigned int wait = 0;
144 
145 	while ((ch = getopt(argc, argv, "w:")) != -1) {
146 		switch (ch) {
147 		case 'w':
148 			wait = strtonum(optarg, 1, UINT_MAX, &errstr);
149 			if (errstr != NULL)
150 				errx(1, "wait is %s: %s", errstr, optarg);
151 			break;
152 		default:
153 			usage();
154 		}
155 	}
156 
157 	argc -= optind;
158 	argv += optind;
159 
160 	for (i = 0; i < argc; i++) {
161 		struct kstat_filter *kf = kstat_filter_parse(argv[i]);
162 		TAILQ_INSERT_TAIL(&kfs, kf, kf_entry);
163 	}
164 
165 	fd = open(DEV_KSTAT, O_RDONLY);
166 	if (fd == -1)
167 		err(1, "%s", DEV_KSTAT);
168 
169 	if (ioctl(fd, KSTATIOC_VERSION, &version) == -1)
170 		err(1, "kstat version");
171 
172 	kstat_list(&kt, fd, version, &kfs);
173 	kstat_print(&kt);
174 
175 	if (wait == 0)
176 		return (0);
177 
178 	if (signal(SIGALRM, handle_alrm) == SIG_ERR)
179 		err(1, "signal");
180 	sigemptyset(&empty);
181 	sigemptyset(&mask);
182 	sigaddset(&mask, SIGALRM);
183 	if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
184 		err(1, "sigprocmask");
185 
186 	itv.it_value.tv_sec = wait;
187 	itv.it_value.tv_usec = 0;
188 	itv.it_interval = itv.it_value;
189 	if (setitimer(ITIMER_REAL, &itv, NULL) == -1)
190 		err(1, "setitimer");
191 
192 	for (;;) {
193 		sigsuspend(&empty);
194 		kstat_read(&kt, fd);
195 		kstat_print(&kt);
196 	}
197 
198 	return (0);
199 }
200 
201 static struct kstat_filter *
202 kstat_filter_parse(char *arg)
203 {
204 	struct kstat_filter *kf;
205 	const char *errstr;
206 	char *argv[4];
207 	size_t argc;
208 
209 	for (argc = 0; argc < nitems(argv); argc++) {
210 		char *s = strsep(&arg, ":");
211 		if (s == NULL)
212 			break;
213 
214 		argv[argc] = s;
215 	}
216 	if (arg != NULL)
217 		usage();
218 
219 	kf = malloc(sizeof(*kf));
220 	if (kf == NULL)
221 		err(1, NULL);
222 
223 	memset(kf, 0, sizeof(*kf));
224 
225 	switch (argc) {
226 	case 1:
227 		if (str_is_empty(argv[0]))
228 			errx(1, "empty name");
229 
230 		kf->kf_name = argv[0];
231 		break;
232 	case 4:
233 		if (!str_is_empty(argv[0]))
234 			kf->kf_provider = argv[0];
235 		if (!str_is_empty(argv[1])) {
236 			kf->kf_instance =
237 			    strtonum(argv[1], 0, 0xffffffffU, &errstr);
238 			if (errstr != NULL) {
239 				errx(1, "%s:%s:%s:%s: instance %s: %s",
240 				    argv[0], argv[1], argv[2], argv[3],
241 				    argv[1], errstr);
242 			}
243 			SET(kf->kf_flags, KSTAT_FILTER_F_INST);
244 		}
245 		if (!str_is_empty(argv[2]))
246 			kf->kf_name = argv[2];
247 		if (!str_is_empty(argv[3])) {
248 			kf->kf_unit =
249 			    strtonum(argv[3], 0, 0xffffffffU, &errstr);
250 			if (errstr != NULL) {
251 				errx(1, "%s:%s:%s:%s: unit %s: %s",
252 				    argv[0], argv[1], argv[2], argv[3],
253 				    argv[3], errstr);
254 			}
255 			SET(kf->kf_flags, KSTAT_FILTER_F_UNIT);
256 		}
257 		break;
258 	default:
259 		usage();
260 	}
261 
262 	return (kf);
263 }
264 
265 static int
266 kstat_filter_entry(struct kstat_filters *kfs, const struct kstat_req *ksreq)
267 {
268 	struct kstat_filter *kf;
269 
270 	if (TAILQ_EMPTY(kfs))
271 		return (1);
272 
273 	TAILQ_FOREACH(kf, kfs, kf_entry) {
274 		if (kf->kf_provider != NULL) {
275 			if (fnmatch(kf->kf_provider, ksreq->ks_provider,
276 			    FNM_NOESCAPE | FNM_LEADING_DIR) == FNM_NOMATCH)
277 				continue;
278 		}
279 		if (ISSET(kf->kf_flags, KSTAT_FILTER_F_INST)) {
280 			if (kf->kf_instance != ksreq->ks_instance)
281 				continue;
282 		}
283 		if (kf->kf_name != NULL) {
284 			if (fnmatch(kf->kf_name, ksreq->ks_name,
285 			    FNM_NOESCAPE | FNM_LEADING_DIR) == FNM_NOMATCH)
286 				continue;
287 		}
288 		if (ISSET(kf->kf_flags, KSTAT_FILTER_F_UNIT)) {
289 			if (kf->kf_unit != ksreq->ks_unit)
290 				continue;
291 		}
292 
293 		return (1);
294 	}
295 
296 	return (0);
297 }
298 
299 static int
300 printable(int ch)
301 {
302 	if (ch == '\0')
303 		return ('_');
304 	if (!isprint(ch))
305 		return ('~');
306 	return (ch);
307 }
308 
309 static void
310 hexdump(const void *d, size_t datalen)
311 {
312 	const uint8_t *data = d;
313 	size_t i, j = 0;
314 
315 	for (i = 0; i < datalen; i += j) {
316 		printf("%4zu: ", i);
317 
318 		for (j = 0; j < 16 && i+j < datalen; j++)
319 			printf("%02x ", data[i + j]);
320 		while (j++ < 16)
321 			printf("   ");
322 		printf("|");
323 
324 		for (j = 0; j < 16 && i+j < datalen; j++)
325 			putchar(printable(data[i + j]));
326 		printf("|\n");
327 	}
328 }
329 
330 static void
331 strdump(const void *s, size_t len)
332 {
333 	const char *str = s;
334 	char dst[8];
335 	size_t i;
336 
337 	for (i = 0; i < len; i++) {
338 		char ch = str[i];
339 		if (ch == '\0')
340 			break;
341 
342 		vis(dst, ch, VIS_TAB | VIS_NL, 0);
343 		printf("%s", dst);
344 	}
345 }
346 
347 static void
348 strdumpnl(const void *s, size_t len)
349 {
350 	strdump(s, len);
351 	printf("\n");
352 }
353 
354 static void
355 kstat_kv(const void *d, ssize_t len)
356 {
357 	const uint8_t *buf;
358 	const struct kstat_kv *kv;
359 	ssize_t blen;
360 	void (*trailer)(const void *, size_t);
361 	double f;
362 
363 	if (len < (ssize_t)sizeof(*kv)) {
364 		warn("short kv (len %zu < size %zu)", len, sizeof(*kv));
365 		return;
366 	}
367 
368 	buf = d;
369 	do {
370 		kv = (const struct kstat_kv *)buf;
371 
372 		buf += sizeof(*kv);
373 		len -= sizeof(*kv);
374 
375 		blen = 0;
376 		trailer = hexdump;
377 
378 		printf("%16.16s: ", kv->kv_key);
379 
380 		switch (kv->kv_type) {
381 		case KSTAT_KV_T_NULL:
382 			printf("null");
383 			break;
384 		case KSTAT_KV_T_BOOL:
385 			printf("%s", kstat_kv_bool(kv) ? "true" : "false");
386 			break;
387 		case KSTAT_KV_T_COUNTER64:
388 		case KSTAT_KV_T_UINT64:
389 			printf("%" PRIu64, kstat_kv_u64(kv));
390 			break;
391 		case KSTAT_KV_T_INT64:
392 			printf("%" PRId64, kstat_kv_s64(kv));
393 			break;
394 		case KSTAT_KV_T_COUNTER32:
395 		case KSTAT_KV_T_UINT32:
396 			printf("%" PRIu32, kstat_kv_u32(kv));
397 			break;
398 		case KSTAT_KV_T_INT32:
399 			printf("%" PRId32, kstat_kv_s32(kv));
400 			break;
401 		case KSTAT_KV_T_COUNTER16:
402 		case KSTAT_KV_T_UINT16:
403 			printf("%" PRIu16, kstat_kv_u16(kv));
404 			break;
405 		case KSTAT_KV_T_INT16:
406 			printf("%" PRId16, kstat_kv_s16(kv));
407 			break;
408 		case KSTAT_KV_T_STR:
409 			blen = kstat_kv_len(kv);
410 			trailer = strdumpnl;
411 			break;
412 		case KSTAT_KV_T_BYTES:
413 			blen = kstat_kv_len(kv);
414 			trailer = hexdump;
415 
416 			printf("\n");
417 			break;
418 
419 		case KSTAT_KV_T_ISTR:
420 			strdump(kstat_kv_istr(kv), sizeof(kstat_kv_istr(kv)));
421 			break;
422 
423 		case KSTAT_KV_T_TEMP:
424 			f = kstat_kv_temp(kv);
425 			printf("%.2f degC", (f - 273150000.0) / 1000000.0);
426 			break;
427 
428 		default:
429 			printf("unknown type %u, stopping\n", kv->kv_type);
430 			return;
431 		}
432 
433 		switch (kv->kv_unit) {
434 		case KSTAT_KV_U_NONE:
435 			break;
436 		case KSTAT_KV_U_PACKETS:
437 			printf(" packets");
438 			break;
439 		case KSTAT_KV_U_BYTES:
440 			printf(" bytes");
441 			break;
442 		case KSTAT_KV_U_CYCLES:
443 			printf(" cycles");
444 			break;
445 
446 		default:
447 			printf(" unit-type-%u", kv->kv_unit);
448 			break;
449 		}
450 
451 		if (blen > 0) {
452 			if (blen > len) {
453 				blen = len;
454 			}
455 
456 			(*trailer)(buf, blen);
457 		} else
458 			printf("\n");
459 
460 		blen = roundup(blen, KSTAT_KV_ALIGN);
461 		buf += blen;
462 		len -= blen;
463 	} while (len >= (ssize_t)sizeof(*kv));
464 }
465 
466 static void
467 kstat_list(struct kstat_tree *kt, int fd, unsigned int version,
468     struct kstat_filters *kfs)
469 {
470 	struct kstat_entry *kse;
471 	struct kstat_req *ksreq;
472 	size_t len;
473 	uint64_t id = 0;
474 
475 	for (;;) {
476 		kse = malloc(sizeof(*kse));
477 		if (kse == NULL)
478 			err(1, NULL);
479 
480 		memset(kse, 0, sizeof(*kse));
481 		ksreq = &kse->kstat;
482 		ksreq->ks_version = version;
483 		ksreq->ks_id = ++id;
484 
485 		ksreq->ks_datalen = len = 64; /* magic */
486 		ksreq->ks_data = malloc(len);
487 		if (ksreq->ks_data == NULL)
488 			err(1, "data alloc");
489 
490 		if (ioctl(fd, KSTATIOC_NFIND_ID, ksreq) == -1) {
491 			if (errno == ENOENT) {
492 				free(ksreq->ks_data);
493 				free(kse);
494 				break;
495 			}
496 
497 			kse->serrno = errno;
498 		} else
499 			id = ksreq->ks_id;
500 
501 		if (!kstat_filter_entry(kfs, ksreq)) {
502 			free(ksreq->ks_data);
503 			free(kse);
504 			continue;
505 		}
506 
507 		if (RBT_INSERT(kstat_tree, kt, kse) != NULL)
508 			errx(1, "duplicate kstat entry");
509 
510 		if (kse->serrno != 0)
511 			continue;
512 
513 		while (ksreq->ks_datalen > len) {
514 			len = ksreq->ks_datalen;
515 			ksreq->ks_data = realloc(ksreq->ks_data, len);
516 			if (ksreq->ks_data == NULL)
517 				err(1, "data resize (%zu)", len);
518 
519 			if (ioctl(fd, KSTATIOC_FIND_ID, ksreq) == -1)
520 				err(1, "find id %llu", ksreq->ks_id);
521 		}
522 	}
523 }
524 
525 static void
526 kstat_print(struct kstat_tree *kt)
527 {
528 	struct kstat_entry *kse;
529 	struct kstat_req *ksreq;
530 
531 	RBT_FOREACH(kse, kstat_tree, kt) {
532 		ksreq = &kse->kstat;
533 		printf("%s:%u:%s:%u\n",
534 		    ksreq->ks_provider, ksreq->ks_instance,
535 		    ksreq->ks_name, ksreq->ks_unit);
536 		if (kse->serrno != 0) {
537 			printf("\t%s\n", strerror(kse->serrno));
538 			continue;
539 		}
540 		switch (ksreq->ks_type) {
541 		case KSTAT_T_RAW:
542 			hexdump(ksreq->ks_data, ksreq->ks_datalen);
543 			break;
544 		case KSTAT_T_KV:
545 			kstat_kv(ksreq->ks_data, ksreq->ks_datalen);
546 			break;
547 		default:
548 			hexdump(ksreq->ks_data, ksreq->ks_datalen);
549 			break;
550 		}
551 	}
552 
553 	fflush(stdout);
554 }
555 
556 static void
557 kstat_read(struct kstat_tree *kt, int fd)
558 {
559 	struct kstat_entry *kse;
560 	struct kstat_req *ksreq;
561 
562 	RBT_FOREACH(kse, kstat_tree, kt) {
563 		ksreq = &kse->kstat;
564 		if (ioctl(fd, KSTATIOC_FIND_ID, ksreq) == -1)
565 			err(1, "update id %llu", ksreq->ks_id);
566 	}
567 }
568 
569 static void
570 handle_alrm(int signo)
571 {
572 }
573