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