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
55struct fmt_result {
56	uint64_t		val;
57	unsigned int		frac;
58	unsigned int		exp;
59};
60
61static void
62fmt_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
82struct 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
93TAILQ_HEAD(kstat_filters, kstat_filter);
94
95struct kstat_entry {
96	struct kstat_req	kstat;
97	RBT_ENTRY(kstat_entry)	entry;
98	int			serrno;
99};
100
101RBT_HEAD(kstat_tree, kstat_entry);
102
103static inline int
104kstat_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
129RBT_PROTOTYPE(kstat_tree, kstat_entry, entry, kstat_cmp);
130RBT_GENERATE(kstat_tree, kstat_entry, entry, kstat_cmp);
131
132static void handle_alrm(int);
133static struct kstat_filter *
134		kstat_filter_parse(char *);
135static int	kstat_filter_entry(struct kstat_filters *,
136		    const struct kstat_req *);
137
138static void	kstat_list(struct kstat_tree *, int, unsigned int,
139		    struct kstat_filters *);
140static void	kstat_print(struct kstat_tree *);
141static void	kstat_read(struct kstat_tree *, int);
142
143__dead static void
144usage(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
154int
155main(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
225static struct kstat_filter *
226kstat_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
289static int
290kstat_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
323static int
324printable(int ch)
325{
326	if (ch == '\0')
327		return ('_');
328	if (!isprint(ch))
329		return ('~');
330	return (ch);
331}
332
333static void
334hexdump(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
354static void
355strdump(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
371static void
372strdumpnl(const void *s, size_t len)
373{
374	strdump(s, len);
375	printf("\n");
376}
377
378static const char *si_prefixes[] = { "", "k", "M", "G", "T", "P", "E" };
379#ifdef notyet
380static const char *iec_prefixes[] = { "", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei" };
381#endif
382
383static void
384kstat_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
524static void
525kstat_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
566static void
567kstat_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
598static void
599kstat_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
612static void
613handle_alrm(int signo)
614{
615}
616