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