Annotation of src/usr.bin/kstat/kstat.c, Revision 1.9
1.9 ! dlg 1: /* $OpenBSD: kstat.c,v 1.8 2021/01/25 06:55:59 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>
18: #include <stdio.h>
19: #include <stdlib.h>
20: #include <stddef.h>
21: #include <string.h>
22: #include <inttypes.h>
1.2 dlg 23: #include <fnmatch.h>
1.1 dlg 24: #include <fcntl.h>
1.3 dlg 25: #include <unistd.h>
1.1 dlg 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>
1.2 dlg 33: #include <sys/queue.h>
1.1 dlg 34:
35: #include <sys/kstat.h>
36:
37: #ifndef roundup
1.2 dlg 38: #define roundup(x, y) ((((x)+((y)-1))/(y))*(y))
1.1 dlg 39: #endif
40:
1.2 dlg 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:
1.1 dlg 55: #define DEV_KSTAT "/dev/kstat"
56:
1.2 dlg 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:
1.4 dlg 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:
1.2 dlg 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:
1.3 dlg 112: static void kstat_list(struct kstat_tree *, int, unsigned int,
113: struct kstat_filters *);
114: static void kstat_print(struct kstat_tree *);
1.5 dlg 115: static void kstat_read(struct kstat_tree *, int);
1.1 dlg 116:
117: __dead static void
118: usage(void)
119: {
120: extern char *__progname;
1.2 dlg 121:
1.5 dlg 122: fprintf(stderr, "usage: %s [-w wait] "
1.6 schwarze 123: "[name | provider:0:name:unit] ...\n", __progname);
1.2 dlg 124:
1.1 dlg 125: exit(1);
126: }
127:
128: int
129: main(int argc, char *argv[])
130: {
1.2 dlg 131: struct kstat_filters kfs = TAILQ_HEAD_INITIALIZER(kfs);
1.3 dlg 132: struct kstat_tree kt = RBT_INITIALIZER();
1.1 dlg 133: unsigned int version;
134: int fd;
1.5 dlg 135: const char *errstr;
136: int ch;
137: struct timespec interval = { 0, 0 };
1.2 dlg 138: int i;
139:
1.5 dlg 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++) {
1.2 dlg 157: struct kstat_filter *kf = kstat_filter_parse(argv[i]);
158: TAILQ_INSERT_TAIL(&kfs, kf, kf_entry);
159: }
1.1 dlg 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:
1.3 dlg 168: kstat_list(&kt, fd, version, &kfs);
169: kstat_print(&kt);
1.2 dlg 170:
1.5 dlg 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:
1.2 dlg 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) {
1.8 dlg 234: errx(1, "%s:%s:%s:%s: unit %s: %s",
1.2 dlg 235: argv[0], argv[1], argv[2], argv[3],
1.8 dlg 236: argv[3], errstr);
1.2 dlg 237: }
1.8 dlg 238: SET(kf->kf_flags, KSTAT_FILTER_F_UNIT);
1.2 dlg 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: }
1.1 dlg 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));
1.9 ! dlg 383: break;
! 384: case KSTAT_KV_T_COUNTER16:
! 385: case KSTAT_KV_T_UINT16:
! 386: printf("%" PRIu16, kstat_kv_u16(kv));
! 387: break;
! 388: case KSTAT_KV_T_INT16:
! 389: printf("%" PRId16, kstat_kv_s16(kv));
1.1 dlg 390: break;
391: case KSTAT_KV_T_STR:
392: blen = kstat_kv_len(kv);
393: trailer = strdumpnl;
394: break;
395: case KSTAT_KV_T_BYTES:
396: blen = kstat_kv_len(kv);
397: trailer = hexdump;
398:
399: printf("\n");
400: break;
401:
402: case KSTAT_KV_T_ISTR:
403: strdump(kstat_kv_istr(kv), sizeof(kstat_kv_istr(kv)));
404: break;
405:
406: case KSTAT_KV_T_TEMP:
407: f = kstat_kv_temp(kv);
408: printf("%.2f degC", (f - 273150000.0) / 1000000.0);
409: break;
410:
411: default:
412: printf("unknown type %u, stopping\n", kv->kv_type);
413: return;
414: }
415:
416: switch (kv->kv_unit) {
417: case KSTAT_KV_U_NONE:
418: break;
419: case KSTAT_KV_U_PACKETS:
420: printf(" packets");
421: break;
422: case KSTAT_KV_U_BYTES:
423: printf(" bytes");
424: break;
425: case KSTAT_KV_U_CYCLES:
426: printf(" cycles");
427: break;
428:
429: default:
430: printf(" unit-type-%u", kv->kv_unit);
431: break;
432: }
433:
434: if (blen > 0) {
435: if (blen > len) {
436: blen = len;
437: }
438:
439: (*trailer)(buf, blen);
440: } else
441: printf("\n");
442:
443: blen = roundup(blen, KSTAT_KV_ALIGN);
444: buf += blen;
445: len -= blen;
446: } while (len >= (ssize_t)sizeof(*kv));
447: }
448:
449: static void
1.3 dlg 450: kstat_list(struct kstat_tree *kt, int fd, unsigned int version,
451: struct kstat_filters *kfs)
1.1 dlg 452: {
453: struct kstat_entry *kse;
454: struct kstat_req *ksreq;
455: size_t len;
456: uint64_t id = 0;
457:
458: for (;;) {
459: kse = malloc(sizeof(*kse));
460: if (kse == NULL)
461: err(1, NULL);
462:
463: memset(kse, 0, sizeof(*kse));
464: ksreq = &kse->kstat;
465: ksreq->ks_version = version;
466: ksreq->ks_id = ++id;
467:
468: ksreq->ks_datalen = len = 64; /* magic */
469: ksreq->ks_data = malloc(len);
470: if (ksreq->ks_data == NULL)
471: err(1, "data alloc");
472:
473: if (ioctl(fd, KSTATIOC_NFIND_ID, ksreq) == -1) {
474: if (errno == ENOENT) {
475: free(ksreq->ks_data);
476: free(kse);
477: break;
478: }
479:
480: kse->serrno = errno;
1.2 dlg 481: } else
482: id = ksreq->ks_id;
483:
484: if (!kstat_filter_entry(kfs, ksreq)) {
485: free(ksreq->ks_data);
486: free(kse);
487: continue;
1.1 dlg 488: }
489:
1.3 dlg 490: if (RBT_INSERT(kstat_tree, kt, kse) != NULL)
1.2 dlg 491: errx(1, "duplicate kstat entry");
492:
493: if (kse->serrno != 0)
494: continue;
495:
1.1 dlg 496: while (ksreq->ks_datalen > len) {
497: len = ksreq->ks_datalen;
498: ksreq->ks_data = realloc(ksreq->ks_data, len);
499: if (ksreq->ks_data == NULL)
500: err(1, "data resize (%zu)", len);
501:
502: if (ioctl(fd, KSTATIOC_FIND_ID, ksreq) == -1)
1.2 dlg 503: err(1, "find id %llu", ksreq->ks_id);
1.1 dlg 504: }
505: }
1.3 dlg 506: }
507:
508: static void
509: kstat_print(struct kstat_tree *kt)
510: {
511: struct kstat_entry *kse;
512: struct kstat_req *ksreq;
1.1 dlg 513:
1.3 dlg 514: RBT_FOREACH(kse, kstat_tree, kt) {
1.1 dlg 515: ksreq = &kse->kstat;
516: printf("%s:%u:%s:%u\n",
517: ksreq->ks_provider, ksreq->ks_instance,
518: ksreq->ks_name, ksreq->ks_unit);
519: if (kse->serrno != 0) {
520: printf("\t%s\n", strerror(kse->serrno));
521: continue;
522: }
523: switch (ksreq->ks_type) {
524: case KSTAT_T_RAW:
525: hexdump(ksreq->ks_data, ksreq->ks_datalen);
526: break;
527: case KSTAT_T_KV:
528: kstat_kv(ksreq->ks_data, ksreq->ks_datalen);
529: break;
530: default:
531: hexdump(ksreq->ks_data, ksreq->ks_datalen);
532: break;
533: }
1.5 dlg 534: }
1.7 dlg 535:
536: fflush(stdout);
1.5 dlg 537: }
538:
539: static void
540: kstat_read(struct kstat_tree *kt, int fd)
541: {
542: struct kstat_entry *kse;
543: struct kstat_req *ksreq;
544:
545: RBT_FOREACH(kse, kstat_tree, kt) {
546: ksreq = &kse->kstat;
547: if (ioctl(fd, KSTATIOC_FIND_ID, ksreq) == -1)
548: err(1, "update id %llu", ksreq->ks_id);
1.1 dlg 549: }
550: }