Annotation of src/usr.bin/kstat/kstat.c, Revision 1.12
1.12 ! dlg 1: /* $OpenBSD: kstat.c,v 1.11 2022/07/10 19:51:37 kn 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);
196: kstat_print(&kt);
1.2 dlg 197:
1.10 cheloha 198: if (wait == 0)
1.5 dlg 199: return (0);
200:
1.10 cheloha 201: if (signal(SIGALRM, handle_alrm) == SIG_ERR)
202: err(1, "signal");
203: sigemptyset(&empty);
204: sigemptyset(&mask);
205: sigaddset(&mask, SIGALRM);
206: if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
207: err(1, "sigprocmask");
208:
209: itv.it_value.tv_sec = wait;
210: itv.it_value.tv_usec = 0;
211: itv.it_interval = itv.it_value;
212: if (setitimer(ITIMER_REAL, &itv, NULL) == -1)
213: err(1, "setitimer");
214:
1.5 dlg 215: for (;;) {
1.10 cheloha 216: sigsuspend(&empty);
1.5 dlg 217: kstat_read(&kt, fd);
218: kstat_print(&kt);
219: }
220:
1.2 dlg 221: return (0);
222: }
223:
224: static struct kstat_filter *
225: kstat_filter_parse(char *arg)
226: {
227: struct kstat_filter *kf;
228: const char *errstr;
229: char *argv[4];
230: size_t argc;
231:
232: for (argc = 0; argc < nitems(argv); argc++) {
233: char *s = strsep(&arg, ":");
234: if (s == NULL)
235: break;
236:
237: argv[argc] = s;
238: }
239: if (arg != NULL)
240: usage();
241:
242: kf = malloc(sizeof(*kf));
243: if (kf == NULL)
244: err(1, NULL);
245:
246: memset(kf, 0, sizeof(*kf));
247:
248: switch (argc) {
249: case 1:
250: if (str_is_empty(argv[0]))
251: errx(1, "empty name");
252:
253: kf->kf_name = argv[0];
254: break;
255: case 4:
256: if (!str_is_empty(argv[0]))
257: kf->kf_provider = argv[0];
258: if (!str_is_empty(argv[1])) {
259: kf->kf_instance =
260: strtonum(argv[1], 0, 0xffffffffU, &errstr);
261: if (errstr != NULL) {
262: errx(1, "%s:%s:%s:%s: instance %s: %s",
263: argv[0], argv[1], argv[2], argv[3],
264: argv[1], errstr);
265: }
266: SET(kf->kf_flags, KSTAT_FILTER_F_INST);
267: }
268: if (!str_is_empty(argv[2]))
269: kf->kf_name = argv[2];
270: if (!str_is_empty(argv[3])) {
271: kf->kf_unit =
272: strtonum(argv[3], 0, 0xffffffffU, &errstr);
273: if (errstr != NULL) {
1.8 dlg 274: errx(1, "%s:%s:%s:%s: unit %s: %s",
1.2 dlg 275: argv[0], argv[1], argv[2], argv[3],
1.8 dlg 276: argv[3], errstr);
1.2 dlg 277: }
1.8 dlg 278: SET(kf->kf_flags, KSTAT_FILTER_F_UNIT);
1.2 dlg 279: }
280: break;
281: default:
282: usage();
283: }
284:
285: return (kf);
286: }
287:
288: static int
289: kstat_filter_entry(struct kstat_filters *kfs, const struct kstat_req *ksreq)
290: {
291: struct kstat_filter *kf;
292:
293: if (TAILQ_EMPTY(kfs))
294: return (1);
295:
296: TAILQ_FOREACH(kf, kfs, kf_entry) {
297: if (kf->kf_provider != NULL) {
298: if (fnmatch(kf->kf_provider, ksreq->ks_provider,
299: FNM_NOESCAPE | FNM_LEADING_DIR) == FNM_NOMATCH)
300: continue;
301: }
302: if (ISSET(kf->kf_flags, KSTAT_FILTER_F_INST)) {
303: if (kf->kf_instance != ksreq->ks_instance)
304: continue;
305: }
306: if (kf->kf_name != NULL) {
307: if (fnmatch(kf->kf_name, ksreq->ks_name,
308: FNM_NOESCAPE | FNM_LEADING_DIR) == FNM_NOMATCH)
309: continue;
310: }
311: if (ISSET(kf->kf_flags, KSTAT_FILTER_F_UNIT)) {
312: if (kf->kf_unit != ksreq->ks_unit)
313: continue;
314: }
315:
316: return (1);
317: }
1.1 dlg 318:
319: return (0);
320: }
321:
322: static int
323: printable(int ch)
324: {
325: if (ch == '\0')
326: return ('_');
327: if (!isprint(ch))
328: return ('~');
329: return (ch);
330: }
331:
332: static void
333: hexdump(const void *d, size_t datalen)
334: {
335: const uint8_t *data = d;
336: size_t i, j = 0;
337:
338: for (i = 0; i < datalen; i += j) {
339: printf("%4zu: ", i);
340:
341: for (j = 0; j < 16 && i+j < datalen; j++)
342: printf("%02x ", data[i + j]);
343: while (j++ < 16)
344: printf(" ");
345: printf("|");
346:
347: for (j = 0; j < 16 && i+j < datalen; j++)
348: putchar(printable(data[i + j]));
349: printf("|\n");
350: }
351: }
352:
353: static void
354: strdump(const void *s, size_t len)
355: {
356: const char *str = s;
357: char dst[8];
358: size_t i;
359:
360: for (i = 0; i < len; i++) {
361: char ch = str[i];
362: if (ch == '\0')
363: break;
364:
365: vis(dst, ch, VIS_TAB | VIS_NL, 0);
366: printf("%s", dst);
367: }
368: }
369:
370: static void
371: strdumpnl(const void *s, size_t len)
372: {
373: strdump(s, len);
374: printf("\n");
375: }
376:
1.12 ! dlg 377: static const char *si_prefixes[] = { "", "k", "M", "G", "T", "P", "E" };
! 378: #ifdef notyet
! 379: static const char *iec_prefixes[] = { "", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei" };
! 380: #endif
! 381:
1.1 dlg 382: static void
383: kstat_kv(const void *d, ssize_t len)
384: {
385: const uint8_t *buf;
386: const struct kstat_kv *kv;
387: ssize_t blen;
388: void (*trailer)(const void *, size_t);
389: double f;
1.12 ! dlg 390: struct fmt_result fr;
1.1 dlg 391:
392: if (len < (ssize_t)sizeof(*kv)) {
393: warn("short kv (len %zu < size %zu)", len, sizeof(*kv));
394: return;
395: }
396:
397: buf = d;
398: do {
399: kv = (const struct kstat_kv *)buf;
400:
401: buf += sizeof(*kv);
402: len -= sizeof(*kv);
403:
404: blen = 0;
405: trailer = hexdump;
406:
407: printf("%16.16s: ", kv->kv_key);
408:
409: switch (kv->kv_type) {
410: case KSTAT_KV_T_NULL:
411: printf("null");
412: break;
413: case KSTAT_KV_T_BOOL:
414: printf("%s", kstat_kv_bool(kv) ? "true" : "false");
415: break;
416: case KSTAT_KV_T_COUNTER64:
417: case KSTAT_KV_T_UINT64:
418: printf("%" PRIu64, kstat_kv_u64(kv));
419: break;
420: case KSTAT_KV_T_INT64:
421: printf("%" PRId64, kstat_kv_s64(kv));
422: break;
423: case KSTAT_KV_T_COUNTER32:
424: case KSTAT_KV_T_UINT32:
425: printf("%" PRIu32, kstat_kv_u32(kv));
426: break;
427: case KSTAT_KV_T_INT32:
428: printf("%" PRId32, kstat_kv_s32(kv));
1.9 dlg 429: break;
430: case KSTAT_KV_T_COUNTER16:
431: case KSTAT_KV_T_UINT16:
432: printf("%" PRIu16, kstat_kv_u16(kv));
433: break;
434: case KSTAT_KV_T_INT16:
435: printf("%" PRId16, kstat_kv_s16(kv));
1.1 dlg 436: break;
437: case KSTAT_KV_T_STR:
438: blen = kstat_kv_len(kv);
439: trailer = strdumpnl;
440: break;
441: case KSTAT_KV_T_BYTES:
442: blen = kstat_kv_len(kv);
443: trailer = hexdump;
444:
445: printf("\n");
446: break;
447:
448: case KSTAT_KV_T_ISTR:
449: strdump(kstat_kv_istr(kv), sizeof(kstat_kv_istr(kv)));
450: break;
451:
452: case KSTAT_KV_T_TEMP:
453: f = kstat_kv_temp(kv);
454: printf("%.2f degC", (f - 273150000.0) / 1000000.0);
1.12 ! dlg 455: break;
! 456:
! 457: case KSTAT_KV_T_FREQ:
! 458: fmt_thing(&fr, kstat_kv_freq(kv), 1000);
! 459: printf("%llu", fr.val);
! 460: if (fr.frac > 10)
! 461: printf(".%02u", fr.frac / 10);
! 462: printf(" %sHz", si_prefixes[fr.exp]);
! 463: break;
! 464:
! 465: case KSTAT_KV_T_VOLTS_DC: /* uV */
! 466: f = kstat_kv_volts(kv);
! 467: printf("%.2f VDC", f / 1000000.0);
! 468: break;
! 469:
! 470: case KSTAT_KV_T_VOLTS_AC: /* uV */
! 471: f = kstat_kv_volts(kv);
! 472: printf("%.2f VAC", f / 1000000.0);
1.1 dlg 473: break;
474:
475: default:
476: printf("unknown type %u, stopping\n", kv->kv_type);
477: return;
478: }
479:
480: switch (kv->kv_unit) {
481: case KSTAT_KV_U_NONE:
482: break;
483: case KSTAT_KV_U_PACKETS:
484: printf(" packets");
485: break;
486: case KSTAT_KV_U_BYTES:
487: printf(" bytes");
488: break;
489: case KSTAT_KV_U_CYCLES:
490: printf(" cycles");
491: break;
492:
493: default:
494: printf(" unit-type-%u", kv->kv_unit);
495: break;
496: }
497:
498: if (blen > 0) {
499: if (blen > len) {
500: blen = len;
501: }
502:
503: (*trailer)(buf, blen);
504: } else
505: printf("\n");
506:
507: blen = roundup(blen, KSTAT_KV_ALIGN);
508: buf += blen;
509: len -= blen;
510: } while (len >= (ssize_t)sizeof(*kv));
511: }
512:
513: static void
1.3 dlg 514: kstat_list(struct kstat_tree *kt, int fd, unsigned int version,
515: struct kstat_filters *kfs)
1.1 dlg 516: {
517: struct kstat_entry *kse;
518: struct kstat_req *ksreq;
519: size_t len;
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: ksreq->ks_datalen = len = 64; /* magic */
533: ksreq->ks_data = malloc(len);
534: if (ksreq->ks_data == NULL)
535: err(1, "data alloc");
536:
537: if (ioctl(fd, KSTATIOC_NFIND_ID, ksreq) == -1) {
538: if (errno == ENOENT) {
539: free(ksreq->ks_data);
540: free(kse);
541: break;
542: }
543:
544: kse->serrno = errno;
1.2 dlg 545: } else
546: id = ksreq->ks_id;
547:
548: if (!kstat_filter_entry(kfs, ksreq)) {
549: free(ksreq->ks_data);
550: free(kse);
551: continue;
1.1 dlg 552: }
553:
1.3 dlg 554: if (RBT_INSERT(kstat_tree, kt, kse) != NULL)
1.2 dlg 555: errx(1, "duplicate kstat entry");
556:
557: if (kse->serrno != 0)
558: continue;
559:
1.1 dlg 560: while (ksreq->ks_datalen > len) {
561: len = ksreq->ks_datalen;
562: ksreq->ks_data = realloc(ksreq->ks_data, len);
563: if (ksreq->ks_data == NULL)
564: err(1, "data resize (%zu)", len);
565:
566: if (ioctl(fd, KSTATIOC_FIND_ID, ksreq) == -1)
1.2 dlg 567: err(1, "find id %llu", ksreq->ks_id);
1.1 dlg 568: }
569: }
1.3 dlg 570: }
571:
572: static void
573: kstat_print(struct kstat_tree *kt)
574: {
575: struct kstat_entry *kse;
576: struct kstat_req *ksreq;
1.1 dlg 577:
1.3 dlg 578: RBT_FOREACH(kse, kstat_tree, kt) {
1.1 dlg 579: ksreq = &kse->kstat;
580: printf("%s:%u:%s:%u\n",
581: ksreq->ks_provider, ksreq->ks_instance,
582: ksreq->ks_name, ksreq->ks_unit);
583: if (kse->serrno != 0) {
584: printf("\t%s\n", strerror(kse->serrno));
585: continue;
586: }
587: switch (ksreq->ks_type) {
588: case KSTAT_T_RAW:
589: hexdump(ksreq->ks_data, ksreq->ks_datalen);
590: break;
591: case KSTAT_T_KV:
592: kstat_kv(ksreq->ks_data, ksreq->ks_datalen);
593: break;
594: default:
595: hexdump(ksreq->ks_data, ksreq->ks_datalen);
596: break;
597: }
1.5 dlg 598: }
1.7 dlg 599:
600: fflush(stdout);
1.5 dlg 601: }
602:
603: static void
604: kstat_read(struct kstat_tree *kt, int fd)
605: {
606: struct kstat_entry *kse;
607: struct kstat_req *ksreq;
608:
609: RBT_FOREACH(kse, kstat_tree, kt) {
610: ksreq = &kse->kstat;
611: if (ioctl(fd, KSTATIOC_FIND_ID, ksreq) == -1)
612: err(1, "update id %llu", ksreq->ks_id);
1.1 dlg 613: }
1.10 cheloha 614: }
615:
616: static void
617: handle_alrm(int signo)
618: {
1.1 dlg 619: }