Annotation of src/usr.bin/kstat/kstat.c, Revision 1.13
1.13 ! dlg 1: /* $OpenBSD: kstat.c,v 1.12 2023/11/16 02:45:54 dlg Exp $ */
1.1 dlg 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>
1.10 cheloha 18: #include <limits.h>
19: #include <signal.h>
1.1 dlg 20: #include <stdio.h>
21: #include <stdlib.h>
22: #include <stddef.h>
23: #include <string.h>
24: #include <inttypes.h>
1.2 dlg 25: #include <fnmatch.h>
1.1 dlg 26: #include <fcntl.h>
1.3 dlg 27: #include <unistd.h>
1.1 dlg 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>
1.2 dlg 35: #include <sys/queue.h>
1.1 dlg 36:
37: #include <sys/kstat.h>
38:
39: #ifndef roundup
1.2 dlg 40: #define roundup(x, y) ((((x)+((y)-1))/(y))*(y))
1.1 dlg 41: #endif
42:
1.2 dlg 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:
1.12 dlg 55: struct fmt_result {
56: uint64_t val;
57: unsigned int frac;
58: unsigned int exp;
59: };
60:
61: static void
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:
1.2 dlg 78: #define str_is_empty(_str) (*(_str) == '\0')
79:
1.1 dlg 80: #define DEV_KSTAT "/dev/kstat"
81:
1.2 dlg 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:
1.4 dlg 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
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:
1.10 cheloha 132: static void handle_alrm(int);
1.2 dlg 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:
1.3 dlg 138: static void kstat_list(struct kstat_tree *, int, unsigned int,
139: struct kstat_filters *);
140: static void kstat_print(struct kstat_tree *);
1.5 dlg 141: static void kstat_read(struct kstat_tree *, int);
1.1 dlg 142:
143: __dead static void
144: usage(void)
145: {
146: extern char *__progname;
1.2 dlg 147:
1.5 dlg 148: fprintf(stderr, "usage: %s [-w wait] "
1.11 kn 149: "[name | provider:instance:name:unit] ...\n", __progname);
1.2 dlg 150:
1.1 dlg 151: exit(1);
152: }
153:
154: int
155: main(int argc, char *argv[])
156: {
1.2 dlg 157: struct kstat_filters kfs = TAILQ_HEAD_INITIALIZER(kfs);
1.3 dlg 158: struct kstat_tree kt = RBT_INITIALIZER();
1.1 dlg 159: unsigned int version;
160: int fd;
1.5 dlg 161: const char *errstr;
162: int ch;
1.10 cheloha 163: struct itimerval itv;
164: sigset_t empty, mask;
1.2 dlg 165: int i;
1.10 cheloha 166: unsigned int wait = 0;
1.2 dlg 167:
1.5 dlg 168: while ((ch = getopt(argc, argv, "w:")) != -1) {
169: switch (ch) {
170: case 'w':
1.10 cheloha 171: wait = strtonum(optarg, 1, UINT_MAX, &errstr);
1.5 dlg 172: if (errstr != NULL)
1.10 cheloha 173: errx(1, "wait is %s: %s", errstr, optarg);
1.5 dlg 174: break;
175: default:
176: usage();
177: }
178: }
179:
180: argc -= optind;
181: argv += optind;
182:
183: for (i = 0; i < argc; i++) {
1.2 dlg 184: struct kstat_filter *kf = kstat_filter_parse(argv[i]);
185: TAILQ_INSERT_TAIL(&kfs, kf, kf_entry);
186: }
1.1 dlg 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:
1.3 dlg 195: kstat_list(&kt, fd, version, &kfs);
1.13 ! dlg 196: kstat_read(&kt, fd);
1.3 dlg 197: kstat_print(&kt);
1.2 dlg 198:
1.10 cheloha 199: if (wait == 0)
1.5 dlg 200: return (0);
201:
1.10 cheloha 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:
1.5 dlg 216: for (;;) {
1.10 cheloha 217: sigsuspend(&empty);
1.5 dlg 218: kstat_read(&kt, fd);
219: kstat_print(&kt);
220: }
221:
1.2 dlg 222: return (0);
223: }
224:
225: static struct kstat_filter *
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) {
1.8 dlg 275: errx(1, "%s:%s:%s:%s: unit %s: %s",
1.2 dlg 276: argv[0], argv[1], argv[2], argv[3],
1.8 dlg 277: argv[3], errstr);
1.2 dlg 278: }
1.8 dlg 279: SET(kf->kf_flags, KSTAT_FILTER_F_UNIT);
1.2 dlg 280: }
281: break;
282: default:
283: usage();
284: }
285:
286: return (kf);
287: }
288:
289: static int
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: }
1.1 dlg 319:
320: return (0);
321: }
322:
323: static int
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
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
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
372: strdumpnl(const void *s, size_t len)
373: {
374: strdump(s, len);
375: printf("\n");
376: }
377:
1.12 dlg 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:
1.1 dlg 383: static void
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;
1.12 dlg 391: struct fmt_result fr;
1.1 dlg 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));
1.9 dlg 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));
1.1 dlg 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);
1.12 dlg 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);
1.1 dlg 474: break;
475:
476: default:
477: printf("unknown type %u, stopping\n", kv->kv_type);
478: return;
479: }
480:
481: switch (kv->kv_unit) {
482: case KSTAT_KV_U_NONE:
483: break;
484: case KSTAT_KV_U_PACKETS:
485: printf(" packets");
486: break;
487: case KSTAT_KV_U_BYTES:
488: printf(" bytes");
489: break;
490: case KSTAT_KV_U_CYCLES:
491: printf(" cycles");
492: break;
493:
494: default:
495: printf(" unit-type-%u", kv->kv_unit);
496: break;
497: }
498:
499: if (blen > 0) {
500: if (blen > len) {
501: blen = len;
502: }
503:
504: (*trailer)(buf, blen);
505: } else
506: printf("\n");
507:
508: blen = roundup(blen, KSTAT_KV_ALIGN);
509: buf += blen;
510: len -= blen;
511: } while (len >= (ssize_t)sizeof(*kv));
512: }
513:
514: static void
1.3 dlg 515: kstat_list(struct kstat_tree *kt, int fd, unsigned int version,
516: struct kstat_filters *kfs)
1.1 dlg 517: {
518: struct kstat_entry *kse;
519: struct kstat_req *ksreq;
520: uint64_t id = 0;
521:
522: for (;;) {
523: kse = malloc(sizeof(*kse));
524: if (kse == NULL)
525: err(1, NULL);
526:
527: memset(kse, 0, sizeof(*kse));
528: ksreq = &kse->kstat;
529: ksreq->ks_version = version;
530: ksreq->ks_id = ++id;
531:
532: if (ioctl(fd, KSTATIOC_NFIND_ID, ksreq) == -1) {
533: if (errno == ENOENT) {
534: free(ksreq->ks_data);
535: free(kse);
536: break;
537: }
1.2 dlg 538: } else
539: id = ksreq->ks_id;
540:
541: if (!kstat_filter_entry(kfs, ksreq)) {
542: free(ksreq->ks_data);
543: free(kse);
544: continue;
1.1 dlg 545: }
546:
1.3 dlg 547: if (RBT_INSERT(kstat_tree, kt, kse) != NULL)
1.2 dlg 548: errx(1, "duplicate kstat entry");
549:
1.13 ! dlg 550: ksreq->ks_data = malloc(ksreq->ks_datalen);
! 551: if (ksreq->ks_data == NULL)
! 552: err(1, "kstat data alloc");
1.1 dlg 553: }
1.3 dlg 554: }
555:
556: static void
557: kstat_print(struct kstat_tree *kt)
558: {
559: struct kstat_entry *kse;
560: struct kstat_req *ksreq;
1.1 dlg 561:
1.3 dlg 562: RBT_FOREACH(kse, kstat_tree, kt) {
1.1 dlg 563: ksreq = &kse->kstat;
564: printf("%s:%u:%s:%u\n",
565: ksreq->ks_provider, ksreq->ks_instance,
566: ksreq->ks_name, ksreq->ks_unit);
567: if (kse->serrno != 0) {
1.13 ! dlg 568: printf("\tkstat read error: %s\n",
! 569: strerror(kse->serrno));
1.1 dlg 570: continue;
571: }
572: switch (ksreq->ks_type) {
573: case KSTAT_T_RAW:
574: hexdump(ksreq->ks_data, ksreq->ks_datalen);
575: break;
576: case KSTAT_T_KV:
577: kstat_kv(ksreq->ks_data, ksreq->ks_datalen);
578: break;
579: default:
580: hexdump(ksreq->ks_data, ksreq->ks_datalen);
581: break;
582: }
1.5 dlg 583: }
1.7 dlg 584:
585: fflush(stdout);
1.5 dlg 586: }
587:
588: static void
589: kstat_read(struct kstat_tree *kt, int fd)
590: {
591: struct kstat_entry *kse;
592: struct kstat_req *ksreq;
593:
594: RBT_FOREACH(kse, kstat_tree, kt) {
1.13 ! dlg 595: kse->serrno = 0;
1.5 dlg 596: ksreq = &kse->kstat;
597: if (ioctl(fd, KSTATIOC_FIND_ID, ksreq) == -1)
1.13 ! dlg 598: kse->serrno = errno;
1.1 dlg 599: }
1.10 cheloha 600: }
601:
602: static void
603: handle_alrm(int signo)
604: {
1.1 dlg 605: }