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
55 struct fmt_result {
56 uint64_t val;
57 unsigned int frac;
58 unsigned int exp;
59 };
60
61 static void
fmt_thing(struct fmt_result * fr,uint64_t val,uint64_t chunk)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
78 #define str_is_empty(_str) (*(_str) == '\0')
79
80 #define DEV_KSTAT "/dev/kstat"
81
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
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
kstat_cmp(const struct kstat_entry * ea,const struct kstat_entry * eb)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
132 static void handle_alrm(int);
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
138 static void kstat_list(struct kstat_tree *, int, unsigned int,
139 struct kstat_filters *);
140 static void kstat_print(struct kstat_tree *);
141 static void kstat_read(struct kstat_tree *, int);
142
143 __dead static void
usage(void)144 usage(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
154 int
main(int argc,char * argv[])155 main(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
225 static struct kstat_filter *
kstat_filter_parse(char * arg)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) {
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
289 static int
kstat_filter_entry(struct kstat_filters * kfs,const struct kstat_req * ksreq)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 }
319
320 return (0);
321 }
322
323 static int
printable(int ch)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
hexdump(const void * d,size_t datalen)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
strdump(const void * s,size_t len)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
strdumpnl(const void * s,size_t len)372 strdumpnl(const void *s, size_t len)
373 {
374 strdump(s, len);
375 printf("\n");
376 }
377
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
383 static void
kstat_kv(const void * d,ssize_t len)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;
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
524 static void
kstat_list(struct kstat_tree * kt,int fd,unsigned int version,struct kstat_filters * kfs)525 kstat_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
566 static void
kstat_print(struct kstat_tree * kt)567 kstat_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
598 static void
kstat_read(struct kstat_tree * kt,int fd)599 kstat_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
612 static void
handle_alrm(int signo)613 handle_alrm(int signo)
614 {
615 }
616