summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Gwynne <dlg@cvs.openbsd.org>2020-07-06 07:09:51 +0000
committerDavid Gwynne <dlg@cvs.openbsd.org>2020-07-06 07:09:51 +0000
commitbe2fc3d12fe18b3541181da23816102c36559141 (patch)
treee297bc44f0855a394f14ef9a26d1d6b1996bd8cb
parentd7d10167cac2fd8f6ee01c4fdb1b15b1ef5dbee9 (diff)
add kstat(8), the userland side of kstat(4).
this currently just dumps all the kstats it can find and prints out their current values. in the future i want to be able to pass what kstats im intersted in as arguments, and tell it to poll stats and show rates instead of current values when appropriate.
-rw-r--r--usr.bin/kstat/Makefile9
-rw-r--r--usr.bin/kstat/kstat.c340
2 files changed, 349 insertions, 0 deletions
diff --git a/usr.bin/kstat/Makefile b/usr.bin/kstat/Makefile
new file mode 100644
index 00000000000..7bf0005cd07
--- /dev/null
+++ b/usr.bin/kstat/Makefile
@@ -0,0 +1,9 @@
+# $OpenBSD: Makefile,v 1.1 2020/07/06 07:09:50 dlg Exp $
+
+PROG= kstat
+SRCS= kstat.c
+MAN=
+WARNINGS=Yes
+DEBUG=-g
+
+.include <bsd.prog.mk>
diff --git a/usr.bin/kstat/kstat.c b/usr.bin/kstat/kstat.c
new file mode 100644
index 00000000000..1964d6c91ba
--- /dev/null
+++ b/usr.bin/kstat/kstat.c
@@ -0,0 +1,340 @@
+/* $OpenBSD: kstat.c,v 1.1 2020/07/06 07:09:50 dlg Exp $ */
+
+/*
+ * Copyright (c) 2020 David Gwynne <dlg@openbsd.org>
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <inttypes.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <err.h>
+#include <vis.h>
+
+#include <sys/tree.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+
+#include <sys/kstat.h>
+
+#ifndef roundup
+#define roundup(x, y) ((((x)+((y)-1))/(y))*(y))
+#endif
+
+#define DEV_KSTAT "/dev/kstat"
+
+static void kstat_list(int, unsigned int);
+
+#if 0
+__dead static void
+usage(void)
+{
+ extern char *__progname;
+ fprintf(stderr, "usage: %s\n", __progname);
+ exit(1);
+}
+#endif
+
+int
+main(int argc, char *argv[])
+{
+ unsigned int version;
+ int fd;
+
+ fd = open(DEV_KSTAT, O_RDONLY);
+ if (fd == -1)
+ err(1, "%s", DEV_KSTAT);
+
+ if (ioctl(fd, KSTATIOC_VERSION, &version) == -1)
+ err(1, "kstat version");
+
+ kstat_list(fd, version);
+
+ return (0);
+}
+
+struct kstat_entry {
+ struct kstat_req kstat;
+ RBT_ENTRY(kstat_entry) entry;
+ int serrno;
+};
+
+RBT_HEAD(kstat_tree, kstat_entry);
+
+static inline int
+kstat_cmp(const struct kstat_entry *ea, const struct kstat_entry *eb)
+{
+ const struct kstat_req *a = &ea->kstat;
+ const struct kstat_req *b = &eb->kstat;
+ int rv;
+
+ rv = strncmp(a->ks_provider, b->ks_provider, sizeof(a->ks_provider));
+ if (rv != 0)
+ return (rv);
+ if (a->ks_instance > b->ks_instance)
+ return (1);
+ if (a->ks_instance < b->ks_instance)
+ return (-1);
+
+ rv = strncmp(a->ks_name, b->ks_name, sizeof(a->ks_name));
+ if (rv != 0)
+ return (rv);
+ if (a->ks_unit > b->ks_unit)
+ return (1);
+ if (a->ks_unit < b->ks_unit)
+ return (-1);
+
+ return (0);
+}
+
+RBT_PROTOTYPE(kstat_tree, kstat_entry, entry, kstat_cmp);
+RBT_GENERATE(kstat_tree, kstat_entry, entry, kstat_cmp);
+
+static int
+printable(int ch)
+{
+ if (ch == '\0')
+ return ('_');
+ if (!isprint(ch))
+ return ('~');
+ return (ch);
+}
+
+static void
+hexdump(const void *d, size_t datalen)
+{
+ const uint8_t *data = d;
+ size_t i, j = 0;
+
+ for (i = 0; i < datalen; i += j) {
+ printf("%4zu: ", i);
+
+ for (j = 0; j < 16 && i+j < datalen; j++)
+ printf("%02x ", data[i + j]);
+ while (j++ < 16)
+ printf(" ");
+ printf("|");
+
+ for (j = 0; j < 16 && i+j < datalen; j++)
+ putchar(printable(data[i + j]));
+ printf("|\n");
+ }
+}
+
+static void
+strdump(const void *s, size_t len)
+{
+ const char *str = s;
+ char dst[8];
+ size_t i;
+
+ for (i = 0; i < len; i++) {
+ char ch = str[i];
+ if (ch == '\0')
+ break;
+
+ vis(dst, ch, VIS_TAB | VIS_NL, 0);
+ printf("%s", dst);
+ }
+}
+
+static void
+strdumpnl(const void *s, size_t len)
+{
+ strdump(s, len);
+ printf("\n");
+}
+
+static void
+kstat_kv(const void *d, ssize_t len)
+{
+ const uint8_t *buf;
+ const struct kstat_kv *kv;
+ ssize_t blen;
+ void (*trailer)(const void *, size_t);
+ double f;
+
+ if (len < (ssize_t)sizeof(*kv)) {
+ warn("short kv (len %zu < size %zu)", len, sizeof(*kv));
+ return;
+ }
+
+ buf = d;
+ do {
+ kv = (const struct kstat_kv *)buf;
+
+ buf += sizeof(*kv);
+ len -= sizeof(*kv);
+
+ blen = 0;
+ trailer = hexdump;
+
+ printf("%16.16s: ", kv->kv_key);
+
+ switch (kv->kv_type) {
+ case KSTAT_KV_T_NULL:
+ printf("null");
+ break;
+ case KSTAT_KV_T_BOOL:
+ printf("%s", kstat_kv_bool(kv) ? "true" : "false");
+ break;
+ case KSTAT_KV_T_COUNTER64:
+ case KSTAT_KV_T_UINT64:
+ printf("%" PRIu64, kstat_kv_u64(kv));
+ break;
+ case KSTAT_KV_T_INT64:
+ printf("%" PRId64, kstat_kv_s64(kv));
+ break;
+ case KSTAT_KV_T_COUNTER32:
+ case KSTAT_KV_T_UINT32:
+ printf("%" PRIu32, kstat_kv_u32(kv));
+ break;
+ case KSTAT_KV_T_INT32:
+ printf("%" PRId32, kstat_kv_s32(kv));
+ break;
+ case KSTAT_KV_T_STR:
+ blen = kstat_kv_len(kv);
+ trailer = strdumpnl;
+ break;
+ case KSTAT_KV_T_BYTES:
+ blen = kstat_kv_len(kv);
+ trailer = hexdump;
+
+ printf("\n");
+ break;
+
+ case KSTAT_KV_T_ISTR:
+ strdump(kstat_kv_istr(kv), sizeof(kstat_kv_istr(kv)));
+ break;
+
+ case KSTAT_KV_T_TEMP:
+ f = kstat_kv_temp(kv);
+ printf("%.2f degC", (f - 273150000.0) / 1000000.0);
+ break;
+
+ default:
+ printf("unknown type %u, stopping\n", kv->kv_type);
+ return;
+ }
+
+ switch (kv->kv_unit) {
+ case KSTAT_KV_U_NONE:
+ break;
+ case KSTAT_KV_U_PACKETS:
+ printf(" packets");
+ break;
+ case KSTAT_KV_U_BYTES:
+ printf(" bytes");
+ break;
+ case KSTAT_KV_U_CYCLES:
+ printf(" cycles");
+ break;
+
+ default:
+ printf(" unit-type-%u", kv->kv_unit);
+ break;
+ }
+
+ if (blen > 0) {
+ if (blen > len) {
+ blen = len;
+ }
+
+ (*trailer)(buf, blen);
+ } else
+ printf("\n");
+
+ blen = roundup(blen, KSTAT_KV_ALIGN);
+ buf += blen;
+ len -= blen;
+ } while (len >= (ssize_t)sizeof(*kv));
+}
+
+static void
+kstat_list(int fd, unsigned int version)
+{
+ struct kstat_entry *kse;
+ struct kstat_req *ksreq;
+ size_t len;
+ uint64_t id = 0;
+ struct kstat_tree kstat_tree = RBT_INITIALIZER();
+
+ for (;;) {
+ kse = malloc(sizeof(*kse));
+ if (kse == NULL)
+ err(1, NULL);
+
+ memset(kse, 0, sizeof(*kse));
+ ksreq = &kse->kstat;
+ ksreq->ks_version = version;
+ ksreq->ks_id = ++id;
+
+ ksreq->ks_datalen = len = 64; /* magic */
+ ksreq->ks_data = malloc(len);
+ if (ksreq->ks_data == NULL)
+ err(1, "data alloc");
+
+ if (ioctl(fd, KSTATIOC_NFIND_ID, ksreq) == -1) {
+ if (errno == ENOENT) {
+ free(ksreq->ks_data);
+ free(kse);
+ break;
+ }
+
+ kse->serrno = errno;
+ goto next;
+ }
+
+ while (ksreq->ks_datalen > len) {
+ len = ksreq->ks_datalen;
+ ksreq->ks_data = realloc(ksreq->ks_data, len);
+ if (ksreq->ks_data == NULL)
+ err(1, "data resize (%zu)", len);
+
+ if (ioctl(fd, KSTATIOC_FIND_ID, ksreq) == -1)
+ err(1, "find id %llu", id);
+ }
+
+next:
+ if (RBT_INSERT(kstat_tree, &kstat_tree, kse) != NULL)
+ errx(1, "duplicate kstat entry");
+
+ id = ksreq->ks_id;
+ }
+
+ RBT_FOREACH(kse, kstat_tree, &kstat_tree) {
+ ksreq = &kse->kstat;
+ printf("%s:%u:%s:%u\n",
+ ksreq->ks_provider, ksreq->ks_instance,
+ ksreq->ks_name, ksreq->ks_unit);
+ if (kse->serrno != 0) {
+ printf("\t%s\n", strerror(kse->serrno));
+ continue;
+ }
+ switch (ksreq->ks_type) {
+ case KSTAT_T_RAW:
+ hexdump(ksreq->ks_data, ksreq->ks_datalen);
+ break;
+ case KSTAT_T_KV:
+ kstat_kv(ksreq->ks_data, ksreq->ks_datalen);
+ break;
+ default:
+ hexdump(ksreq->ks_data, ksreq->ks_datalen);
+ break;
+ }
+ }
+}