1 /* $OpenBSD: kstat.c,v 1.11 2022/07/10 19:51:37 kn 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 #define str_is_empty(_str) (*(_str) == '\0') 56 57 #define DEV_KSTAT "/dev/kstat" 58 59 struct kstat_filter { 60 TAILQ_ENTRY(kstat_filter) kf_entry; 61 const char *kf_provider; 62 const char *kf_name; 63 unsigned int kf_flags; 64 #define KSTAT_FILTER_F_INST (1 << 0) 65 #define KSTAT_FILTER_F_UNIT (1 << 1) 66 unsigned int kf_instance; 67 unsigned int kf_unit; 68 }; 69 70 TAILQ_HEAD(kstat_filters, kstat_filter); 71 72 struct kstat_entry { 73 struct kstat_req kstat; 74 RBT_ENTRY(kstat_entry) entry; 75 int serrno; 76 }; 77 78 RBT_HEAD(kstat_tree, kstat_entry); 79 80 static inline int 81 kstat_cmp(const struct kstat_entry *ea, const struct kstat_entry *eb) 82 { 83 const struct kstat_req *a = &ea->kstat; 84 const struct kstat_req *b = &eb->kstat; 85 int rv; 86 87 rv = strncmp(a->ks_provider, b->ks_provider, sizeof(a->ks_provider)); 88 if (rv != 0) 89 return (rv); 90 if (a->ks_instance > b->ks_instance) 91 return (1); 92 if (a->ks_instance < b->ks_instance) 93 return (-1); 94 95 rv = strncmp(a->ks_name, b->ks_name, sizeof(a->ks_name)); 96 if (rv != 0) 97 return (rv); 98 if (a->ks_unit > b->ks_unit) 99 return (1); 100 if (a->ks_unit < b->ks_unit) 101 return (-1); 102 103 return (0); 104 } 105 106 RBT_PROTOTYPE(kstat_tree, kstat_entry, entry, kstat_cmp); 107 RBT_GENERATE(kstat_tree, kstat_entry, entry, kstat_cmp); 108 109 static void handle_alrm(int); 110 static struct kstat_filter * 111 kstat_filter_parse(char *); 112 static int kstat_filter_entry(struct kstat_filters *, 113 const struct kstat_req *); 114 115 static void kstat_list(struct kstat_tree *, int, unsigned int, 116 struct kstat_filters *); 117 static void kstat_print(struct kstat_tree *); 118 static void kstat_read(struct kstat_tree *, int); 119 120 __dead static void 121 usage(void) 122 { 123 extern char *__progname; 124 125 fprintf(stderr, "usage: %s [-w wait] " 126 "[name | provider:instance:name:unit] ...\n", __progname); 127 128 exit(1); 129 } 130 131 int 132 main(int argc, char *argv[]) 133 { 134 struct kstat_filters kfs = TAILQ_HEAD_INITIALIZER(kfs); 135 struct kstat_tree kt = RBT_INITIALIZER(); 136 unsigned int version; 137 int fd; 138 const char *errstr; 139 int ch; 140 struct itimerval itv; 141 sigset_t empty, mask; 142 int i; 143 unsigned int wait = 0; 144 145 while ((ch = getopt(argc, argv, "w:")) != -1) { 146 switch (ch) { 147 case 'w': 148 wait = strtonum(optarg, 1, UINT_MAX, &errstr); 149 if (errstr != NULL) 150 errx(1, "wait is %s: %s", errstr, optarg); 151 break; 152 default: 153 usage(); 154 } 155 } 156 157 argc -= optind; 158 argv += optind; 159 160 for (i = 0; i < argc; i++) { 161 struct kstat_filter *kf = kstat_filter_parse(argv[i]); 162 TAILQ_INSERT_TAIL(&kfs, kf, kf_entry); 163 } 164 165 fd = open(DEV_KSTAT, O_RDONLY); 166 if (fd == -1) 167 err(1, "%s", DEV_KSTAT); 168 169 if (ioctl(fd, KSTATIOC_VERSION, &version) == -1) 170 err(1, "kstat version"); 171 172 kstat_list(&kt, fd, version, &kfs); 173 kstat_print(&kt); 174 175 if (wait == 0) 176 return (0); 177 178 if (signal(SIGALRM, handle_alrm) == SIG_ERR) 179 err(1, "signal"); 180 sigemptyset(&empty); 181 sigemptyset(&mask); 182 sigaddset(&mask, SIGALRM); 183 if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) 184 err(1, "sigprocmask"); 185 186 itv.it_value.tv_sec = wait; 187 itv.it_value.tv_usec = 0; 188 itv.it_interval = itv.it_value; 189 if (setitimer(ITIMER_REAL, &itv, NULL) == -1) 190 err(1, "setitimer"); 191 192 for (;;) { 193 sigsuspend(&empty); 194 kstat_read(&kt, fd); 195 kstat_print(&kt); 196 } 197 198 return (0); 199 } 200 201 static struct kstat_filter * 202 kstat_filter_parse(char *arg) 203 { 204 struct kstat_filter *kf; 205 const char *errstr; 206 char *argv[4]; 207 size_t argc; 208 209 for (argc = 0; argc < nitems(argv); argc++) { 210 char *s = strsep(&arg, ":"); 211 if (s == NULL) 212 break; 213 214 argv[argc] = s; 215 } 216 if (arg != NULL) 217 usage(); 218 219 kf = malloc(sizeof(*kf)); 220 if (kf == NULL) 221 err(1, NULL); 222 223 memset(kf, 0, sizeof(*kf)); 224 225 switch (argc) { 226 case 1: 227 if (str_is_empty(argv[0])) 228 errx(1, "empty name"); 229 230 kf->kf_name = argv[0]; 231 break; 232 case 4: 233 if (!str_is_empty(argv[0])) 234 kf->kf_provider = argv[0]; 235 if (!str_is_empty(argv[1])) { 236 kf->kf_instance = 237 strtonum(argv[1], 0, 0xffffffffU, &errstr); 238 if (errstr != NULL) { 239 errx(1, "%s:%s:%s:%s: instance %s: %s", 240 argv[0], argv[1], argv[2], argv[3], 241 argv[1], errstr); 242 } 243 SET(kf->kf_flags, KSTAT_FILTER_F_INST); 244 } 245 if (!str_is_empty(argv[2])) 246 kf->kf_name = argv[2]; 247 if (!str_is_empty(argv[3])) { 248 kf->kf_unit = 249 strtonum(argv[3], 0, 0xffffffffU, &errstr); 250 if (errstr != NULL) { 251 errx(1, "%s:%s:%s:%s: unit %s: %s", 252 argv[0], argv[1], argv[2], argv[3], 253 argv[3], errstr); 254 } 255 SET(kf->kf_flags, KSTAT_FILTER_F_UNIT); 256 } 257 break; 258 default: 259 usage(); 260 } 261 262 return (kf); 263 } 264 265 static int 266 kstat_filter_entry(struct kstat_filters *kfs, const struct kstat_req *ksreq) 267 { 268 struct kstat_filter *kf; 269 270 if (TAILQ_EMPTY(kfs)) 271 return (1); 272 273 TAILQ_FOREACH(kf, kfs, kf_entry) { 274 if (kf->kf_provider != NULL) { 275 if (fnmatch(kf->kf_provider, ksreq->ks_provider, 276 FNM_NOESCAPE | FNM_LEADING_DIR) == FNM_NOMATCH) 277 continue; 278 } 279 if (ISSET(kf->kf_flags, KSTAT_FILTER_F_INST)) { 280 if (kf->kf_instance != ksreq->ks_instance) 281 continue; 282 } 283 if (kf->kf_name != NULL) { 284 if (fnmatch(kf->kf_name, ksreq->ks_name, 285 FNM_NOESCAPE | FNM_LEADING_DIR) == FNM_NOMATCH) 286 continue; 287 } 288 if (ISSET(kf->kf_flags, KSTAT_FILTER_F_UNIT)) { 289 if (kf->kf_unit != ksreq->ks_unit) 290 continue; 291 } 292 293 return (1); 294 } 295 296 return (0); 297 } 298 299 static int 300 printable(int ch) 301 { 302 if (ch == '\0') 303 return ('_'); 304 if (!isprint(ch)) 305 return ('~'); 306 return (ch); 307 } 308 309 static void 310 hexdump(const void *d, size_t datalen) 311 { 312 const uint8_t *data = d; 313 size_t i, j = 0; 314 315 for (i = 0; i < datalen; i += j) { 316 printf("%4zu: ", i); 317 318 for (j = 0; j < 16 && i+j < datalen; j++) 319 printf("%02x ", data[i + j]); 320 while (j++ < 16) 321 printf(" "); 322 printf("|"); 323 324 for (j = 0; j < 16 && i+j < datalen; j++) 325 putchar(printable(data[i + j])); 326 printf("|\n"); 327 } 328 } 329 330 static void 331 strdump(const void *s, size_t len) 332 { 333 const char *str = s; 334 char dst[8]; 335 size_t i; 336 337 for (i = 0; i < len; i++) { 338 char ch = str[i]; 339 if (ch == '\0') 340 break; 341 342 vis(dst, ch, VIS_TAB | VIS_NL, 0); 343 printf("%s", dst); 344 } 345 } 346 347 static void 348 strdumpnl(const void *s, size_t len) 349 { 350 strdump(s, len); 351 printf("\n"); 352 } 353 354 static void 355 kstat_kv(const void *d, ssize_t len) 356 { 357 const uint8_t *buf; 358 const struct kstat_kv *kv; 359 ssize_t blen; 360 void (*trailer)(const void *, size_t); 361 double f; 362 363 if (len < (ssize_t)sizeof(*kv)) { 364 warn("short kv (len %zu < size %zu)", len, sizeof(*kv)); 365 return; 366 } 367 368 buf = d; 369 do { 370 kv = (const struct kstat_kv *)buf; 371 372 buf += sizeof(*kv); 373 len -= sizeof(*kv); 374 375 blen = 0; 376 trailer = hexdump; 377 378 printf("%16.16s: ", kv->kv_key); 379 380 switch (kv->kv_type) { 381 case KSTAT_KV_T_NULL: 382 printf("null"); 383 break; 384 case KSTAT_KV_T_BOOL: 385 printf("%s", kstat_kv_bool(kv) ? "true" : "false"); 386 break; 387 case KSTAT_KV_T_COUNTER64: 388 case KSTAT_KV_T_UINT64: 389 printf("%" PRIu64, kstat_kv_u64(kv)); 390 break; 391 case KSTAT_KV_T_INT64: 392 printf("%" PRId64, kstat_kv_s64(kv)); 393 break; 394 case KSTAT_KV_T_COUNTER32: 395 case KSTAT_KV_T_UINT32: 396 printf("%" PRIu32, kstat_kv_u32(kv)); 397 break; 398 case KSTAT_KV_T_INT32: 399 printf("%" PRId32, kstat_kv_s32(kv)); 400 break; 401 case KSTAT_KV_T_COUNTER16: 402 case KSTAT_KV_T_UINT16: 403 printf("%" PRIu16, kstat_kv_u16(kv)); 404 break; 405 case KSTAT_KV_T_INT16: 406 printf("%" PRId16, kstat_kv_s16(kv)); 407 break; 408 case KSTAT_KV_T_STR: 409 blen = kstat_kv_len(kv); 410 trailer = strdumpnl; 411 break; 412 case KSTAT_KV_T_BYTES: 413 blen = kstat_kv_len(kv); 414 trailer = hexdump; 415 416 printf("\n"); 417 break; 418 419 case KSTAT_KV_T_ISTR: 420 strdump(kstat_kv_istr(kv), sizeof(kstat_kv_istr(kv))); 421 break; 422 423 case KSTAT_KV_T_TEMP: 424 f = kstat_kv_temp(kv); 425 printf("%.2f degC", (f - 273150000.0) / 1000000.0); 426 break; 427 428 default: 429 printf("unknown type %u, stopping\n", kv->kv_type); 430 return; 431 } 432 433 switch (kv->kv_unit) { 434 case KSTAT_KV_U_NONE: 435 break; 436 case KSTAT_KV_U_PACKETS: 437 printf(" packets"); 438 break; 439 case KSTAT_KV_U_BYTES: 440 printf(" bytes"); 441 break; 442 case KSTAT_KV_U_CYCLES: 443 printf(" cycles"); 444 break; 445 446 default: 447 printf(" unit-type-%u", kv->kv_unit); 448 break; 449 } 450 451 if (blen > 0) { 452 if (blen > len) { 453 blen = len; 454 } 455 456 (*trailer)(buf, blen); 457 } else 458 printf("\n"); 459 460 blen = roundup(blen, KSTAT_KV_ALIGN); 461 buf += blen; 462 len -= blen; 463 } while (len >= (ssize_t)sizeof(*kv)); 464 } 465 466 static void 467 kstat_list(struct kstat_tree *kt, int fd, unsigned int version, 468 struct kstat_filters *kfs) 469 { 470 struct kstat_entry *kse; 471 struct kstat_req *ksreq; 472 size_t len; 473 uint64_t id = 0; 474 475 for (;;) { 476 kse = malloc(sizeof(*kse)); 477 if (kse == NULL) 478 err(1, NULL); 479 480 memset(kse, 0, sizeof(*kse)); 481 ksreq = &kse->kstat; 482 ksreq->ks_version = version; 483 ksreq->ks_id = ++id; 484 485 ksreq->ks_datalen = len = 64; /* magic */ 486 ksreq->ks_data = malloc(len); 487 if (ksreq->ks_data == NULL) 488 err(1, "data alloc"); 489 490 if (ioctl(fd, KSTATIOC_NFIND_ID, ksreq) == -1) { 491 if (errno == ENOENT) { 492 free(ksreq->ks_data); 493 free(kse); 494 break; 495 } 496 497 kse->serrno = errno; 498 } else 499 id = ksreq->ks_id; 500 501 if (!kstat_filter_entry(kfs, ksreq)) { 502 free(ksreq->ks_data); 503 free(kse); 504 continue; 505 } 506 507 if (RBT_INSERT(kstat_tree, kt, kse) != NULL) 508 errx(1, "duplicate kstat entry"); 509 510 if (kse->serrno != 0) 511 continue; 512 513 while (ksreq->ks_datalen > len) { 514 len = ksreq->ks_datalen; 515 ksreq->ks_data = realloc(ksreq->ks_data, len); 516 if (ksreq->ks_data == NULL) 517 err(1, "data resize (%zu)", len); 518 519 if (ioctl(fd, KSTATIOC_FIND_ID, ksreq) == -1) 520 err(1, "find id %llu", ksreq->ks_id); 521 } 522 } 523 } 524 525 static void 526 kstat_print(struct kstat_tree *kt) 527 { 528 struct kstat_entry *kse; 529 struct kstat_req *ksreq; 530 531 RBT_FOREACH(kse, kstat_tree, kt) { 532 ksreq = &kse->kstat; 533 printf("%s:%u:%s:%u\n", 534 ksreq->ks_provider, ksreq->ks_instance, 535 ksreq->ks_name, ksreq->ks_unit); 536 if (kse->serrno != 0) { 537 printf("\t%s\n", strerror(kse->serrno)); 538 continue; 539 } 540 switch (ksreq->ks_type) { 541 case KSTAT_T_RAW: 542 hexdump(ksreq->ks_data, ksreq->ks_datalen); 543 break; 544 case KSTAT_T_KV: 545 kstat_kv(ksreq->ks_data, ksreq->ks_datalen); 546 break; 547 default: 548 hexdump(ksreq->ks_data, ksreq->ks_datalen); 549 break; 550 } 551 } 552 553 fflush(stdout); 554 } 555 556 static void 557 kstat_read(struct kstat_tree *kt, int fd) 558 { 559 struct kstat_entry *kse; 560 struct kstat_req *ksreq; 561 562 RBT_FOREACH(kse, kstat_tree, kt) { 563 ksreq = &kse->kstat; 564 if (ioctl(fd, KSTATIOC_FIND_ID, ksreq) == -1) 565 err(1, "update id %llu", ksreq->ks_id); 566 } 567 } 568 569 static void 570 handle_alrm(int signo) 571 { 572 } 573