Module Name:    src
Committed By:   pgoyette
Date:           Sun Jan  1 23:58:47 UTC 2017

Modified Files:
        src/sys/kern: kern_history.c
        src/sys/sys: kernhist.h

Log Message:
Provide a sysctl method of exporting the kernel history data.

XXX vmstat will be update soon to use the sysctl rather than grovelling
XXX through kvm.


To generate a diff of this commit:
cvs rdiff -u -r1.8 -r1.9 src/sys/kern/kern_history.c
cvs rdiff -u -r1.13 -r1.14 src/sys/sys/kernhist.h

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/kern/kern_history.c
diff -u src/sys/kern/kern_history.c:1.8 src/sys/kern/kern_history.c:1.9
--- src/sys/kern/kern_history.c:1.8	Mon Dec 26 23:49:53 2016
+++ src/sys/kern/kern_history.c	Sun Jan  1 23:58:47 2017
@@ -1,4 +1,4 @@
-/*	$NetBSD: kern_history.c,v 1.8 2016/12/26 23:49:53 pgoyette Exp $	 */
+/*	$NetBSD: kern_history.c,v 1.9 2017/01/01 23:58:47 pgoyette Exp $	 */
 
 /*
  * Copyright (c) 1997 Charles D. Cranor and Washington University.
@@ -33,7 +33,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: kern_history.c,v 1.8 2016/12/26 23:49:53 pgoyette Exp $");
+__KERNEL_RCSID(0, "$NetBSD: kern_history.c,v 1.9 2017/01/01 23:58:47 pgoyette Exp $");
 
 #include "opt_ddb.h"
 #include "opt_kernhist.h"
@@ -41,11 +41,14 @@ __KERNEL_RCSID(0, "$NetBSD: kern_history
 #include "opt_usb.h"
 #include "opt_uvmhist.h"
 #include "opt_biohist.h"
+#include "opt_sysctl.h"
 
 #include <sys/param.h>
 #include <sys/systm.h>
 #include <sys/cpu.h>
+#include <sys/sysctl.h>
 #include <sys/kernhist.h>
+#include <sys/kmem.h>
 
 #ifdef UVMHIST
 #include <uvm/uvm.h>
@@ -63,6 +66,12 @@ __KERNEL_RCSID(0, "$NetBSD: kern_history
 KERNHIST_DECL(scdebughist);
 #endif
 
+struct addr_xlt {
+	const char *addr;
+	size_t len;
+	uint32_t offset;
+};
+
 /*
  * globals
  */
@@ -71,6 +80,8 @@ struct kern_history_head kern_histories;
 
 int kernhist_print_enabled = 1;
 
+int sysctl_hist_node;
+
 #ifdef DDB
 
 /*
@@ -79,10 +90,11 @@ int kernhist_print_enabled = 1;
 
 void kernhist_dump(struct kern_history *,
     void (*)(const char *, ...) __printflike(1, 2));
-void kernhist_dumpmask(u_int32_t);
+void kernhist_dumpmask(uint32_t);
 static void kernhist_dump_histories(struct kern_history *[],
     void (*)(const char *, ...) __printflike(1, 2));
 
+static int sysctl_kernhist_helper(SYSCTLFN_PROTO);
 
 /*
  * call this from ddb
@@ -176,7 +188,7 @@ restart:
  * expects the system to be quiesced, no locking
  */
 void
-kernhist_dumpmask(u_int32_t bitmask)	/* XXX only support 32 hists */
+kernhist_dumpmask(uint32_t bitmask)	/* XXX only support 32 hists */
 {
 	struct kern_history *hists[MAXHISTS + 1];
 	int i = 0;
@@ -256,3 +268,251 @@ kernhist_print(void *addr, void (*pr)(co
 }
 
 #endif
+
+/*
+ * sysctl interface
+ */
+
+/*
+ * sysctl_hist_new()
+ *
+ *	Scan the list of histories;  for any history that does not already
+ *	have a sysctl node (under kern.hist) we create a new one and record
+ *	it's node number.
+ */
+static void
+sysctl_hist_new(void)
+{
+	int error;
+	struct kern_history *h;
+	const struct sysctlnode *rnode = NULL;
+
+	LIST_FOREACH(h, &kern_histories, list) {
+		if (h->s != 0)
+			continue;
+		error = sysctl_createv(NULL, 0, NULL, &rnode,
+			    CTLFLAG_PERMANENT,
+			    CTLTYPE_STRUCT, h->name,
+			    SYSCTL_DESCR("history data"),
+			    sysctl_kernhist_helper, 0, NULL, 0,
+			    CTL_KERN, sysctl_hist_node, CTL_CREATE, CTL_EOL);
+		if (error == 0)
+			h->s = rnode->sysctl_num;
+	}
+}
+
+/*
+ * sysctl_kerhnist_init()
+ *
+ *	Create the 2nd level "hw.hist" sysctl node
+ */
+void
+sysctl_kernhist_init(void)
+{
+	const struct sysctlnode *rnode = NULL;
+
+	sysctl_createv(NULL, 0, NULL, &rnode,
+			CTLFLAG_PERMANENT,
+			CTLTYPE_NODE, "hist",
+			SYSCTL_DESCR("kernel history tables"),
+			sysctl_kernhist_helper, 0, NULL, 0,
+			CTL_KERN, CTL_CREATE, CTL_EOL);
+	sysctl_hist_node = rnode->sysctl_num;
+
+	sysctl_hist_new();
+}
+
+/*
+ * find_string()
+ *
+ *	Search the address-to-offset translation table for matching an
+ *	address and len, and return the index of the entry we found.  If
+ *	not found, returns index 0 which points to the "?" entry.  (We
+ *	start matching at index 1, ignoring any matches of the "?" entry
+ *	itself.)
+ */
+static int
+find_string(struct addr_xlt table[], size_t *count, const char *string,
+	    size_t len)
+{
+	int i;
+
+	for (i = 1; i < *count; i++)
+		if (string == table[i].addr && len == table[i].len)
+			return i;
+
+	return 0;
+}
+
+/*
+ * add_string()
+ *
+ *	If the string and len are unique, add a new address-to-offset
+ *	entry in the translation table and set the offset of the next
+ *	entry.
+ */
+static void
+add_string(struct addr_xlt table[], size_t *count, const char *string,
+	   size_t len)
+{
+
+	if (find_string(table, count, string, len) == 0) {
+		table[*count].addr = string;
+		table[*count].len = len;
+		table[*count + 1].offset = table[*count].offset + len + 1;
+		(*count)++;
+	}
+}
+
+/*
+ * sysctl_kernhist_helper
+ *
+ *	This helper routine is called for all accesses to the kern.hist
+ *	hierarchy.
+ */
+static int
+sysctl_kernhist_helper(SYSCTLFN_ARGS)
+{
+	struct kern_history *h;
+	struct kern_history_ent *in_evt;
+	struct sysctl_history_event *out_evt;
+	struct sysctl_history *buf;
+	struct addr_xlt *xlate_t, *xlt;
+	size_t bufsize, xlate_s;
+	size_t xlate_c;
+	const char *strp;
+	char *next;
+	int i, j;
+	int error;
+
+	sysctl_hist_new();	/* make sure we're up to date */
+
+	if (namelen == 1 && name[0] == CTL_QUERY)
+		return sysctl_query(SYSCTLFN_CALL(rnode));
+
+	/*
+	 * Disallow userland updates, verify that we arrived at a
+	 * valid history rnode
+	 */
+	if (newp)
+		return EPERM;
+	if (namelen != 1 || name[0] != CTL_EOL)
+		return EINVAL;
+
+	/* Find the correct kernhist for this sysctl node */
+	LIST_FOREACH(h, &kern_histories, list) {
+		if (h->s == rnode->sysctl_num)
+			break;
+	}
+	if (h == NULL)
+		return ENOENT;
+
+	/*
+	 * Worst case is two string pointers per history entry, plus
+	 * two for the history name and "?" string; allocate an extra
+	 * entry since we pre-set the "next" entry's offset member.
+	 */
+	xlate_s = sizeof(struct addr_xlt) * h->n * 2 + 3;
+	xlate_t = kmem_alloc(xlate_s, KM_SLEEP);
+	xlate_c = 0;
+
+	/* offset 0 reserved for NULL pointer, ie unused history entry */
+	xlate_t[0].offset = 1;
+
+	/*
+	 * If the history gets updated and an unexpected string is
+	 * found later, we'll point it here.  Otherwise, we'd have to
+	 * repeat this process iteratively, and it could take multiple
+	 * iterations before terminating.
+	 */
+	add_string(xlate_t, &xlate_c, "?", 0);
+
+	/* Copy the history name itself to the export structure */
+	add_string(xlate_t, &xlate_c, h->name, h->namelen);
+
+	/*
+	 * Loop through all used history entries to find the unique
+	 * fn and fmt strings
+	 */
+	for (i = 0, in_evt = h->e; i < h->n; i++, in_evt++) {
+		if (in_evt->fn == NULL)
+			continue;
+		add_string(xlate_t, &xlate_c, in_evt->fn, in_evt->fnlen);
+		add_string(xlate_t, &xlate_c, in_evt->fmt, in_evt->fmtlen);
+	}
+
+	/* Total buffer size includes header, events, and string table */
+	bufsize = sizeof(struct sysctl_history) + 
+	    h->n * sizeof(struct sysctl_history_event) +
+	    xlate_t[xlate_c].offset;
+	buf = kmem_alloc(bufsize, KM_SLEEP);
+
+	/*
+	 * Copy history header info to the export structure
+	 */
+	j = find_string(xlate_t, &xlate_c, h->name, h->namelen);
+	buf->sh_listentry.shle_nameoffset = xlate_t[j].offset;
+	buf->sh_listentry.shle_numentries = h->n;
+	buf->sh_listentry.shle_nextfree = h->f;
+
+	/*
+	 * Loop through the history events again, copying the data to
+	 * the export structure
+	 */
+	for (i = 0, in_evt = h->e, out_evt = buf->sh_events; i < h->n;
+	    i++, in_evt++, out_evt++) {
+		if (in_evt->fn == NULL) {	/* skip unused entries */
+			out_evt->she_funcoffset = 0;
+			out_evt->she_fmtoffset = 0;
+			continue;
+		}
+		TIMEVAL_TO_TIMESPEC(&in_evt->tv, &out_evt->she_tspec);
+		out_evt->she_callnumber = in_evt->call;
+		out_evt->she_cpunum = in_evt->cpunum;
+		out_evt->she_values[0] = in_evt->v[0];
+		out_evt->she_values[1] = in_evt->v[1];
+		out_evt->she_values[2] = in_evt->v[2];
+		out_evt->she_values[3] = in_evt->v[3];
+		j = find_string(xlate_t, &xlate_c, in_evt->fn, in_evt->fnlen);
+		out_evt->she_funcoffset = xlate_t[j].offset;
+		j = find_string(xlate_t, &xlate_c, in_evt->fmt, in_evt->fmtlen);
+		out_evt->she_fmtoffset = xlate_t[j].offset;
+	}
+
+	/*
+	 * Finally, fill the text string area with all the unique
+	 * strings we found earlier.
+	 *
+	 * Skip the initial byte, since we use an offset of 0 to mean
+	 * a NULL pointer (which means an unused history event).
+	 */
+	strp = next = (char *)(&buf->sh_events[h->n]);
+	*next++ = '\0';
+
+	/*
+	 * Then copy each string into the export structure, making
+	 * sure to terminate each string with a '\0' character
+	 */
+	for (i = 0, xlt = xlate_t; i < xlate_c; i++, xlt++) {
+		KASSERTMSG((next - strp) == xlt->offset,
+		    "entry %d at wrong offset %"PRIu32, i, xlt->offset);
+		memcpy(next, xlt->addr, xlt->len);
+		next += xlt->len;
+		*next++ = '\0';	
+	}
+
+	/* Copy data to userland */
+	error = copyout(buf, oldp, min(bufsize, *oldlenp));
+
+	/* If copyout was successful but only partial, report ENOMEM */
+	if (error == 0 && *oldlenp < bufsize)
+		error = ENOMEM;
+
+	*oldlenp = bufsize;	/* inform userland of space requirements */
+
+	/* Free up the stuff we allocated */
+	kmem_free(buf, bufsize);
+	kmem_free(xlate_t, xlate_s);
+
+	return error;
+}

Index: src/sys/sys/kernhist.h
diff -u src/sys/sys/kernhist.h:1.13 src/sys/sys/kernhist.h:1.14
--- src/sys/sys/kernhist.h:1.13	Mon Dec 26 23:12:33 2016
+++ src/sys/sys/kernhist.h	Sun Jan  1 23:58:47 2017
@@ -1,4 +1,4 @@
-/*	$NetBSD: kernhist.h,v 1.13 2016/12/26 23:12:33 pgoyette Exp $	*/
+/*	$NetBSD: kernhist.h,v 1.14 2017/01/01 23:58:47 pgoyette Exp $	*/
 
 /*
  * Copyright (c) 1997 Charles D. Cranor and Washington University.
@@ -63,6 +63,39 @@ struct kern_history {
 	unsigned int n;			/* number of entries */
 	unsigned int f;			/* next free one */
 	struct kern_history_ent *e;	/* the allocated entries */
+	int s;				/* our sysctl number */
+};
+
+/*
+ * structs for exporting history info via sysctl(3)
+ */
+
+/* info for a single kernhist */
+struct sysctl_history_list_entry {
+	uint32_t	shle_nameoffset;
+	uint32_t	shle_numentries;
+	uint32_t	shle_nextfree;
+	uint32_t	shle_filler;
+};
+
+/* info for a single history event */
+struct sysctl_history_event {
+	struct timespec	she_tspec;
+	uint64_t	she_callnumber;
+	uint64_t	she_values[4];
+	uint32_t	she_cpunum;
+	uint32_t	she_fmtoffset;
+	uint32_t	she_funcoffset;
+	uint32_t	she_filler;
+};
+
+/* list of all events for a single history */
+struct sysctl_history {
+	struct sysctl_history_list_entry
+			sh_listentry;
+	struct sysctl_history_event
+			sh_events[];
+	/* char		sh_strings[]; */	/* follows last sh_events */
 };
 
 LIST_HEAD(kern_history_head, kern_history);
@@ -123,6 +156,7 @@ do { \
 	(NAME).f = 0; \
 	(NAME).e = (struct kern_history_ent *) \
 		kmem_zalloc(sizeof(struct kern_history_ent) * (N), KM_SLEEP); \
+	(NAME).s = 0; \
 	LIST_INSERT_HEAD(&kern_histories, &(NAME), list); \
 } while (/*CONSTCOND*/ 0)
 
@@ -133,6 +167,7 @@ do { \
 	.n = sizeof(BUF) / sizeof(struct kern_history_ent), \
 	.f = 0, \
 	.e = (struct kern_history_ent *) (BUF), \
+	.s = 0, \
 	/* BUF will inititalized to zeroes by being in .bss */ \
 }
 
@@ -146,6 +181,7 @@ do { \
 	(NAME).n = sizeof(BUF) / sizeof(struct kern_history_ent); \
 	(NAME).f = 0; \
 	(NAME).e = (struct kern_history_ent *) (BUF); \
+	(NAME).s = 0; \
 	memset((NAME).e, 0, sizeof(struct kern_history_ent) * (NAME).n); \
 	KERNHIST_LINK_STATIC(NAME); \
 } while (/*CONSTCOND*/ 0)
@@ -232,6 +268,8 @@ void	kernhist_dump(struct kern_history *
 void	kernhist_print(void *, void (*)(const char *, ...) __printflike(1, 2));
 #endif /* DDB */
 
+void sysctl_kernhist_init(void);
+
 #endif /* KERNHIST */
 
 #endif /* _KERNEL */

Reply via email to