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