Bring in a simple event tracing library and POC utility
authorAggelos Economopoulos <aoiko@cc.ece.ntua.gr>
Mon, 8 Feb 2010 17:43:33 +0000 (19:43 +0200)
committerAggelos Economopoulos <aoiko@cc.ece.ntua.gr>
Mon, 8 Feb 2010 17:43:33 +0000 (19:43 +0200)
- Import libevtr, a library for abstracting access to an event stream.
  libevtr uses its own dump format and can synthesize event attributes
  based on known event types.
- Modify ktrdump(8) to be able to dump an event stream to a file
  using libevtr.
- Add evtranalyze(1), a proof of concept utility to display events in
  a line-oriented text format or to generate an svg file displaying
  the events on each processor. This needs quite some work.

16 files changed:
lib/Makefile
lib/libevtr/Makefile [new file with mode: 0644]
lib/libevtr/evtr.c [new file with mode: 0644]
lib/libevtr/evtr.h [new file with mode: 0644]
sys/kern/lwkt_thread.c
usr.bin/Makefile
usr.bin/evtranalyze/Makefile [new file with mode: 0644]
usr.bin/evtranalyze/evtranalyze.1 [new file with mode: 0644]
usr.bin/evtranalyze/evtranalyze.c [new file with mode: 0644]
usr.bin/evtranalyze/svg.c [new file with mode: 0644]
usr.bin/evtranalyze/svg.h [new file with mode: 0644]
usr.bin/evtranalyze/xml.c [new file with mode: 0644]
usr.bin/evtranalyze/xml.h [new file with mode: 0644]
usr.bin/ktrdump/Makefile
usr.bin/ktrdump/ktrdump.8
usr.bin/ktrdump/ktrdump.c

index 2ffc5e0..26b3f07 100644 (file)
@@ -28,7 +28,7 @@ SUBDIR=       csu \
        libpcap libposix1e libsdp libthread_xu libpthread librpcsvc ${_libsm} \
        ${_libsmb} ${_libsmdb} ${_libsmutil} libstand libtelnet libusbhid \
        ${_libvgl} libwrap libxpg4 liby libypclnt libz i18n_module pam_module \
-       libc_rtld libsctp
+       libc_rtld libsctp libevtr
 
 .if exists(${.CURDIR}/compat/${MACHINE_ARCH}/Makefile)
 SUBDIR+= compat/${MACHINE_ARCH}
diff --git a/lib/libevtr/Makefile b/lib/libevtr/Makefile
new file mode 100644 (file)
index 0000000..8dfcdf7
--- /dev/null
@@ -0,0 +1,9 @@
+LIB=evtr
+SHLIB_MAJOR=1
+
+SRCS=evtr.c
+INCS=evtr.h
+
+CFLAGS+= -W -Wall
+
+.include <bsd.lib.mk>
diff --git a/lib/libevtr/evtr.c b/lib/libevtr/evtr.c
new file mode 100644 (file)
index 0000000..2d0f50f
--- /dev/null
@@ -0,0 +1,1674 @@
+/*
+ * Copyright (c) 2009, 2010 Aggelos Economopoulos.  All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ * 3. Neither the name of The DragonFly Project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific, prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/tree.h>
+
+
+#include "evtr.h"
+
+enum {
+       MAX_EVHDR_SIZE = PATH_MAX + 200,
+       /* string namespaces */
+       EVTR_NS_PATH = 0x1,
+       EVTR_NS_FUNC,
+       EVTR_NS_DSTR,
+       EVTR_NS_MAX,
+       NR_BUCKETS = 1023, /* XXX */
+       REC_ALIGN = 8,
+       REC_BOUNDARY = 1 << 14,
+       FILTF_ID = 0x10,
+       EVTRF_WR = 0x1,         /* open for writing */
+};
+
+typedef uint16_t fileid_t;
+typedef uint16_t funcid_t;
+typedef uint16_t fmtid_t;
+
+struct trace_event_header {
+       uint8_t type;
+       uint64_t ts;    /* XXX: this should only be part of probe */
+} __attribute__((packed));
+
+struct probe_event_header {
+       struct trace_event_header eh;
+       /*
+        * For these fields, 0 implies "not available"
+        */
+       fileid_t file;
+       funcid_t caller1;
+       funcid_t caller2;
+       funcid_t func;
+       uint16_t line;
+       fmtid_t fmt;
+       uint16_t datalen;
+       uint8_t cpu;    /* -1 if n/a */
+} __attribute__((packed));
+
+struct string_event_header {
+       struct trace_event_header eh;
+       uint16_t ns;
+       uint32_t id;
+       uint16_t len;
+} __attribute__((packed));
+
+struct fmt_event_header {
+       struct trace_event_header eh;
+       uint16_t id;
+       uint8_t subsys_len;
+       uint8_t fmt_len;
+} __attribute__((packed));
+
+struct hashentry {
+       const char *str;
+       uint16_t id;
+       struct hashentry *next;
+};
+
+struct hashtab {
+       struct hashentry *buckets[NR_BUCKETS];
+       uint16_t id;
+};
+
+struct event_fmt {
+       const char *subsys;
+       const char *fmt;
+};
+
+struct event_filter_unresolved {
+       TAILQ_ENTRY(event_filter_unresolved) link;
+       evtr_filter_t filt;
+};
+
+struct id_map {
+       RB_ENTRY(id_map) rb_node;
+       int id;
+       const void *data;
+};
+
+RB_HEAD(id_tree, id_map);
+struct string_map {
+       struct id_tree root;
+};
+
+struct fmt_map {
+       struct id_tree root;
+};
+
+RB_HEAD(thread_tree, evtr_thread);
+
+struct thread_map {
+       struct thread_tree root;
+};
+
+struct event_callback {
+       void (*cb)(evtr_event_t, void *data);
+       void *data;     /* this field must be malloc()ed */
+};
+
+struct cpu {
+       struct evtr_thread *td; /* currently executing thread */
+};
+
+struct evtr {
+       FILE *f;
+       int err;
+       int flags;
+       char *errmsg;
+       off_t bytes;
+       union {
+               /*
+                * When writing, we keep track of the strings we've
+                * already dumped so we only dump them once.
+                * Paths, function names etc belong to different
+                * namespaces.
+                */
+               struct hashtab *strings[EVTR_NS_MAX - 1];
+               /*
+                * When reading, we build a map from id to string.
+                * Every id must be defined at the point of use.
+                */
+               struct string_map maps[EVTR_NS_MAX - 1];
+       };
+       union {
+               /* same as above, but for subsys+fmt pairs */
+               struct fmt_map fmtmap;
+               struct hashtab *fmts;
+       };
+       /*
+        * Filters that have a format specified and we
+        * need to resolve that to an fmtid
+        */
+       TAILQ_HEAD(, event_filter_unresolved) unresolved_filtq;
+       struct event_callback **cbs;
+       int ncbs;
+       struct thread_map threads;
+       struct cpu *cpus;
+       int ncpus;
+};
+
+struct evtr_query {
+       evtr_t evtr;
+       off_t off;
+       evtr_filter_t filt;
+       int nfilt;
+       int nmatched;
+       int ntried;
+       void *buf;
+       int bufsize;
+};
+
+static int
+evtr_debug = 0;
+
+void
+evtr_set_debug(int lvl)
+{
+       evtr_debug = lvl;
+}
+
+static int id_map_cmp(struct id_map *, struct id_map *);
+RB_PROTOTYPE2(id_tree, id_map, rb_node, id_map_cmp, int);
+RB_GENERATE2(id_tree, id_map, rb_node, id_map_cmp, int, id);
+
+static int thread_cmp(struct evtr_thread *, struct evtr_thread *);
+RB_PROTOTYPE2(thread_tree, evtr_thread, rb_node, thread_cmp, void *);
+RB_GENERATE2(thread_tree, evtr_thread, rb_node, thread_cmp, void *, id);
+
+#define printd(...)                            \
+       do {                                    \
+       if (evtr_debug)                         \
+               fprintf(stderr, __VA_ARGS__);   \
+       } while (0)
+
+static inline
+void
+validate_string(const char *str)
+{
+       if (!evtr_debug)
+               return;
+       for (; *str; ++str)
+               assert(isprint(*str));
+}
+
+static
+void
+id_tree_free(struct id_tree *root)
+{
+       struct id_map *v, *n;
+
+       for (v = RB_MIN(id_tree, root); v; v = n) {
+               n = RB_NEXT(id_tree, root, v);
+               RB_REMOVE(id_tree, root, v);
+       }
+}
+
+static
+int
+evtr_register_callback(evtr_t evtr, void (*fn)(evtr_event_t, void *), void *d)
+{
+       struct event_callback *cb;
+       void *cbs;
+
+       if (!(cb = malloc(sizeof(*cb)))) {
+               evtr->err = ENOMEM;
+               return !0;
+       }
+       cb->cb = fn;
+       cb->data = d;
+       if (!(cbs = realloc(evtr->cbs, (++evtr->ncbs) * sizeof(cb)))) {
+               --evtr->ncbs;
+               free(cb);
+               evtr->err = ENOMEM;
+               return !0;
+       }
+       evtr->cbs = cbs;
+       evtr->cbs[evtr->ncbs - 1] = cb;
+       return 0;
+}
+
+static
+void
+evtr_deregister_callbacks(evtr_t evtr)
+{
+       int i;
+
+       for (i = 0; i < evtr->ncbs; ++i) {
+               free(evtr->cbs[i]);
+       }
+       free(evtr->cbs);
+       evtr->cbs = NULL;
+}
+
+static
+void
+evtr_run_callbacks(evtr_event_t ev, evtr_t evtr)
+{
+       struct event_callback *cb;
+       int i;
+
+       for (i = 0; i < evtr->ncbs; ++i) {
+               cb = evtr->cbs[i];
+               cb->cb(ev, cb->data);
+       }
+}
+
+static
+struct cpu *
+evtr_cpu(evtr_t evtr, int c)
+{
+       if ((c < 0) || (c >= evtr->ncpus))
+               return NULL;
+       return &evtr->cpus[c];
+}
+
+static
+int
+parse_format_data(evtr_event_t ev, const char *fmt, ...) __attribute__((format (scanf, 2, 3)));
+static
+int
+parse_format_data(evtr_event_t ev, const char *fmt, ...)
+{
+       va_list ap;
+       char buf[2048];
+
+       if (strcmp(fmt, ev->fmt))
+               return 0;
+       vsnprintf(buf, sizeof(buf), fmt, ev->fmtdata);
+       printd("string is: %s\n", buf);
+       va_start(ap, fmt);
+       return vsscanf(buf, fmt, ap);
+}
+
+static
+void
+evtr_deregister_filters(evtr_t evtr, evtr_filter_t filt, int nfilt)
+{
+       struct event_filter_unresolved *u, *tmp;
+       int i;
+       TAILQ_FOREACH_MUTABLE(u, &evtr->unresolved_filtq, link, tmp) {
+               for (i = 0; i < nfilt; ++i) {
+                       if (u->filt == &filt[i]) {
+                               TAILQ_REMOVE(&evtr->unresolved_filtq, u, link);
+                       }
+               }
+       }
+}
+
+static
+void
+evtr_resolve_filters(evtr_t evtr, const char *fmt, int id)
+{
+       struct event_filter_unresolved *u, *tmp;
+       TAILQ_FOREACH_MUTABLE(u, &evtr->unresolved_filtq, link, tmp) {
+               if ((u->filt->fmt != NULL) && !strcmp(fmt, u->filt->fmt)) {
+                       u->filt->fmtid = id;
+                       u->filt->flags |= FILTF_ID;
+                       TAILQ_REMOVE(&evtr->unresolved_filtq, u, link);
+               }
+       }
+}
+
+static
+int
+evtr_filter_register(evtr_t evtr, evtr_filter_t filt)
+{
+       struct event_filter_unresolved *res;
+
+       if (!(res = malloc(sizeof(*res)))) {
+               evtr->err = ENOMEM;
+               return !0;
+       }
+       res->filt = filt;
+       TAILQ_INSERT_TAIL(&evtr->unresolved_filtq, res, link);
+       return 0;
+}
+
+void
+evtr_event_data(evtr_event_t ev, char *buf, size_t len)
+{
+       /*
+        * XXX: we implicitly trust the format string.
+        * We shouldn't.
+        */
+       if (ev->fmtdatalen) {
+               vsnprintf(buf, len, ev->fmt, ev->fmtdata);
+       } else {
+               strlcpy(buf, ev->fmt, len);
+       }
+}
+
+
+int
+evtr_error(evtr_t evtr)
+{
+       return evtr->err || (evtr->errmsg == NULL);
+}
+
+const char *
+evtr_errmsg(evtr_t evtr)
+{
+       return evtr->errmsg ? evtr->errmsg : strerror(evtr->err);
+}
+
+static
+int
+id_map_cmp(struct id_map *a, struct id_map *b)
+{
+       return a->id - b->id;
+}
+
+static
+int
+thread_cmp(struct evtr_thread *a, struct evtr_thread *b)
+{
+       return (int)a->id - (int)b->id;
+}
+
+#define DEFINE_MAP_FIND(prefix, type)          \
+       static                                  \
+       type                            \
+       prefix ## _map_find(struct id_tree *tree, int id)\
+       {                                                \
+               struct id_map *sid;                      \
+                                                       \
+               sid = id_tree_RB_LOOKUP(tree, id);      \
+               return sid ? sid->data : NULL;          \
+       }
+
+DEFINE_MAP_FIND(string, const char *)
+DEFINE_MAP_FIND(fmt, const struct event_fmt *)
+
+static
+struct evtr_thread *
+thread_map_find(struct thread_map *map, void *id)
+{
+       return thread_tree_RB_LOOKUP(&map->root, id);
+}
+
+#define DEFINE_MAP_INSERT(prefix, type, _cmp, _dup)    \
+       static                                  \
+       int                                                             \
+       prefix ## _map_insert(struct id_tree *tree, type data, int id) \
+       {                                                               \
+       struct id_map *sid, *osid;                                      \
+                                                                       \
+       sid = malloc(sizeof(*sid));                                     \
+       if (!sid) {                                                     \
+               return ENOMEM;                                          \
+       }                                                               \
+       sid->id = id;                                                   \
+       sid->data = data;                                               \
+       if ((osid = id_tree_RB_INSERT(tree, sid))) {                    \
+               free(sid);                                              \
+               if (_cmp((type)osid->data, data)) {                     \
+                       return EEXIST;                                  \
+               }                                                       \
+               printd("mapping already exists, skipping\n");           \
+               /* we're OK with redefinitions of an id to the same string */ \
+               return 0;                                               \
+       }                                                               \
+       /* only do the strdup if we're inserting a new string */        \
+       sid->data = _dup(data);         /* XXX: oom */                  \
+       return 0;                                                       \
+}
+
+static
+void
+thread_map_insert(struct thread_map *map, struct evtr_thread *td)
+{
+       struct evtr_thread *otd;
+
+       if ((otd = thread_tree_RB_INSERT(&map->root, td))) {
+               /*
+                * Thread addresses might be reused, we're
+                * ok with that.
+                * DANGER, Will Robinson: this means the user
+                * of the API needs to copy event->td if they
+                * want it to remain stable.
+                */
+               free((void *)otd->comm);
+               otd->comm = td->comm;
+               free(td);
+       }
+}
+
+static
+int
+event_fmt_cmp(const struct event_fmt *a, const struct event_fmt *b)
+{
+       int ret = 0;
+
+       if (a->subsys) {
+               if (b->subsys) {
+                       ret = strcmp(a->subsys, b->subsys);
+               } else {
+                       ret = strcmp(a->subsys, "");
+               }
+       } else if (b->subsys) {
+                       ret = strcmp("", b->subsys);
+       }
+       if (ret)
+               return ret;
+       return strcmp(a->fmt, b->fmt);
+}
+
+static
+struct event_fmt *
+event_fmt_dup(const struct event_fmt *o)
+{
+       struct event_fmt *n;
+
+       if (!(n = malloc(sizeof(*n)))) {
+               return n;
+       }
+       memcpy(n, o, sizeof(*n));
+       return n;
+}
+
+DEFINE_MAP_INSERT(string, const char *, strcmp, strdup)
+DEFINE_MAP_INSERT(fmt, const struct event_fmt *, event_fmt_cmp, event_fmt_dup)
+
+static
+int
+hashfunc(const char *str)
+{
+        unsigned long hash = 5381;
+        int c;
+
+        while ((c = *str++))
+            hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
+       return hash  % NR_BUCKETS;
+}
+
+static
+struct hashentry *
+hash_find(struct hashtab *tab, const char *str)
+{
+       struct hashentry *ent;
+
+       for(ent = tab->buckets[hashfunc(str)]; ent && strcmp(ent->str, str);
+           ent = ent->next);
+
+       return ent;
+}
+
+static
+struct hashentry *
+hash_insert(struct hashtab *tab, const char *str)
+{
+       struct hashentry *ent;
+       int hsh;
+
+       if (!(ent = malloc(sizeof(*ent)))) {
+               fprintf(stderr, "out of memory\n");
+               return NULL;
+       }
+       hsh = hashfunc(str);
+       ent->next = tab->buckets[hsh];
+       ent->str = strdup(str);
+       ent->id = ++tab->id;
+       if (tab->id == 0) {
+               fprintf(stderr, "too many strings\n");
+               free(ent);
+               return NULL;
+       }
+       tab->buckets[hsh] = ent;
+       return ent;
+}
+
+static
+void
+thread_creation_callback(evtr_event_t ev, void *d)
+{
+       evtr_t evtr = (evtr_t)d;
+       struct evtr_thread *td;
+       void *ktd;
+       char buf[20];
+
+       //printd("thread_creation_callback\n");
+       if (parse_format_data(ev, "new_td %p %s", &ktd, buf) != 2) {
+               return;
+       }
+       buf[19] = '\0';
+
+       if (!(td = malloc(sizeof(*td)))) {
+               evtr->err = ENOMEM;
+               return;
+       }
+       td->id = ktd;
+       td->userdata = NULL;
+       if (!(td->comm = strdup(buf))) {
+               free(td);
+               evtr->err = ENOMEM;
+               return;
+       }
+       printd("inserting new thread %p: %s\n", td->id, td->comm);
+       thread_map_insert(&evtr->threads, td);
+}
+
+static
+void
+thread_switch_callback(evtr_event_t ev, void *d)
+{
+       evtr_t evtr = (evtr_t)d;
+       struct evtr_thread *tdp, *tdn;
+       void *ktdp, *ktdn;
+       struct cpu *cpu;
+       static struct evtr_event tdcr;
+       static char *fmt = "new_td %p %s";
+       char tidstr[40];
+       char fmtdata[sizeof(void *) + sizeof(char *)];
+
+       //printd("thread_switch_callback\n");
+       cpu = evtr_cpu(evtr, ev->cpu);
+       if (!cpu) {
+               printd("invalid cpu %d\n", ev->cpu);
+               return;
+       }
+       if (parse_format_data(ev, "sw  %p > %p", &ktdp, &ktdn) != 2) {
+               return;
+       }
+       tdp = thread_map_find(&evtr->threads, ktdp);
+       if (!tdp) {
+               printd("switching from unknown thread %p\n", ktdp);
+       }
+       tdn = thread_map_find(&evtr->threads, ktdn);
+       if (!tdn) {
+               /*
+                * Fake a thread creation event for threads we
+                * haven't seen before.
+                */
+               tdcr.type = EVTR_TYPE_PROBE;
+               tdcr.ts = ev->ts;
+               tdcr.file = NULL;
+               tdcr.func = NULL;
+               tdcr.line = 0;
+               tdcr.fmt = fmt;
+               tdcr.fmtdata = &fmtdata;
+               tdcr.fmtdatalen = sizeof(fmtdata);
+               tdcr.cpu = ev->cpu;
+               tdcr.td = NULL;
+               snprintf(tidstr, sizeof(tidstr), "%p", ktdn);
+               ((void **)fmtdata)[0] = ktdn;
+               ((char **)fmtdata)[1] = &tidstr[0];
+               thread_creation_callback(&tdcr, evtr);
+
+               tdn = thread_map_find(&evtr->threads, ktdn);
+               assert(tdn != NULL);
+               printd("switching to unknown thread %p\n", ktdn);
+               cpu->td = tdn;
+               return;
+       }
+       printd("cpu %d: switching to thread %p\n", ev->cpu, ktdn);
+       cpu->td = tdn;
+}
+
+static
+void
+assert_foff_in_sync(evtr_t evtr)
+{
+       off_t off;
+
+       /*
+        * We keep our own offset because we
+        * might want to support mmap()
+        */
+       off = ftello(evtr->f);
+       if (evtr->bytes != off) {
+               fprintf(stderr, "bytes %jd, off %jd\n", evtr->bytes, off);
+               abort();
+       }
+}
+
+static
+int
+evtr_write(evtr_t evtr, const void *buf, size_t bytes)
+{
+       assert_foff_in_sync(evtr);
+       if (fwrite(buf, bytes, 1, evtr->f) != 1) {
+               evtr->err = errno;
+               evtr->errmsg = strerror(errno);
+               return !0;
+       }
+       evtr->bytes += bytes;
+       assert_foff_in_sync(evtr);
+       return 0;
+}
+
+/*
+ * Called after dumping a record to make sure the next
+ * record is REC_ALIGN aligned. This does not make much sense,
+ * as we shouldn't be using packed structs anyway.
+ */
+static
+int
+evtr_dump_pad(evtr_t evtr)
+{
+       size_t pad;
+       static char buf[REC_ALIGN];
+
+       pad = REC_ALIGN - (evtr->bytes % REC_ALIGN);
+       if (pad > 0) {
+               return evtr_write(evtr, buf, pad);
+       }
+       return 0;
+}
+
+/*
+ * We make sure that there is a new record every REC_BOUNDARY
+ * bytes, this costs next to nothing in space and allows for
+ * fast seeking.
+ */
+static
+int
+evtr_dump_avoid_boundary(evtr_t evtr, size_t bytes)
+{
+       unsigned pad, i;
+       static char buf[256];
+
+       pad = REC_BOUNDARY - (evtr->bytes % REC_BOUNDARY);
+       /* if adding @bytes would cause us to cross a boundary... */
+       if (bytes > pad) {
+               /* then pad to the boundary */
+               for (i = 0; i < (pad / sizeof(buf)); ++i) {
+                       if (evtr_write(evtr, buf, sizeof(buf))) {
+                               return !0;
+                       }
+               }
+               i = pad % sizeof(buf);
+               if (i) {
+                       if (evtr_write(evtr, buf, i)) {
+                               return !0;
+                       }
+               }
+       }
+       return 0;
+}
+
+static
+int
+evtr_dump_fmt(evtr_t evtr, uint64_t ts, const evtr_event_t ev)
+{
+       struct fmt_event_header fmt;
+       struct hashentry *ent;
+       char *subsys = "", buf[1024];
+
+       if (strlcpy(buf, subsys, sizeof(buf)) >= sizeof(buf)) {
+               evtr->errmsg = "name of subsystem is too large";
+               evtr->err = ERANGE;
+               return 0;
+       }
+       if (strlcat(buf, ev->fmt, sizeof(buf)) >= sizeof(buf)) {
+               evtr->errmsg = "fmt + name of subsystem is too large";
+               evtr->err = ERANGE;
+               return 0;
+       }
+
+       if ((ent = hash_find(evtr->fmts, buf))) {
+               return ent->id;
+       }
+       if (!(ent = hash_insert(evtr->fmts, buf))) {
+               evtr->err = evtr->fmts->id ? ENOMEM : ERANGE;
+               return 0;
+       }
+
+       fmt.eh.type = EVTR_TYPE_FMT;
+       fmt.eh.ts = ts;
+       fmt.subsys_len = strlen(subsys);
+       fmt.fmt_len = strlen(ev->fmt);
+       fmt.id = ent->id;
+       if (evtr_dump_avoid_boundary(evtr, sizeof(fmt) + fmt.subsys_len +
+                                    fmt.fmt_len))
+               return 0;
+       if (evtr_write(evtr, &fmt, sizeof(fmt)))
+               return 0;
+       if (evtr_write(evtr, subsys, fmt.subsys_len))
+               return 0;
+       if (evtr_write(evtr, ev->fmt, fmt.fmt_len))
+               return 0;
+       if (evtr_dump_pad(evtr))
+               return 0;
+       return fmt.id;
+}
+
+/*
+ * Replace string pointers or string ids in fmtdata
+ */ 
+static
+int
+mangle_string_ptrs(const char *fmt, uint8_t *fmtdata,
+                  const char *(*replace)(void *, const char *), void *ctx)
+{
+       const char *f, *p;
+       size_t skipsize, intsz;
+       int ret = 0;
+
+       for (f = fmt; f[0] != '\0'; ++f) {
+               if (f[0] != '%')
+                       continue;
+               ++f;
+               skipsize = 0;
+               for (p = f; p[0]; ++p) {
+                       int again = 0;
+                       /*
+                        * Eat flags. Notice this will accept duplicate
+                        * flags.
+                        */
+                       switch (p[0]) {
+                       case '#':
+                       case '0':
+                       case '-':
+                       case ' ':
+                       case '+':
+                       case '\'':
+                               again = !0;
+                               break;
+                       }
+                       if (!again)
+                               break;
+               }
+               /* Eat minimum field width, if any */
+               for (; isdigit(p[0]); ++p)
+                       ;
+               if (p[0] == '.')
+                       ++p;
+               /* Eat precision, if any */
+               for (; isdigit(p[0]); ++p)
+                       ;
+               intsz = 0;
+               switch (p[0]) {
+               case 'l':
+                       if (p[1] == 'l') {
+                               ++p;
+                               intsz = sizeof(long long);
+                       } else {
+                               intsz = sizeof(long);
+                       }
+                       break;
+               case 'j':
+                       intsz = sizeof(intmax_t);
+                       break;
+               case 't':
+                       intsz = sizeof(ptrdiff_t);
+                       break;
+               case 'z':
+                       intsz = sizeof(size_t);
+                       break;
+               default:
+                       break;
+               }
+               if (intsz != 0)
+                       ++p;
+               else
+                       intsz = sizeof(int);
+
+               switch (p[0]) {
+               case 'd':
+               case 'i':
+               case 'o':
+               case 'u':
+               case 'x':
+               case 'X':
+               case 'c':
+                       skipsize = intsz;
+                       break;
+               case 'p':
+                       skipsize = sizeof(void *);
+                       break;
+               case 'f':
+                       if (p[-1] == 'l')
+                               skipsize = sizeof(double);
+                       else
+                               skipsize = sizeof(float);
+                       break;
+               case 's':
+                       ((const char **)fmtdata)[0] =
+                               replace(ctx, ((char **)fmtdata)[0]);
+                       skipsize = sizeof(char *);
+                       ++ret;
+                       break;
+               default:
+                       fprintf(stderr, "Unknown conversion specifier %c "
+                               "in fmt starting with %s", p[0], f - 1);
+                       return -1;
+               }
+               fmtdata += skipsize;
+       }
+       return ret;
+}
+
+/* XXX: do we really want the timestamp? */
+static
+int
+evtr_dump_string(evtr_t evtr, uint64_t ts, const char *str, int ns)
+{
+       struct string_event_header s;
+       struct hashentry *ent;
+
+       assert((0 <= ns) && (ns < EVTR_NS_MAX));
+       if ((ent = hash_find(evtr->strings[ns], str))) {
+               return ent->id;
+       }
+       if (!(ent = hash_insert(evtr->strings[ns], str))) {
+               evtr->err = evtr->strings[ns]->id ? ENOMEM : ERANGE;
+               return 0;
+       }
+
+       printd("hash_insert %s ns %d id %d\n", str, ns, ent->id);
+       s.eh.type = EVTR_TYPE_STR;
+       s.eh.ts = ts;
+       s.ns = ns;
+       s.id = ent->id;
+       s.len = strnlen(str, PATH_MAX);
+
+       if (evtr_dump_avoid_boundary(evtr, sizeof(s) + s.len))
+               return 0;
+       if (evtr_write(evtr, &s, sizeof(s)))
+               return 0;
+       if (evtr_write(evtr, str, s.len))
+               return 0;
+       if (evtr_dump_pad(evtr))
+               return 0;
+       return s.id;
+}
+
+struct replace_ctx {
+       evtr_t evtr;
+       uint64_t ts;
+};
+
+static
+const char *
+replace_strptr(void *_ctx, const char *s)
+{
+       struct replace_ctx *ctx = _ctx;
+       return (const char *)evtr_dump_string(ctx->evtr, ctx->ts, s, EVTR_NS_DSTR);
+}
+
+static
+const char *
+replace_strid(void *_ctx, const char *s)
+{
+       struct replace_ctx *ctx = _ctx;
+       const char *ret;
+
+       ret = string_map_find(&ctx->evtr->maps[EVTR_NS_DSTR - 1].root,
+                             (uint32_t)s);
+       if (!ret) {
+               fprintf(stderr, "Unknown id for data string\n");
+               ctx->evtr->errmsg = "unknown id for data string";
+               ctx->evtr->err = !0;
+       }
+       validate_string(ret);
+       printd("replacing strid %d (ns %d) with string '%s' (or int %#x)\n", (int)s,
+              EVTR_NS_DSTR, ret ? ret : "NULL", (int)ret);
+       return ret;
+}
+
+static
+int
+evtr_dump_probe(evtr_t evtr, evtr_event_t ev)
+{
+       struct probe_event_header kev;
+       char buf[1024];
+
+       memset(&kev, '\0', sizeof(kev));
+       kev.eh.type = ev->type;
+       kev.eh.ts = ev->ts;
+       kev.line = ev->line;
+       kev.cpu = ev->cpu;
+       if (ev->file) {
+               kev.file = evtr_dump_string(evtr, kev.eh.ts, ev->file,
+                                           EVTR_NS_PATH);
+       }
+       if (ev->func) {
+               kev.func = evtr_dump_string(evtr, kev.eh.ts, ev->func,
+                                           EVTR_NS_FUNC);
+       }
+       if (ev->fmt) {
+               kev.fmt = evtr_dump_fmt(evtr, kev.eh.ts, ev);
+       }
+       if (ev->fmtdata) {
+               struct replace_ctx replctx = {
+                       .evtr = evtr,
+                       .ts = ev->ts,
+               };
+               assert(ev->fmtdatalen <= sizeof(buf));
+               kev.datalen = ev->fmtdatalen;
+               /*
+                * Replace all string pointers with string ids before dumping
+                * the data.
+                */
+               memcpy(buf, ev->fmtdata, ev->fmtdatalen);
+               if (mangle_string_ptrs(ev->fmt, buf,
+                                      replace_strptr, &replctx) < 0)
+                       return !0;
+               if (evtr->err)
+                       return evtr->err;
+       }
+       if (evtr_dump_avoid_boundary(evtr, sizeof(kev) + ev->fmtdatalen))
+               return !0;
+       if (evtr_write(evtr, &kev, sizeof(kev)))
+               return !0;
+       if (evtr_write(evtr, buf, ev->fmtdatalen))
+               return !0;
+       if (evtr_dump_pad(evtr))
+               return !0;
+       return 0;
+}
+
+static
+int
+evtr_dump_cpuinfo(evtr_t evtr, evtr_event_t ev)
+{
+       uint8_t type = EVTR_TYPE_CPUINFO;
+       uint16_t ncpus = ev->ncpus;
+
+       if (ncpus <= 0) {
+               evtr->errmsg = "invalid number of cpus";
+               return !0;
+       }
+       if (evtr_dump_avoid_boundary(evtr, sizeof(type) + sizeof(ncpus)))
+               return !0;
+       if (evtr_write(evtr, &type, sizeof(type))) {
+               return !0;
+       }
+       if (evtr_write(evtr, &ncpus, sizeof(ncpus))) {
+               return !0;
+       }
+       if (evtr_dump_pad(evtr))
+               return !0;
+       return 0;
+}
+
+int
+evtr_rewind(evtr_t evtr)
+{
+       assert((evtr->flags & EVTRF_WR) == 0);
+       evtr->bytes = 0;
+       if (fseek(evtr->f, 0, SEEK_SET)) {
+               evtr->err = errno;
+               return !0;
+       }
+       return 0;
+}
+
+int
+evtr_dump_event(evtr_t evtr, evtr_event_t ev)
+{
+       switch (ev->type) {
+       case EVTR_TYPE_PROBE:
+               return evtr_dump_probe(evtr, ev);
+       case EVTR_TYPE_CPUINFO:
+               return evtr_dump_cpuinfo(evtr, ev);
+       }
+       evtr->errmsg = "unknown event type";
+       return !0;
+}
+
+static
+evtr_t
+evtr_alloc(FILE *f)
+{
+       evtr_t evtr;
+       if (!(evtr = malloc(sizeof(*evtr)))) {
+               return NULL;
+       }
+
+       evtr->f = f;
+       evtr->err = 0;
+       evtr->errmsg = NULL;
+       evtr->bytes = 0;
+       TAILQ_INIT(&evtr->unresolved_filtq);
+       return evtr;
+}
+
+evtr_t
+evtr_open_read(FILE *f)
+{
+       evtr_t evtr;
+       struct evtr_event ev;
+       int i;
+
+       if (!(evtr = evtr_alloc(f))) {
+               return NULL;
+       }
+       evtr->flags = 0;
+       for (i = 0; i < (EVTR_NS_MAX - 1); ++i) {
+               RB_INIT(&evtr->maps[i].root);
+       }
+       RB_INIT(&evtr->fmtmap.root);
+       TAILQ_INIT(&evtr->unresolved_filtq);
+       evtr->cbs = 0;
+       evtr->ncbs = 0;
+       RB_INIT(&evtr->threads.root);
+       evtr->cpus = NULL;
+       evtr->ncpus = 0;
+       if (evtr_register_callback(evtr, &thread_creation_callback, evtr)) {
+               goto free_evtr;
+       }
+       if (evtr_register_callback(evtr, &thread_switch_callback, evtr)) {
+               goto free_cbs;
+       }
+       /*
+        * Load the first event so we can pick up any
+        * cpuinfo entries.
+        */
+       if (evtr_next_event(evtr, &ev)) {
+               goto free_cbs;
+       }
+       if (evtr_rewind(evtr))
+               goto free_cbs;
+       return evtr;
+free_cbs:
+       evtr_deregister_callbacks(evtr);
+free_evtr:
+       free(evtr);
+       return NULL;
+}
+
+evtr_t
+evtr_open_write(FILE *f)
+{
+       evtr_t evtr;
+       int i, j;
+
+       if (!(evtr = evtr_alloc(f))) {
+               return NULL;
+       }
+
+       evtr->flags = EVTRF_WR;
+       if (!(evtr->fmts = calloc(sizeof(struct hashtab), 1)))
+               goto free_evtr;
+
+       for (i = 0; i < EVTR_NS_MAX; ++i) {
+               evtr->strings[i] = calloc(sizeof(struct hashtab), 1);
+               if (!evtr->strings[i]) {
+                       for (j = 0; j < i; ++j) {
+                               free(evtr->strings[j]);
+                       }
+                       goto free_fmts;
+               }
+       }
+
+       return evtr;
+free_fmts:
+       free(evtr->fmts);
+free_evtr:
+       free(evtr);
+       return NULL;
+}
+
+static
+void
+hashtab_destroy(struct hashtab *h)
+{
+       struct hashentry *ent, *next;
+       int i;
+       for (i = 0; i < NR_BUCKETS; ++i) {
+               for (ent = h->buckets[i]; ent; ent = next) {
+                       next = ent->next;
+                       free(ent);
+               }
+       }
+       free(h);
+}
+
+void
+evtr_close(evtr_t evtr)
+{
+       int i;
+
+       if (evtr->flags & EVTRF_WR) {
+               hashtab_destroy(evtr->fmts);
+               for (i = 0; i < EVTR_NS_MAX; ++i)
+                       hashtab_destroy(evtr->strings[i]);
+       } else {
+               id_tree_free(&evtr->fmtmap.root);
+               for (i = 0; i < EVTR_NS_MAX - 1; ++i) {
+                       id_tree_free(&evtr->maps[i].root);
+               }
+       }
+       free(evtr);
+}
+
+static
+int
+evtr_read(evtr_t evtr, void *buf, size_t size)
+{
+       assert(size > 0);
+       assert_foff_in_sync(evtr);
+//     printd("evtr_read at %#jx, %zd bytes\n", evtr->bytes, size);
+       if (fread(buf, size, 1, evtr->f) != 1) {
+               if (feof(evtr->f)) {
+                       evtr->errmsg = "incomplete record";
+               } else {
+                       evtr->errmsg = strerror(errno);
+               }
+               return !0;
+       }
+       evtr->bytes += size;
+       assert_foff_in_sync(evtr);
+       return 0;
+}
+
+static
+int
+evtr_load_fmt(evtr_t evtr, char *buf)
+{
+       struct fmt_event_header *evh = (struct fmt_event_header *)buf;
+       struct event_fmt *fmt;
+       char *subsys = NULL, *fmtstr;
+
+       if (!(fmt = malloc(sizeof(*fmt)))) {
+               evtr->err = errno;
+               return !0;
+       }
+       if (evtr_read(evtr, buf + sizeof(struct trace_event_header),
+                     sizeof(*evh) - sizeof(evh->eh))) {
+               goto free_fmt;
+       }
+       assert(!evh->subsys_len);
+       if (evh->subsys_len) {
+               if (!(subsys = malloc(evh->subsys_len))) {
+                       evtr->err = errno;
+                       goto free_fmt;
+               }
+               if (evtr_read(evtr, subsys, evh->subsys_len)) {
+                       goto free_subsys;
+               }
+               fmt->subsys = subsys;
+       } else {
+               fmt->subsys = "";
+       }
+       if (!(fmtstr = malloc(evh->fmt_len + 1))) {
+               evtr->err = errno;
+               goto free_subsys;
+       }
+       if (evtr_read(evtr, fmtstr, evh->fmt_len)) {
+               goto free_fmtstr;
+       }
+       fmtstr[evh->fmt_len] = '\0';
+       fmt->fmt = fmtstr;
+
+       printd("fmt_map_insert (%d, %s)\n", evh->id, fmt->fmt);
+       evtr->err = fmt_map_insert(&evtr->fmtmap.root, fmt, evh->id);
+       switch (evtr->err) {
+       case ENOMEM:
+               evtr->errmsg = "out of memory";
+               break;
+       case EEXIST:
+               evtr->errmsg = "redefinition of an id to a "
+                       "different format (corrupt input)";
+               break;
+       default:
+               evtr_resolve_filters(evtr, fmt->fmt, evh->id);
+       }
+       return 0;
+
+free_fmtstr:
+       free(fmtstr);
+free_subsys:
+       if (subsys)
+               free(subsys);
+free_fmt:
+       free(fmt);
+       return !0;
+}
+
+static
+int
+evtr_load_string(evtr_t evtr, char *buf)
+{
+       char sbuf[PATH_MAX + 1];
+       struct string_event_header *evh = (struct string_event_header *)buf;
+
+       if (evtr_read(evtr, buf + sizeof(struct trace_event_header),
+                     sizeof(*evh) - sizeof(evh->eh))) {
+               return !0;
+       }
+       if (evh->len > PATH_MAX) {
+               evtr->errmsg = "string too large (corrupt input)";
+               return !0;
+       } else if (evh->len < 0) {
+               evtr->errmsg = "negative string size (corrupt input)";
+               return !0;
+       }
+       if (evh->len && evtr_read(evtr, sbuf, evh->len)) {
+               return !0;
+       }
+       sbuf[evh->len] = 0;
+       if (evh->ns >= EVTR_NS_MAX) {
+               evtr->errmsg = "invalid namespace (corrupt input)";
+               return !0;
+       }
+       validate_string(sbuf);
+       printd("evtr_load_string:ns %d id %d : \"%s\"\n", evh->ns, evh->id,
+              sbuf);
+       evtr->err = string_map_insert(&evtr->maps[evh->ns - 1].root, sbuf, evh->id);
+       switch (evtr->err) {
+       case ENOMEM:
+               evtr->errmsg = "out of memory";
+               break;
+       case EEXIST:
+               evtr->errmsg = "redefinition of an id to a "
+                       "different string (corrupt input)";
+               break;
+       default:
+               ;
+       }
+       return 0;
+}
+
+static
+int
+evtr_filter_match(evtr_filter_t f, struct probe_event_header *pev)
+{
+       if ((f->cpu != -1) && (f->cpu != pev->cpu))
+               return 0;
+       if (!f->fmtid)
+               return !0;
+       /*
+        * If we don't have an id for the required format
+        * string, the format string won't match anyway
+        * (we require that id <-> fmt mappings appear
+        * before the first appearance of the fmt string),
+        * so don't bother comparing.
+        */
+       if (!(f->flags & FILTF_ID))
+               return 0;
+       if(pev->fmt == f->fmtid)
+               return !0;
+       return 0;
+}
+
+static
+int
+evtr_match_filters(struct evtr_query *q, struct probe_event_header *pev)
+{
+       int i;
+
+       /* no filters means we're interested in all events */
+       if (!q->nfilt)
+               return !0;
+       ++q->ntried;
+       for (i = 0; i < q->nfilt; ++i) {
+               if (evtr_filter_match(&q->filt[i], pev)) {
+                       ++q->nmatched;
+                       return !0;
+               }
+       }
+       return 0;
+}
+
+static
+int
+evtr_skip(evtr_t evtr, off_t bytes)
+{
+       if (fseek(evtr->f, bytes, SEEK_CUR)) {
+               evtr->err = errno;
+               evtr->errmsg = strerror(errno);
+               return !0;
+       }
+       evtr->bytes += bytes;
+       return 0;
+}
+
+/*
+ * Make sure q->buf is at least len bytes
+ */
+static
+int
+evtr_query_reserve_buf(struct evtr_query *q, int len)
+{
+       void *tmp;
+
+       if (q->bufsize >= len)
+               return 0;
+       if (!(tmp = realloc(q->buf, len)))
+               return !0;
+       q->buf = tmp;
+       q->bufsize = len;
+       return 0;
+}
+
+static
+int
+evtr_load_probe(evtr_t evtr, evtr_event_t ev, char *buf, struct evtr_query *q)
+{
+       struct probe_event_header *evh = (struct probe_event_header *)buf;
+       struct cpu *cpu;
+
+       if (evtr_read(evtr, buf + sizeof(struct trace_event_header),
+                     sizeof(*evh) - sizeof(evh->eh)))
+               return !0;
+       memset(ev, '\0', sizeof(*ev));
+       ev->ts = evh->eh.ts;
+       ev->type = EVTR_TYPE_PROBE;
+       ev->line = evh->line;
+       ev->cpu = evh->cpu;
+       if ((cpu = evtr_cpu(evtr, evh->cpu))) {
+               ev->td = cpu->td;
+       } else {
+               ev->td = NULL;
+       }
+       if (evh->file) {
+               ev->file = string_map_find(
+                       &evtr->maps[EVTR_NS_PATH - 1].root,
+                       evh->file);
+               if (!ev->file) {
+                       evtr->errmsg = "unknown id for file path";
+                       evtr->err = !0;
+                       ev->file = "<unknown>";
+               } else {
+                       validate_string(ev->file);
+               }
+       } else {
+               ev->file = "<unknown>";
+       }
+       if (evh->fmt) {
+               const struct event_fmt *fmt;
+               if (!(fmt = fmt_map_find(&evtr->fmtmap.root, evh->fmt))) {
+                       evtr->errmsg = "unknown id for event fmt";
+                       evtr->err = !0;
+                       ev->fmt = NULL;
+               } else {
+                       ev->fmt = fmt->fmt;
+                       validate_string(fmt->fmt);
+               }
+       }
+       if (evh->datalen) {
+               if (evtr_query_reserve_buf(q, evh->datalen + 1)) {
+                       evtr->err = ENOMEM;
+               } else if (!evtr_read(evtr, q->buf, evh->datalen)) {
+                       struct replace_ctx replctx = {
+                               .evtr = evtr,
+                               .ts = ev->ts,
+                       };
+                       assert(ev->fmt);
+
+                       ev->fmtdata = q->buf;
+                       /*
+                        * If the format specifies any string pointers, there
+                        * is a string id stored in the fmtdata. Look it up
+                        * and replace it with a string pointer before
+                        * returning it to the user.
+                        */
+                       if (mangle_string_ptrs(ev->fmt, __DECONST(uint8_t *,
+                                                                 ev->fmtdata),
+                                              replace_strid, &replctx) < 0)
+                               return evtr->err;
+                       if (evtr->err)
+                               return evtr->err;
+                       ((char *)ev->fmtdata)[evh->datalen] = '\0';
+                       ev->fmtdatalen = evh->datalen;
+               }
+       }
+       evtr_run_callbacks(ev, evtr);
+       /* we can't filter before running the callbacks */ 
+       if (!evtr_match_filters(q, evh)) {
+               return -1;      /* no match */
+       }
+
+       return evtr->err;
+}
+
+static
+int
+evtr_skip_to_record(evtr_t evtr)
+{
+       int skip;
+       
+       skip = REC_ALIGN - (evtr->bytes % REC_ALIGN);
+       if (skip > 0) {
+               if (fseek(evtr->f, skip, SEEK_CUR)) {
+                       evtr->err = errno;
+                       evtr->errmsg = strerror(errno);
+                       return !0;
+               }
+               evtr->bytes += skip;
+       }
+       return 0;
+}
+
+static
+int
+evtr_load_cpuinfo(evtr_t evtr)
+{
+       uint16_t ncpus;
+       int i;
+
+       if (evtr_read(evtr, &ncpus, sizeof(ncpus))) {
+               return !0;
+       }
+       if (evtr->cpus)
+               return 0;
+       evtr->cpus = malloc(ncpus * sizeof(struct cpu));
+       if (!evtr->cpus) {
+               evtr->err = ENOMEM;
+               return !0;
+       }
+       evtr->ncpus = ncpus;
+       for (i = 0; i < ncpus; ++i) {
+               evtr->cpus[i].td = NULL;
+       }
+       return 0;
+}
+
+static
+int
+_evtr_next_event(evtr_t evtr, evtr_event_t ev, struct evtr_query *q)
+{
+       char buf[MAX_EVHDR_SIZE];
+       int ret, err, ntried, nmatched;
+       struct trace_event_header *evhdr = (struct trace_event_header *)buf;
+
+       for (ret = 0; !ret;) {
+               /*
+                * skip pad records -- this will only happen if there's a
+                * variable sized record close to the boundary
+                */
+               if (evtr_read(evtr, &evhdr->type, 1))
+                       return feof(evtr->f) ? -1 : !0;
+               if (evhdr->type == EVTR_TYPE_PAD) {
+                       evtr_skip_to_record(evtr);
+                       continue;
+               }
+               if (evhdr->type == EVTR_TYPE_CPUINFO) {
+                       evtr_load_cpuinfo(evtr);
+                       continue;
+               }
+               if (evtr_read(evtr, buf + 1, sizeof(*evhdr) - 1))
+                       return feof(evtr->f) ? -1 : !0;
+               switch (evhdr->type) {
+               case EVTR_TYPE_PROBE:
+                       ntried = q->ntried;
+                       nmatched = q->nmatched;
+                       if ((err = evtr_load_probe(evtr, ev, buf, q))) {
+                               if (err == -1) {
+                                       /* no match */
+                                       ret = 0;
+                               } else {
+                                       return !0;
+                               }
+                       } else {
+                               ret = !0;
+                       }
+                       break;
+               case EVTR_TYPE_STR:
+                       if (evtr_load_string(evtr, buf)) {
+                               return !0;
+                       }
+                       break;
+               case EVTR_TYPE_FMT:
+                       if (evtr_load_fmt(evtr, buf)) {
+                               return !0;
+                       }
+                       break;
+               default:
+                       evtr->err = !0;
+                       evtr->errmsg = "unknown event type (corrupt input?)";
+                       return !0;
+               }
+               evtr_skip_to_record(evtr);
+               if (ret) {
+                       q->off = evtr->bytes;
+                       return 0;
+               }
+       }
+       /* can't get here */
+       return !0;
+}
+
+int
+evtr_next_event(evtr_t evtr, evtr_event_t ev)
+{
+       struct evtr_query *q;
+       int ret;
+
+       if (!(q = evtr_query_init(evtr, NULL, 0))) {
+               evtr->err = ENOMEM;
+               return !0;
+       }
+       ret = _evtr_next_event(evtr, ev, q);
+       evtr_query_destroy(q);
+       return ret;
+}
+
+int
+evtr_last_event(evtr_t evtr, evtr_event_t ev)
+{
+       struct stat st;
+       int fd;
+       off_t last_boundary;
+
+       fd = fileno(evtr->f);
+       if (fstat(fd, &st))
+               return !0;
+       /*
+        * This skips pseudo records, so we can't provide
+        * an event with all fields filled in this way.
+        * It's doable, just needs some care. TBD.
+        */
+       if (0 && (st.st_mode & S_IFREG)) {
+               /*
+                * Skip to last boundary, that's the closest to the EOF
+                * location that we are sure contains a header so we can
+                * pick up the stream.
+                */
+               last_boundary = (st.st_size / REC_BOUNDARY) * REC_BOUNDARY;
+               /* XXX: ->bytes should be in query */
+               assert(evtr->bytes == 0);
+               evtr_skip(evtr, last_boundary);
+       }
+
+
+       /*
+        * If we can't seek, we need to go through the whole file.
+        * Since you can't seek back, this is pretty useless unless
+        * you really are interested only in the last event.
+        */
+       while (!evtr_next_event(evtr, ev))
+               ;
+       if (evtr_error(evtr))
+               return !0;
+       evtr_rewind(evtr);
+       return 0;
+}
+
+struct evtr_query *
+evtr_query_init(evtr_t evtr, evtr_filter_t filt, int nfilt)
+{
+       struct evtr_query *q;
+       int i;
+
+       if (!(q = malloc(sizeof(*q)))) {
+               return q;
+       }
+       q->bufsize = 2;
+       if (!(q->buf = malloc(q->bufsize))) {
+               goto free_q;
+       }
+       q->evtr = evtr;
+       q->off = 0;
+       q->filt = filt;
+       q->nfilt = nfilt;
+       q->nmatched = 0;
+       for (i = 0; i < nfilt; ++i) {
+               filt[i].flags = 0;
+               if (filt[i].fmt == NULL)
+                       continue;
+               if (evtr_filter_register(evtr, &filt[i])) {
+                       evtr_deregister_filters(evtr, filt, i);
+                       goto free_buf;
+               }
+       }
+
+       return q;
+free_buf:
+       free(q->buf);
+free_q:
+       free(q);
+       return NULL;
+}
+
+void
+evtr_query_destroy(struct evtr_query *q)
+{
+       evtr_deregister_filters(q->evtr, q->filt, q->nfilt);
+       free(q->buf);
+       free(q);
+}
+
+int
+evtr_query_next(struct evtr_query *q, evtr_event_t ev)
+{
+       /* we may support that in the future */
+       if (q->off != q->evtr->bytes)
+               return !0;
+       return _evtr_next_event(q->evtr, ev, q);
+}
+
+int
+evtr_ncpus(evtr_t evtr)
+{
+       return evtr->ncpus;
+}
diff --git a/lib/libevtr/evtr.h b/lib/libevtr/evtr.h
new file mode 100644 (file)
index 0000000..cefa9c2
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2009, 2010 Aggelos Economopoulos.  All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ * 3. Neither the name of The DragonFly Project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific, prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef EVTR_H
+#define EVTR_H
+
+#include <stdint.h>
+#include <stdio.h>
+/* XXX: remove */
+#include <sys/tree.h>
+
+enum {
+       EVTR_TYPE_PAD = 0x0,
+       EVTR_TYPE_PROBE = 0x1,
+       EVTR_TYPE_STR = 0x2,
+       EVTR_TYPE_FMT = 0x3,
+       EVTR_TYPE_CPUINFO = 0x4,
+};
+
+struct evtr_thread {
+       RB_ENTRY(evtr_thread) rb_node;
+       void *id;
+       const char *comm;
+       /* available for the user of the library, NULL if not set */
+       void *userdata;
+};
+
+/*
+ * This structure is used for interchange of data with
+ * the user of the library
+ */
+typedef struct evtr_event {
+       uint8_t type;
+       union {
+               /* timestamp. Must be nondecreasing */
+               uint64_t ts;
+               uint16_t ncpus; /* EVTR_TYPE_CPUINFO */
+       };
+       /*
+        * Pointer to filename. NULL if n/a.
+        * For an event returned by the library,
+        * it is a pointer to storage allocated
+        * by the library that will be around
+        * until the call to evtr_close.
+        */
+       const char *file;
+       /* Same as above */ 
+       const char *func;
+       /* line number. 0 if n/a */
+       uint16_t line;
+       /*
+        * Format string, also used to identify
+        * the event. Ownership rules are the same
+        * as for file.
+        */
+       const char *fmt;
+       /*
+        * Data corresponding to the format string.
+        * For an event returned by the library,
+        * it is a pointer to an internal buffer
+        * that becomes invalid when the next record
+        * is returned. If the user wants to keep this
+        * data around, they must copy it.
+        */
+       const void *fmtdata;
+       /* Length of fmtdata */
+       int fmtdatalen;
+       /* Cpu on which the event occured */
+       uint8_t cpu;
+       /*
+        * Thread, which generated the event (if applicable). The
+        * storage pointed to belongs to the library and may (even
+        * though it's highly unlikely) point to a different thread
+        * in the future. The user needs to copy it if they want
+        * this data.
+        */
+       struct evtr_thread *td;
+} *evtr_event_t;
+
+/*
+ * Specifies which conditions to filter query results
+ * with. It is modified by the library and should
+ * not be touched after initialization.
+ */
+typedef struct evtr_filter {
+       int flags;      /* must be initialized to 0 */
+       /*
+        * Which cpu we are interested in. -1 means
+        * any cpu. XXX: use mask? (note we could just
+        * do that internally)
+        */
+       int cpu;
+       /*
+        * If the user sets fmt, only events with a format
+        * string identical to the one specified will be
+        * returned. This field is modified by the library.
+        */
+       union {
+               const char *fmt;
+               int fmtid;
+       };
+} *evtr_filter_t;
+
+struct evtr_query;
+struct evtr;
+typedef struct evtr *evtr_t;
+
+int evtr_next_event(evtr_t, evtr_event_t);
+evtr_t evtr_open_read(FILE *);
+evtr_t evtr_open_write(FILE *);
+void evtr_close(evtr_t);
+int evtr_dump_event(evtr_t, evtr_event_t);
+int evtr_error(evtr_t);
+const char * evtr_errmsg(evtr_t);
+void evtr_event_data(evtr_event_t, char *, size_t);
+struct evtr_query * evtr_query_init(evtr_t, evtr_filter_t, int);
+void evtr_query_destroy(struct evtr_query *);
+int evtr_query_next(struct evtr_query *, evtr_event_t);
+int evtr_last_event(evtr_t, evtr_event_t);
+int evtr_rewind(evtr_t);
+
+int evtr_ncpus(evtr_t);
+void evtr_set_debug(int);
+
+
+#endif /* EVTR_H */
index b21100e..288b152 100644 (file)
@@ -75,6 +75,9 @@
 KTR_INFO_MASTER(ctxsw);
 KTR_INFO(KTR_CTXSW, ctxsw, sw, 0, "sw  %p > %p", 2 * sizeof(struct thread *));
 KTR_INFO(KTR_CTXSW, ctxsw, pre, 1, "pre %p > %p", 2 * sizeof(struct thread *));
+KTR_INFO(KTR_CTXSW, ctxsw, newtd, 2, "new_td %p %s", sizeof (struct thread *) +
+        sizeof(char *));
+KTR_INFO(KTR_CTXSW, ctxsw, deadtd, 3, "dead_td %p", sizeof (struct thread *));
 
 static MALLOC_DEFINE(M_THREAD, "thread", "lwkt threads");
 
@@ -387,6 +390,7 @@ lwkt_set_comm(thread_t td, const char *ctl, ...)
     __va_start(va, ctl);
     kvsnprintf(td->td_comm, sizeof(td->td_comm), ctl, va);
     __va_end(va);
+    KTR_LOG(ctxsw_newtd, td, &td->td_comm[0]);
 }
 
 void
@@ -425,6 +429,7 @@ lwkt_free_thread(thread_t td)
        td->td_kstack = NULL;
        td->td_kstack_size = 0;
     }
+    KTR_LOG(ctxsw_deadtd, td);
 }
 
 
index 04a3630..2539a6f 100644 (file)
@@ -50,6 +50,7 @@ SUBDIR=       alias \
        ee \
        enigma \
        env \
+       evtranalyze \
        expand \
        false \
        fetch \
diff --git a/usr.bin/evtranalyze/Makefile b/usr.bin/evtranalyze/Makefile
new file mode 100644 (file)
index 0000000..3099607
--- /dev/null
@@ -0,0 +1,6 @@
+PROG=  evtranalyze
+
+SRCS= xml.c svg.c evtranalyze.c
+DPADD= ${LIBEVTR}
+LDADD=  -levtr -lm
+.include <bsd.prog.mk>
diff --git a/usr.bin/evtranalyze/evtranalyze.1 b/usr.bin/evtranalyze/evtranalyze.1
new file mode 100644 (file)
index 0000000..4eee7cb
--- /dev/null
@@ -0,0 +1,87 @@
+.\"-
+.\" Copyright (c) 2009 Aggelos Economopoulos
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\"
+.Dd February 8, 2009
+.Dt EVTRANALYZE 1
+.Os
+.Sh NAME
+.Nm evtranalyze
+.Nd analyze a trace stream
+.Sh SYNOPSIS
+.Nm
+.Op Fl f Ar infile
+.Ar command
+.Op Ar argument ...
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to analyze an event trace stream.
+It takes a few global options, after which the user should
+specify a subcommand, followed by the subcommands options.
+.Ss Global options
+The global options are
+.Bl -tag -width indent
+.It Fl f Ar path
+Specifies the file containing the event stream.
+.El
+The subcommands are:
+.Bl -ohang -width indent
+.\" ==== show ====
+.It Cm show
+Lists the individual events, one per line.
+The flags it accepts are:
+.Bl -tag -width indent-two
+.It Fl f Ar fmt
+Limits the displayed events to those matching
+.Ar fmt .
+.El
+.It Cm svg
+Generates an svg file ("output.svg") in the current directory,
+displaying the stream events.
+The flags it accepts are:
+.Bl -tag -width indent-two
+.It Fl i Ar interval
+Limits the displayed events to those occuring within the
+specified time interval.
+The interval is specified in the form <num>:<num>, where
+<num> is given in clock cycles (absolute).
+.El
+.El
+.Sh SEE ALSO
+.Xr ktrdump 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Dx 2.5 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+utility was implemented by
+.An Aggelos Economopoulos Aq aggelos@dragonflybsd.org
+for
+.Dx .
diff --git a/usr.bin/evtranalyze/evtranalyze.c b/usr.bin/evtranalyze/evtranalyze.c
new file mode 100644 (file)
index 0000000..f7e7706
--- /dev/null
@@ -0,0 +1,714 @@
+/*
+ * Copyright (c) 2009, 2010 Aggelos Economopoulos.  All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ * 3. Neither the name of The DragonFly Project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific, prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <assert.h>
+#include <err.h>
+#include <libgen.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <evtr.h>
+#include "xml.h"
+#include "svg.h"
+
+enum {
+       NR_TOP_THREADS = 5,
+};
+
+struct rows {
+       double row_increment;
+       double row_off;
+};
+
+#define CMD_PROTO(name)        \
+       static int cmd_ ## name(int, char **)
+
+CMD_PROTO(show);
+CMD_PROTO(svg);
+
+struct command {
+       const char *name;
+       int (*func)(int argc, char **argv);
+} commands[] = {
+       {
+               .name = "show",
+               .func = &cmd_show,
+       },
+       {
+               .name = "svg",
+               .func = &cmd_svg,
+       },
+       {
+               .name = NULL,
+       },
+};
+
+evtr_t evtr;
+char *opt_infile;
+static int evtranalyze_debug;
+
+#define printd(...)                                    \
+       do {                                            \
+               if (evtranalyze_debug) {                \
+                       fprintf(stderr, __VA_ARGS__);   \
+               }                                       \
+       } while (0)
+
+static
+void
+usage(void)
+{
+       fprintf(stderr, "bad usage :P\n");
+       exit(2);
+}
+
+static
+void
+rows_init(struct rows *rows, int n, double height, double perc)
+{
+       double row_h;
+       rows->row_increment = height / n;
+       /* actual row height */
+        row_h = perc * rows->row_increment;
+       rows->row_off = (rows->row_increment - row_h) / 2.0;
+       assert(!isnan(rows->row_increment));
+       assert(!isnan(rows->row_off));
+}
+
+static
+void
+rows_n(struct rows *rows, int n, double *y, double *height)
+{
+       *y = n * rows->row_increment + rows->row_off;
+       *height = rows->row_increment - 2 * rows->row_off;
+}
+
+/*
+ * Which fontsize to use so that the string fits in the
+ * given rect.
+ */
+static
+double
+fontsize_for_rect(double width, double height, int textlen)
+{
+       double wpc, maxh;
+       /*
+        * We start with a font size equal to the height
+        * of the rectangle and round down so that we only
+        * use a limited number of sizes.
+        *
+        * For a rectangle width comparable to the height,
+        * the text might extend outside of the rectangle.
+        * In that case we need to limit it.
+        */
+       /* available width per character */
+       wpc = width / textlen;
+       /*
+        * Assuming a rough hight/width ratio for characters,
+        * calculate the available height and round it down
+        * just to be on the safe side.
+        */
+#define GLYPH_HIGHT_TO_WIDTH 1.5
+       maxh = GLYPH_HIGHT_TO_WIDTH * wpc * 0.9;
+       if (height > maxh) {
+               height = maxh;
+       } else if (height < 0.01) {
+               height = 0.01;
+       } else {
+               /* rounding (XXX: make cheaper)*/
+               height = log(height);
+               height = round(height);
+               height = exp(height);
+       }
+       return height;
+}
+
+struct pass_hook {
+       void (*pre)(void *);
+       void (*event)(void *, evtr_event_t);
+       void (*post)(void *);
+       void *data;
+       struct evtr_filter *filts;
+       int nfilts;
+};
+
+struct thread_info {
+       uint64_t runtime;
+};
+
+struct td_switch_ctx {
+       svg_document_t svg;
+       struct rows *cpu_rows;
+       struct rows *thread_rows;
+       uint64_t interval_start, interval_end;
+       uint64_t first_ts, last_ts;
+       double width;
+       double xscale;  /* scale factor applied to x */
+       svg_rect_t cpu_sw_rect;
+       svg_rect_t thread_rect;
+       svg_rect_t inactive_rect;
+       svg_text_t thread_label;
+       struct cpu *cpus;
+       int ncpus;
+       struct evtr_thread **top_threads;
+       int nr_top_threads;
+       double thread_rows_yoff;
+};
+
+struct cpu {
+       struct evtr_thread *td;
+       int i;          /* cpu index */
+       uint64_t ts;    /* time cpu switched to td */
+       uint64_t first_ts, last_ts;
+};
+
+static
+void
+do_pass(struct pass_hook *hooks, int nhooks)
+{
+       struct evtr_filter *filts = NULL;
+       int nfilts = 0, i;
+       struct evtr_query *q;
+       struct evtr_event ev;
+
+       for (i = 0; i < nhooks; ++i) {
+               struct pass_hook *h = &hooks[i];
+               if (h->pre)
+                       h->pre(h->data);
+               if (h->nfilts > 0) {
+                       filts = realloc(filts, (nfilts + h->nfilts) *
+                                       sizeof(struct evtr_filter));
+                       if (!filts)
+                               err(1, "Out of memory");
+                       memcpy(filts + nfilts, &h->filts,
+                              h->nfilts * sizeof(struct evtr_filter));
+                       nfilts += h->nfilts;
+               }
+       }
+       q = evtr_query_init(evtr, filts, nfilts);
+       if (!q)
+               err(1, "Can't initialize query\n");
+       while(!evtr_query_next(q, &ev)) {
+               for (i = 0; i < nhooks; ++i) {
+                       if (hooks[i].event)
+                               hooks[i].event(hooks[i].data, &ev);
+               }
+       }
+       if (evtr_error(evtr)) {
+               err(1, evtr_errmsg(evtr));
+       }
+       evtr_query_destroy(q);
+
+       for (i = 0; i < nhooks; ++i) {
+               if (hooks[i].post)
+                       hooks[i].post(hooks[i].data);
+       }
+       if (evtr_rewind(evtr))
+               err(1, "Can't rewind event stream\n");
+}
+
+static
+void
+draw_thread_run(struct td_switch_ctx *ctx, struct cpu *c, evtr_event_t ev, int row)
+{
+       double x, w, y, height;
+       w = (ev->ts - c->ts) * ctx->xscale;
+       x = (ev->ts - ctx->first_ts) * ctx->xscale;
+       rows_n(ctx->thread_rows, row, &y, &height);
+       svg_rect_draw(ctx->svg, ctx->thread_rect, x - w,
+                     y + ctx->thread_rows_yoff, w, height);
+}
+
+static
+void
+draw_ctx_switch(struct td_switch_ctx *ctx, struct cpu *c, evtr_event_t ev)
+{
+       struct svg_transform textrot;
+       char comm[100];
+       double x, w, fs, y, height;
+       int textlen;
+
+       assert(ctx->xscale > 0.0);
+       if (!c->ts)
+               return;
+       /* distance to previous context switch */
+       w = (ev->ts - c->ts) * ctx->xscale;
+       x = (ev->ts - ctx->first_ts) * ctx->xscale;
+       if ((x - w) < 0) {
+               fprintf(stderr, "(%llu - %llu) * %.20lf\n", ev->ts,
+                       ctx->first_ts, ctx->xscale);
+               abort();
+       }
+
+       rows_n(ctx->cpu_rows, c->i, &y, &height);
+       assert(!isnan(y));
+       assert(!isnan(height));
+
+       svg_rect_draw(ctx->svg, ctx->cpu_sw_rect, x - w, y, w, height);
+
+       /*
+        * Draw the text label describing the thread we
+        * switched out of.
+        */
+       textrot.tx = x - w;
+       textrot.ty = y;
+       textrot.sx = 1.0;
+       textrot.sy = 1.0;
+       textrot.rot = 90.0;
+       textlen = snprintf(comm, sizeof(comm) - 1, "%s (%p)",
+                          c->td ? c->td->comm : "unknown",
+                                c->td ? c->td->id: NULL);
+       if (textlen > (int)sizeof(comm))
+               textlen = sizeof(comm) - 1;
+       comm[sizeof(comm) - 1] = '\0';
+       /*
+        * Note the width and hight are exchanged because
+        * the bounding rectangle is rotated by 90 degrees.
+        */
+       fs = fontsize_for_rect(height, w, textlen);
+       svg_text_draw(ctx->svg, ctx->thread_label, &textrot, comm,
+                     fs);
+}
+
+
+/*
+ * The stats for ntd have changed, update ->top_threads
+ */
+static
+void
+top_threads_update(struct td_switch_ctx *ctx, struct evtr_thread *ntd)
+{
+       struct thread_info *tdi = ntd->userdata;
+       int i, j;
+       for (i = 0; i < ctx->nr_top_threads; ++i) {
+               struct evtr_thread *td = ctx->top_threads[i];
+               if (td == ntd) {
+                       /*
+                        * ntd is already in top_threads and it is at
+                        * the correct ranking
+                        */
+                       break;
+               }
+               if (!td) {
+                       /* empty slot -- just insert our thread */
+                       ctx->top_threads[i] = ntd;
+                       break;
+               }
+               if (((struct thread_info *)td->userdata)->runtime >=
+                   tdi->runtime) {
+                       /* this thread ranks higher than we do. Move on */
+                       continue;
+               }
+               /*
+                * OK, we've found the first thread that we outrank, so we
+                * need to replace it w/ our thread.
+                */
+               td = ntd;       /* td holds the thread we will insert next */
+               for (j = i + 1; j < ctx->nr_top_threads; ++j, ++i) {
+                       struct evtr_thread *tmp;
+
+                       /* tmp holds the thread we replace */
+                       tmp = ctx->top_threads[j];
+                       ctx->top_threads[j] = td;
+                       if (tmp == ntd) {
+                               /*
+                                * Our thread was already in the top list,
+                                * and we just removed the second instance.
+                                * Nothing more to do.
+                                */
+                               break;
+                       }
+                       td = tmp;
+               }
+               break;
+       }
+}
+
+static
+void
+ctxsw_prepare_event(void *_ctx, evtr_event_t ev)
+{
+       struct td_switch_ctx *ctx = _ctx;
+       struct cpu *c, *cpus = ctx->cpus;
+       struct thread_info *tdi;
+
+       (void)evtr;
+       printd("test1 (%llu:%llu) : %llu\n", ctx->interval_start,
+              ctx->interval_end, ev->ts);
+       if ((ev->ts > ctx->interval_end) ||
+           (ev->ts < ctx->interval_start))
+               return;
+       printf("FPEV\n");
+
+       /* update first/last timestamps */
+       c = &cpus[ev->cpu];
+       if (!c->first_ts) {
+               c->first_ts = ev->ts;
+               printd("setting first_ts (%d) = %llu\n", ev->cpu,
+                      c->first_ts);
+       }
+       c->last_ts = ev->ts;
+       /*
+        * c->td can be null when this is the first ctxsw event we
+        * observe for a cpu
+        */
+       if (c->td) {
+               /* update thread stats */
+               if (!c->td->userdata) {
+                       if (!(tdi = malloc(sizeof(struct thread_info))))
+                               err(1, "Out of memory");
+                       c->td->userdata = tdi;
+                       tdi->runtime = 0;
+               }
+               tdi = c->td->userdata;
+               tdi->runtime += ev->ts - c->ts;
+               printd("EVCPU %d\n", c->i);
+               top_threads_update(ctx, c->td);
+       }
+
+       /* Notice that ev->td is the new thread for ctxsw events */
+       c->td = ev->td;
+       c->ts = ev->ts;
+}
+
+static
+void
+ctxsw_prepare_post(void *_ctx)
+{
+       struct td_switch_ctx *ctx = _ctx;
+       struct cpu *cpus = ctx->cpus;
+       int i;
+
+       (void)evtr;
+       ctx->first_ts = -1;
+       ctx->last_ts = 0;
+       printd("first_ts[0] = %llu\n",cpus[0].first_ts);
+       for (i = 0; i < ctx->ncpus; ++i) {
+               printd("first_ts[%d] = %llu\n", i, cpus[i].first_ts);
+               if (cpus[i].first_ts && (cpus[i].first_ts < ctx->first_ts))
+                       ctx->first_ts = cpus[i].first_ts;
+               if (cpus[i].last_ts && (cpus[i].last_ts > ctx->last_ts))
+                       ctx->last_ts = cpus[i].last_ts;
+               cpus[i].td = NULL;
+               cpus[i].ts = 0;
+       }
+}
+
+static
+void
+ctxsw_draw_pre(void *_ctx)
+{
+       struct td_switch_ctx *ctx = _ctx;
+       struct svg_transform textrot;
+       char comm[100];
+       double y, height, fs;
+       int i, textlen;
+       struct evtr_thread *td;
+
+       textrot.tx = 0.0 - 0.2; /* XXX */
+       textrot.sx = 1.0;
+       textrot.sy = 1.0;
+       textrot.rot = 270.0;
+
+       for (i = 0; i < ctx->nr_top_threads; ++i) {
+               td = ctx->top_threads[i];
+               rows_n(ctx->thread_rows, i, &y, &height);
+               svg_rect_draw(ctx->svg, ctx->inactive_rect, 0.0,
+                             y + ctx->thread_rows_yoff, ctx->width, height);
+               textlen = snprintf(comm, sizeof(comm) - 1, "%s (%p)",
+                                  td->comm, td->id);
+               if (textlen > (int)sizeof(comm))
+                       textlen = sizeof(comm) - 1;
+               comm[sizeof(comm) - 1] = '\0';
+               fs = fontsize_for_rect(height, 100.0, textlen);
+
+               textrot.ty = y + ctx->thread_rows_yoff + height;
+               svg_text_draw(ctx->svg, ctx->thread_label, &textrot,
+                             comm, fs);
+       }
+}
+
+static
+void
+ctxsw_draw_event(void *_ctx, evtr_event_t ev)
+{
+       struct td_switch_ctx *ctx = _ctx;
+       struct cpu *c = &ctx->cpus[ev->cpu];
+       int i;
+
+       /*
+        * ctx->last_ts can be 0 if there were no events
+        * in the specified interval, in which case
+        * ctx->first_ts is invalid too.
+        */
+       assert(!ctx->last_ts || (ev->ts >= ctx->first_ts));
+       printd("test2 (%llu:%llu) : %llu\n", ctx->interval_start,
+              ctx->interval_end, ev->ts);
+       if ((ev->ts > ctx->interval_end) ||
+           (ev->ts < ctx->interval_start))
+               return;
+       printd("SPEV %d\n", ev->cpu);
+       if (c->td != ev->td) {  /* thread switch (or preemption) */
+               draw_ctx_switch(ctx, c, ev);
+               /* XXX: this is silly */
+               for (i = 0; i < ctx->nr_top_threads; ++i) {
+                       if (ctx->top_threads[i] == c->td) {
+                               draw_thread_run(ctx, c, ev, i);
+                               break;
+                       }
+               }
+               c->td = ev->td;
+               c->ts = ev->ts;
+       }
+}
+
+static
+int
+cmd_svg(int argc, char **argv)
+{
+       svg_document_t svg;
+       int ncpus, i, ch;
+       double height, width;
+       struct rows cpu_rows, thread_rows;
+       struct cpu *cpus;
+       struct td_switch_ctx td_ctx;
+       struct evtr_filter ctxsw_filts[2] = {
+               {
+                       .flags = 0,
+                       .cpu = -1,
+               },
+               {
+                       .flags = 0,
+                       .cpu = -1,
+               },
+       };
+       struct pass_hook ctxsw_prepare = {
+               .pre = NULL,
+               .event = ctxsw_prepare_event,
+               .post = ctxsw_prepare_post,
+               .data = &td_ctx,
+               .filts = ctxsw_filts,
+               .nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
+       }, ctxsw_draw = {
+               .pre = ctxsw_draw_pre,
+               .event = ctxsw_draw_event,
+               .post = NULL,
+               .data = &td_ctx,
+               .filts = ctxsw_filts,
+               .nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
+       };
+
+       /*
+        * We are interested in thread switch and preemption
+        * events, but we don't use the data directly. Instead
+        * we rely on ev->td.
+        */
+       ctxsw_filts[0].fmt = "sw %p > %p";
+       ctxsw_filts[1].fmt = "pre %p > %p";
+       td_ctx.interval_start = 0;
+       td_ctx.interval_end = -1;       /* i.e. no interval given */
+       td_ctx.nr_top_threads = NR_TOP_THREADS;
+
+       printd("argc: %d, argv[0] = %s\n", argc, argv[0] ? argv[0] : "NULL");
+       optind = 0;
+       optreset = 1;
+       while ((ch = getopt(argc, argv, "i:")) != -1) {
+               switch (ch) {
+               case 'i':
+                       if (sscanf(optarg, "%llu:%llu", &td_ctx.interval_start,
+                                  &td_ctx.interval_end) != 2) {
+                               usage();
+                       }
+                       break;
+               default:
+                       usage();
+               }
+
+       }
+       argc -= optind;
+       argv += optind;
+
+       height = 200.0;
+       width = 700.0;
+       td_ctx.width = width;
+       if ((ncpus = evtr_ncpus(evtr)) <= 0)
+               err(1, "No cpu information!\n");
+       printd("evtranalyze: ncpus %d\n", ncpus);
+
+       if (!(cpus = malloc(ncpus * sizeof(struct cpu))))
+               err(1, "Can't allocate memory\n");
+       /* initialize cpu array */
+       for (i = 0; i < ncpus; ++i) {
+               cpus[i].td = NULL;
+               cpus[i].ts = 0;
+               cpus[i].i = i;
+               cpus[i].first_ts = 0;
+               cpus[i].last_ts = 0;
+       }
+       td_ctx.cpus = cpus;
+       td_ctx.ncpus = ncpus;
+       if (!(td_ctx.top_threads = calloc(td_ctx.nr_top_threads, sizeof(struct evtr_thread *))))
+               err(1, "Can't allocate memory\n");
+       if (!(svg = svg_document_create("output.svg")))
+               err(1, "Can't open svg document\n");
+
+       /*
+        * Create rectangles to use for output.
+        */
+       if (!(td_ctx.cpu_sw_rect = svg_rect_new("generic")))
+               err(1, "Can't create rectangle\n");
+       if (!(td_ctx.thread_rect = svg_rect_new("thread")))
+               err(1, "Can't create rectangle\n");
+       if (!(td_ctx.inactive_rect = svg_rect_new("inactive")))
+               err(1, "Can't create rectangle\n");
+       /* text for thread names */
+       if (!(td_ctx.thread_label = svg_text_new("generic")))
+               err(1, "Can't create text\n");
+       rows_init(&cpu_rows, ncpus, height, 0.9);
+       td_ctx.svg = svg;
+       td_ctx.xscale = -1.0;
+       td_ctx.cpu_rows = &cpu_rows;
+
+       do_pass(&ctxsw_prepare, 1);
+       td_ctx.thread_rows_yoff = height;
+       td_ctx.thread_rows = &thread_rows;
+       rows_init(td_ctx.thread_rows, td_ctx.nr_top_threads, 300, 0.9);
+       td_ctx.xscale = width / (td_ctx.last_ts - td_ctx.first_ts);
+       printd("first %llu, last %llu, xscale %lf\n", td_ctx.first_ts,
+              td_ctx.last_ts, td_ctx.xscale);
+
+       do_pass(&ctxsw_draw, 1);
+
+       svg_document_close(svg);
+       return 0;
+}
+
+static
+int
+cmd_show(int argc, char **argv)
+{
+       struct evtr_event ev;
+       struct evtr_query *q;
+       struct evtr_filter filt;
+       int ch;
+
+       filt.fmt = NULL;
+       optind = 0;
+       optreset = 1;
+       while ((ch = getopt(argc, argv, "f:")) != -1) {
+               switch (ch) {
+               case 'f':
+                       filt.fmt = optarg;
+                       break;
+               }
+       }
+       filt.flags = 0;
+       filt.cpu = -1;
+       printd("fmt = %s\n", filt.fmt ? filt.fmt : "NULL");
+       q = evtr_query_init(evtr, &filt, 1);
+       if (!q)
+               err(1, "Can't initialize query\n");
+       while(!evtr_query_next(q, &ev)) {
+               char buf[1024];
+               printf("%s\t%llu cycles\t[%.3d]\t%s:%d",
+                      ev.td ? ev.td->comm : "unknown",
+                      ev.ts, ev.cpu,
+                      basename(ev.file), ev.line);
+               if (ev.fmt) {
+                       evtr_event_data(&ev, buf, sizeof(buf));
+                       printf(" !\t%s\n", buf);
+               } else {
+                       printf("\n");
+               }
+       }
+       if (evtr_error(evtr)) {
+               err(1, evtr_errmsg(evtr));
+       }
+       evtr_query_destroy(q);
+       return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+       int ch;
+       FILE *inf;
+       struct command *cmd;
+
+       while ((ch = getopt(argc, argv, "f:D:")) != -1) {
+               switch (ch) {
+               case 'f':
+                       opt_infile = optarg;
+                       break;
+               case 'D':
+                       evtranalyze_debug = atoi(optarg);
+                       evtr_set_debug(evtranalyze_debug);
+                       break;
+               default:
+                       usage();
+               }
+       }
+       argc -= optind;
+       argv += optind;
+
+       if (argc == 0) {
+               err(2, "need to specify a command\n");
+       }
+       if (!opt_infile || !strcmp(opt_infile, "-")) {
+               inf = stdin;
+       } else {
+               inf = fopen(opt_infile, "r");
+               if (!inf) {
+                       err(2, "Can't open input file\n");
+               }
+       }
+
+       if (!(evtr = evtr_open_read(inf))) {
+               err(1, "Can't open evtr stream\n");
+       }
+
+
+       for (cmd = commands; cmd->name != NULL; ++cmd) {
+               if (strcmp(argv[0], cmd->name))
+                       continue;
+               cmd->func(argc, argv);
+               break;
+       }
+       if (!cmd->name) {
+               err(2, "no such command: %s\n", argv[0]);
+       }
+               
+       evtr_close(evtr);
+       return 0;
+}
diff --git a/usr.bin/evtranalyze/svg.c b/usr.bin/evtranalyze/svg.c
new file mode 100644 (file)
index 0000000..e3d8bfb
--- /dev/null
@@ -0,0 +1,337 @@
+/*
+ * Copyright (c) 2009, 2010 Aggelos Economopoulos.  All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ * 3. Neither the name of The DragonFly Project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific, prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <assert.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "xml.h"
+#include "svg.h"
+
+enum {
+       MAX_VALSTR_LEN = 30,
+};
+
+struct svg_rect {
+       struct xml_element el;
+       struct xml_attribute x, y, w, h, cl;
+       char x_val[MAX_VALSTR_LEN];
+       char y_val[MAX_VALSTR_LEN];
+       char w_val[MAX_VALSTR_LEN];
+       char h_val[MAX_VALSTR_LEN];
+};
+
+struct svg_text {
+       struct xml_element el;
+       struct xml_attribute x, y, cl;
+       struct xml_attribute fontsize, transform;
+       char x_val[MAX_VALSTR_LEN];
+       char y_val[MAX_VALSTR_LEN];
+       char fontsize_val[MAX_VALSTR_LEN];
+       char transform_val[MAX_VALSTR_LEN * 4];
+};
+
+struct svg_line {
+       struct xml_element el;
+       struct xml_attribute x1, y1, x2, y2, cl;
+       struct xml_attribute transform;
+       char x1_val[MAX_VALSTR_LEN], y1_val[MAX_VALSTR_LEN];
+       char x2_val[MAX_VALSTR_LEN], y2_val[MAX_VALSTR_LEN];
+       char transform_val[MAX_VALSTR_LEN * 6];
+};
+
+struct svg_document {
+       xml_document_t xml;
+       const char *css;
+       struct xml_element svg;
+       struct xml_attribute svg_attrs[2];
+       struct svg_text text;
+};
+
+static char default_css[] =
+       "<![CDATA["
+       "rect.generic { fill: green; stroke: black; stroke-width: 0.01;}"
+       "rect.thread { fill: yellow; stroke: black; stroke-width: 0.01;}"
+       "rect.inactive { fill: grey; stroke: black; stroke-width: 0.01;}"
+       "text.generic { fill: black; stroke: none;}]]>";
+
+static
+int
+svg_transform_print(svg_transform_t tf, char *buf, size_t len)
+{
+       static double eps = 0.0001;
+       char *p;
+       int c;
+
+       if (!tf) {
+               assert(len >= 1);
+               buf[0] = '\0';
+               return 0;
+       }
+       p = buf;
+       if ((fabs(tf->tx) > eps) && (fabs(tf->ty) > eps)) {
+               c = snprintf(buf, len, "translate(%.20lf,%.20lf)", tf->tx,
+                            tf->ty);
+               len -= c;
+               if (len <= 0)
+                       return !0;
+               p += c;
+       }
+       if ((fabs(tf->sx - 1) > eps) && (fabs(tf->sy - 1) > eps)) {
+               c = snprintf(p, len, "%sscale(%.20lf,%.20lf)",
+                            (p == buf) ? "" : " ", tf->sx, tf->sy);
+               len -= c;
+               if (len <= 0)
+                       return !0;
+               p += c;
+       }
+       if (fabs(tf->rot) > eps) {
+               c = snprintf(p, len, "%srotate(%.2lf)",
+                            (p == buf) ? "" : " ", tf->rot);
+               len -= c;
+               if (len <= 0)
+                       return !0;
+               p += c;
+       }
+       return 0;
+}
+
+static
+void
+svg_rect_init(struct svg_rect *rect, const char *cl)
+{
+       xml_elem_init(&rect->el, "rect");
+       xml_attribute_init(&rect->x, "x", NULL);
+       xml_elem_set_attribute(&rect->el, &rect->x);
+       xml_attribute_init(&rect->y, "y", NULL);
+       xml_elem_set_attribute(&rect->el, &rect->y);
+       xml_attribute_init(&rect->w, "width", NULL);
+       xml_elem_set_attribute(&rect->el, &rect->w);
+       xml_attribute_init(&rect->h, "height", NULL);
+       xml_elem_set_attribute(&rect->el, &rect->h);
+       if (cl) {
+               xml_attribute_init(&rect->cl, "class", cl);
+               xml_elem_set_attribute(&rect->el, &rect->cl);
+       }
+}
+
+/*
+ * In the future, we might want to stick the rectangle in the
+ * <defs> element at this point and then <use> it in the rest
+ * of the document.
+ */
+struct svg_rect *
+svg_rect_new(const char *cl)
+{
+       struct svg_rect *r;
+
+       if (!(r = malloc(sizeof(*r))))
+               return r;
+       svg_rect_init(r, cl);
+       return r;
+}
+
+
+int
+svg_rect_draw(svg_document_t doc, struct svg_rect *rect, double x,
+             double y, double w, double h)
+{
+       snprintf(&rect->x_val[0], sizeof(rect->x_val), "%.20lf", x);
+       xml_attribute_set_value(&rect->x, &rect->x_val[0]);
+       snprintf(&rect->y_val[0], sizeof(rect->y_val), "%lf", y);
+       xml_attribute_set_value(&rect->y, &rect->y_val[0]);
+       snprintf(&rect->w_val[0], sizeof(rect->w_val), "%.20lf", w);
+       xml_attribute_set_value(&rect->w, &rect->w_val[0]);
+       snprintf(&rect->h_val[0], sizeof(rect->h_val), "%lf", h);
+       xml_attribute_set_value(&rect->h, &rect->h_val[0]);
+
+       xml_elem_closed(doc->xml, &rect->el);
+       return 0;
+}
+
+static
+void
+svg_text_init(struct svg_text *text, const char *cl)
+{
+       xml_elem_init(&text->el, "text");
+#if remove
+       xml_attribute_init(&text->x, "x", NULL);
+       xml_elem_set_attribute(&text->el, &text->x);
+       xml_attribute_init(&text->y, "y", NULL);
+       xml_elem_set_attribute(&text->el, &text->y);
+#endif
+       xml_attribute_init(&text->fontsize, "font-size", NULL);
+       xml_elem_set_attribute(&text->el, &text->fontsize);
+       xml_attribute_init(&text->transform, "transform", NULL);
+       xml_elem_set_attribute(&text->el, &text->transform);
+
+       if (cl) {
+               xml_attribute_init(&text->cl, "class", cl);
+               xml_elem_set_attribute(&text->el, &text->cl);
+       }
+
+}
+
+struct svg_text *
+svg_text_new(const char *cl)
+{
+       svg_text_t text;
+
+       if (!(text = malloc(sizeof(*text))))
+               return text;
+       svg_text_init(text, cl);
+       return text;
+}
+
+int
+svg_text_draw(svg_document_t doc, svg_text_t text, svg_transform_t tf,
+             const char *str, double fontsize)
+{
+#if remove
+       snprintf(&text->x_val[0], sizeof(text->x_val), "%.20lf", x);
+       xml_attribute_set_value(&text->x, &text->x_val[0]);
+       snprintf(&text->y_val[0], sizeof(text->y_val), "%.20lf", y);
+       xml_attribute_set_value(&text->y, &text->y_val[0]);
+#endif
+       snprintf(&text->fontsize_val[0], sizeof(text->fontsize_val), "%.20lf",
+                fontsize);
+       xml_attribute_set_value(&text->fontsize, &text->fontsize_val[0]);
+       if (svg_transform_print(tf, &text->transform_val[0],
+                               sizeof(text->transform_val)))
+               return !0;
+       xml_attribute_set_value(&text->transform, &text->transform_val[0]);
+       xml_elem_set_value(&text->el, str);
+
+       xml_elem_closed(doc->xml, &text->el);
+       return 0;
+}
+
+static
+void
+svg_line_init(struct svg_line *line, const char *cl)
+{
+       xml_elem_init(&line->el, "line");
+       xml_attribute_init(&line->x1, "x1", NULL);
+       xml_elem_set_attribute(&line->el, &line->x1);
+       xml_attribute_init(&line->x2, "x2", NULL);
+       xml_elem_set_attribute(&line->el, &line->x2);
+       xml_attribute_init(&line->y1, "y1", NULL);
+       xml_elem_set_attribute(&line->el, &line->y1);
+       xml_attribute_init(&line->y2, "y2", NULL);
+       xml_elem_set_attribute(&line->el, &line->y2);
+
+       xml_attribute_init(&line->transform, "transform", NULL);
+       xml_elem_set_attribute(&line->el, &line->transform);
+
+       if (cl) {
+               xml_attribute_init(&line->cl, "class", cl);
+               xml_elem_set_attribute(&line->el, &line->cl);
+       }
+
+}
+
+struct svg_line *
+svg_line_new(const char *cl)
+{
+       svg_line_t line;
+
+       if (!(line = malloc(sizeof(*line))))
+               return line;
+       svg_line_init(line, cl);
+       return line;
+}
+
+int
+svg_line_draw(svg_document_t doc, svg_line_t line, double x1, double _y1,
+             double x2, double y2, svg_transform_t tf)
+{
+       snprintf(&line->x1_val[0], sizeof(line->x1_val), "%.20lf", x1);
+       xml_attribute_set_value(&line->x1, &line->x1_val[0]);
+
+       snprintf(&line->x2_val[0], sizeof(line->x2_val), "%.20lf", x2);
+       xml_attribute_set_value(&line->x2, &line->x2_val[0]);
+
+       snprintf(&line->y1_val[0], sizeof(line->y1_val), "%.10lf", _y1);
+       xml_attribute_set_value(&line->y1, &line->y1_val[0]);
+
+       snprintf(&line->y2_val[0], sizeof(line->y2_val), "%.20lf", y2);
+       xml_attribute_set_value(&line->y2, &line->y2_val[0]);
+
+       xml_attribute_set_value(&line->transform, &line->transform_val[0]);
+       if (svg_transform_print(tf,
+                               &line->transform_val[0],
+                               sizeof(line->transform_val)))
+               return !0;
+       xml_elem_closed(doc->xml, &line->el);
+       return 0;
+}
+
+svg_document_t
+svg_document_create(const char *path)
+{
+       svg_document_t svg;
+       struct xml_element style, defs;
+       struct xml_attribute type;
+
+       if (!(svg = malloc(sizeof(*svg))))
+               return NULL;
+       if (!(svg->xml = xml_document_create(path))) {
+               free(svg);
+               return NULL;
+       }
+       svg->css = &default_css[0];
+       xml_attribute_init(&type, "type", "text/css");
+       xml_elem_init(&defs, "defs");
+       xml_elem_init(&style, "style");
+       xml_elem_set_attribute(&style, &type);
+       xml_elem_init(&svg->svg, "svg");
+       xml_attribute_init(&svg->svg_attrs[0], "version", "1.1");
+       xml_elem_set_attribute(&svg->svg, &svg->svg_attrs[0]);
+       xml_attribute_init(&svg->svg_attrs[1], "xmlns",
+                          "http://www.w3.org/2000/svg");
+       xml_elem_set_attribute(&svg->svg, &svg->svg_attrs[1]);
+       xml_elem_begin(svg->xml, &svg->svg);
+       xml_elem_begin(svg->xml, &defs);
+       xml_elem_set_value(&style, svg->css);
+       xml_elem_closed(svg->xml, &style);
+       xml_elem_close(svg->xml, &defs);
+       
+       return svg;
+}
+
+int
+svg_document_close(svg_document_t svg)
+{
+       xml_elem_close(svg->xml, &svg->svg);
+       xml_document_close(svg->xml);
+       return 0;
+}
diff --git a/usr.bin/evtranalyze/svg.h b/usr.bin/evtranalyze/svg.h
new file mode 100644 (file)
index 0000000..4501899
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2009, 2010 Aggelos Economopoulos.  All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ * 3. Neither the name of The DragonFly Project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific, prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef SVG_H
+#define SVG_H
+
+#include <stdint.h>
+
+struct svg_document;
+struct svg_rect;
+struct svg_text;
+struct svg_line;
+typedef struct svg_document *svg_document_t;
+typedef struct svg_rect *svg_rect_t;
+typedef struct svg_text *svg_text_t;
+typedef struct svg_line *svg_line_t;
+
+typedef struct svg_transform {
+       double tx, ty;
+       double sx, sy;
+       double rot;
+} *svg_transform_t;
+
+svg_document_t svg_document_create(const char *);
+int svg_document_close(svg_document_t);
+struct svg_rect *svg_rect_new(const char *);
+int svg_rect_draw(svg_document_t, svg_rect_t, double, double, double,
+                 double);
+struct svg_text *svg_text_new(const char *);
+int svg_text_draw(svg_document_t, svg_text_t, svg_transform_t,
+             const char *, double);
+struct svg_line *svg_line_new(const char *);
+int svg_line_draw(svg_document_t, svg_line_t, double, double, double, double,
+                 svg_transform_t);
+
+#endif /* SVG_H */
diff --git a/usr.bin/evtranalyze/xml.c b/usr.bin/evtranalyze/xml.c
new file mode 100644 (file)
index 0000000..50f20db
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2009, 2010 Aggelos Economopoulos.  All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ * 3. Neither the name of The DragonFly Project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific, prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "xml.h"
+
+
+xml_document_t
+xml_document_create(const char *file)
+{
+       xml_document_t doc;
+
+       if ((doc = malloc(sizeof(xml_document_t))) == NULL)
+               return (NULL);
+
+       if ((doc->file = fopen(file, "w")) == NULL) {
+               free(doc);
+               return (NULL);
+       }
+       STAILQ_INIT(&doc->open_elems);
+       doc->nr_open = 0;
+       doc->errmsg = NULL;
+
+       fprintf(doc->file, "<?xml version=\"1.0\" encoding=\"UTF-8\" "
+               "standalone=\"no\"?>\n");
+       fprintf(doc->file, "<!-- Created by evtranalyze -->\n");
+
+       return doc;
+}
+
+int
+xml_document_close(xml_document_t doc)
+{
+       fclose(doc->file);
+       return 0;
+}
+
+
+static
+void
+indent(xml_document_t doc)
+{
+       int i;
+
+       for (i = 0; i < doc->nr_open; ++i) {
+               fprintf(doc->file, "  ");
+       }
+}
+
+#if 0
+int
+xml_elem_compile(xml_element_t el)
+{
+       char *buf, *p;
+       int bufsize, c, ret;
+
+       bufsize = 2;
+       if (!(buf = malloc(bufsize))) {
+               return !0;
+       }
+again_name:
+       p = buf;
+       ret = snprintf(p, sizeof(buf), "<%s ", el->name);
+       if (ret > sizeof(buf)) {
+               bufsize *= 2;
+               buf = realloc(bufsize);
+               if (!buf) {
+                       free(p);
+                       return !0;
+               }
+               goto again_name;
+       }
+       c += ret;
+}
+#endif
+
+static
+int
+xml_elem_print(xml_document_t doc, xml_element_t el, int closed, int nl)
+{
+       xml_attribute_t at;
+       fprintf(doc->file, "<%s", el->name);
+       STAILQ_FOREACH(at, &el->attributes, next) {
+               fprintf(doc->file, " %s=\"%s\"", at->name, at->value);
+       }
+       fprintf(doc->file, "%s%s", closed ? "/>" : ">", nl ? "\n" : "");
+       return 0;
+}
+
+static
+int
+_xml_elem_begin(xml_document_t doc, xml_element_t el, int closed, int nl)
+{
+       STAILQ_INSERT_HEAD(&doc->open_elems, el, link);
+       indent(doc);
+       ++doc->nr_open;
+       xml_elem_print(doc, el, closed, nl);
+       return 0;
+}
+
+static
+int
+_xml_elem_close(xml_document_t doc, xml_element_t el, int do_indent)
+{
+       if (el != STAILQ_FIRST(&doc->open_elems)) {
+               return !0;
+       }
+
+       STAILQ_REMOVE_HEAD(&doc->open_elems, link);
+       --doc->nr_open;
+       if (do_indent)
+               indent(doc);
+       fprintf(doc->file, "</%s>\n", el->name);
+       return 0;
+}
+
+int
+xml_elem_close(xml_document_t doc, xml_element_t el)
+{
+       return _xml_elem_close(doc, el, !0);
+}
+
+int
+xml_elem_begin(xml_document_t doc, xml_element_t el)
+{
+       if (el->value) {
+               return !0;
+       }
+       return _xml_elem_begin(doc, el, 0, !0);
+}
+
+int
+xml_elem_closed(xml_document_t doc, xml_element_t el)
+{
+       if (el->value) {
+               _xml_elem_begin(doc, el, 0, 0);
+               fprintf(doc->file, "%s", el->value);
+               _xml_elem_close(doc, el, 0);
+               return 0;
+       }
+       indent(doc);
+       return xml_elem_print(doc, el, !0, !0);
+}
+
diff --git a/usr.bin/evtranalyze/xml.h b/usr.bin/evtranalyze/xml.h
new file mode 100644 (file)
index 0000000..590f778
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2009, 2010 Aggelos Economopoulos.  All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ * 3. Neither the name of The DragonFly Project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific, prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _EVTRANALYZE_XML_H_
+#define _EVTRANALYZE_XML_H_
+
+#include <stdio.h>
+#include <sys/queue.h>
+
+
+typedef struct xml_attribute {
+       const char    *name;
+       const char    *value;
+       STAILQ_ENTRY(xml_attribute) next;
+} *xml_attribute_t;
+
+typedef struct xml_element {
+       const char    *name;
+       const char    *value;
+       STAILQ_HEAD(, xml_attribute) attributes;
+       STAILQ_ENTRY(xml_element) link;
+} *xml_element_t;
+
+typedef struct xml_document {
+       FILE    *file;
+       STAILQ_HEAD(, xml_element) open_elems;
+       int nr_open;
+       const char *errmsg;
+} *xml_document_t;
+
+static inline
+void
+xml_elem_init(xml_element_t el, const char *name)
+{
+       el->name = name;
+       el->value = NULL;
+       STAILQ_INIT(&el->attributes);
+}
+
+static inline
+void
+xml_elem_set_value(xml_element_t el, const char *value)
+{
+       el->value = value;
+}
+
+static inline
+void
+xml_attribute_init(xml_attribute_t at, const char *name, const char *value)
+{
+       at->name = name;
+       at->value = value;
+}
+
+static inline
+void
+xml_attribute_set_value(xml_attribute_t at, const char *value)
+{
+       at->value = value;
+}
+
+static inline
+void
+xml_elem_set_attribute(xml_element_t el, xml_attribute_t at)
+{
+       STAILQ_INSERT_TAIL(&el->attributes, at, next);
+}
+
+
+xml_document_t xml_document_create(const char *);
+int xml_document_close(xml_document_t);
+int xml_elem_closed(xml_document_t, xml_element_t);
+int xml_elem_begin(xml_document_t, xml_element_t);
+int xml_elem_close(xml_document_t, xml_element_t);
+
+#endif /* !_EVTRANALYZE_XML_H_ */
index 806fa6b..72a74b4 100644 (file)
@@ -2,8 +2,8 @@
 # $FreeBSD: src/usr.bin/ktrdump/Makefile,v 1.3 2002/06/06 11:27:03 ru Exp $
 
 PROG=  ktrdump
-DPADD= ${LIBKVM}
-LDADD= -lkvm
+DPADD= ${LIBKVM} ${LIBEVTR}
+LDADD= -lkvm -levtr
 MAN=   ktrdump.8
 
 .include <bsd.prog.mk>
index 5e5f45c..80497ff 100644 (file)
@@ -34,7 +34,7 @@
 .Nd print kernel ktr trace buffer
 .Sh SYNOPSIS
 .Nm
-.Op Fl acfilnpqrstx
+.Op Fl acdfilnpqrstx
 .Op Fl A Ar factor
 .Op Fl N Ar execfile
 .Op Fl M Ar corefile
@@ -61,6 +61,11 @@ Note that
 is not included.
 .It Fl c
 Print the CPU number that each entry was logged from.
+.It Fl d
+Dump an event stream to the file specified with
+.Fl o .
+This stream can be examined with
+.Xr evtrdump 8 .
 .It Fl f
 Print the file and line number that each entry was logged from.
 .It Fl i
index d48cfc0..0f8fe26 100644 (file)
@@ -36,6 +36,7 @@
 #include <sys/stat.h>
 #include <sys/queue.h>
 
+#include <ctype.h>
 #include <err.h>
 #include <fcntl.h>
 #include <kvm.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+#include <evtr.h>
 #include <stdarg.h>
 
-#define SBUFLEN                256
-#define SBUFMASK       (SBUFLEN - 1)
-
 struct ktr_buffer {
        struct ktr_entry *ents;
        int modified;
@@ -82,7 +81,15 @@ static struct nlist nl_version_ktr_cpu[] = {
        { .n_name = NULL }
 };
 
+struct save_ctx {
+       char save_buf[512];
+       const void *save_kptr;
+};
+
+typedef void (*ktr_iter_cb_t)(void *, int, int, struct ktr_entry *, uint64_t *);
+
 static int cflag;
+static int dflag;
 static int fflag;
 static int iflag;
 static int lflag;
@@ -110,18 +117,21 @@ static int ktr_version;
 
 static void usage(void);
 static int earliest_ts(struct ktr_buffer *);
+static void dump_machine_info(evtr_t);
 static void print_header(FILE *, int);
 static void print_entry(FILE *, int, int, struct ktr_entry *, u_int64_t *);
-static struct ktr_info *kvm_ktrinfo(void *);
-static const char *kvm_string(char *buf, const char *);
+static void print_callback(void *, int, int, struct ktr_entry *, uint64_t *);
+static void dump_callback(void *, int, int, struct ktr_entry *, uint64_t *);
+static struct ktr_info *kvm_ktrinfo(void *, struct save_ctx *);
+static const char *kvm_string(const char *, struct save_ctx *);
 static const char *trunc_path(const char *, int);
 static void read_symbols(const char *);
-static const char *address_to_symbol(void *);
+static const char *address_to_symbol(void *, struct save_ctx *);
 static struct ktr_buffer *ktr_bufs_init(void);
 static void get_indices(struct ktr_entry **, int *);
 static void load_bufs(struct ktr_buffer *, struct ktr_entry **, int *);
-static void print_buf(FILE *, struct ktr_buffer *, int, u_int64_t *);
-static void print_bufs_timesorted(FILE *, struct ktr_buffer *, u_int64_t *);
+static void iterate_buf(FILE *, struct ktr_buffer *, int, u_int64_t *, ktr_iter_cb_t);
+static void iterate_bufs_timesorted(FILE *, struct ktr_buffer *, u_int64_t *, ktr_iter_cb_t);
 static void kvmfprintf(FILE *fp, const char *ctl, va_list va);
 
 /*
@@ -132,8 +142,10 @@ main(int ac, char **av)
 {
        struct ktr_buffer *ktr_bufs;
        struct ktr_entry **ktr_kbuf;
+       ktr_iter_cb_t callback = &print_callback;
        int *ktr_idx;
        FILE *fo;
+       void *ctx;
        int64_t tts;
        int *ktr_start_index;
        int c;
@@ -143,7 +155,7 @@ main(int ac, char **av)
         * Parse commandline arguments.
         */
        fo = stdout;
-       while ((c = getopt(ac, av, "acfinqrtxpslA:N:M:o:")) != -1) {
+       while ((c = getopt(ac, av, "acfinqrtxpslA:N:M:o:d")) != -1) {
                switch (c) {
                case 'a':
                        cflag = 1;
@@ -157,6 +169,10 @@ main(int ac, char **av)
                case 'c':
                        cflag = 1;
                        break;
+               case 'd':
+                       dflag = 1;
+                       callback = &dump_callback;
+                       break;
                case 'N':
                        if (strlcpy(execfile, optarg, sizeof(execfile))
                            >= sizeof(execfile))
@@ -211,6 +227,13 @@ main(int ac, char **av)
                        usage();
                }
        }
+       ctx = fo;
+       if (dflag) {
+               ctx = evtr_open_write(fo);
+               if (!ctx) {
+                       err(1, "Can't create event stream");
+               }
+       }
        if (cflag + iflag + tflag + xflag + fflag + pflag == 0) {
                cflag = 1;
                iflag = 1;
@@ -254,6 +277,9 @@ main(int ac, char **av)
 
        printf("TSC frequency is %6.3f MHz\n", tsc_frequency / 1000000.0);
 
+       if (dflag) {
+               dump_machine_info((evtr_t)ctx);
+       }
        ktr_kbuf = calloc(ncpus, sizeof(*ktr_kbuf));
        ktr_idx = calloc(ncpus, sizeof(*ktr_idx));
 
@@ -276,7 +302,8 @@ main(int ac, char **av)
                u_int64_t last_timestamp = 0;
                do {
                        load_bufs(ktr_bufs, ktr_kbuf, ktr_idx);
-                       print_bufs_timesorted(fo, ktr_bufs, &last_timestamp);
+                       iterate_bufs_timesorted(ctx, ktr_bufs, &last_timestamp,
+                                               callback);
                        if (lflag)
                                usleep(1000000 / 10);
                } while (lflag);
@@ -285,14 +312,29 @@ main(int ac, char **av)
                do {
                        load_bufs(ktr_bufs, ktr_kbuf, ktr_idx);
                        for (n = 0; n < ncpus; ++n)
-                               print_buf(fo, ktr_bufs, n, &last_timestamp[n]);
+                               iterate_buf(ctx, ktr_bufs, n, &last_timestamp[n],
+                                       callback);
                        if (lflag)
                                usleep(1000000 / 10);
                } while (lflag);
        }
+       if (dflag)
+               evtr_close(ctx);
        return (0);
 }
 
+static
+void
+dump_machine_info(evtr_t evtr)
+{
+       struct evtr_event ev;
+
+       ev.type = EVTR_TYPE_CPUINFO;
+       ev.ncpus = ncpus;
+
+       evtr_dump_event(evtr, &ev);
+}
+
 static void
 print_header(FILE *fo, int row)
 {
@@ -323,7 +365,7 @@ print_entry(FILE *fo, int n, int row, struct ktr_entry *entry,
            u_int64_t *last_timestamp)
 {
        struct ktr_info *info = NULL;
-       char buf[SBUFLEN];
+       static struct save_ctx nctx, pctx, fmtctx, symctx, infoctx;
 
        fprintf(fo, " %06x ", row & 0x00FFFFFF);
        if (cflag)
@@ -345,72 +387,235 @@ print_entry(FILE *fo, int n, int row, struct ktr_entry *entry,
                    fprintf(fo, "%p %p ", 
                            entry->ktr_caller2, entry->ktr_caller1);
                } else {
-                   fprintf(fo, "%-25s ",
-                           address_to_symbol(entry->ktr_caller2));
-                   fprintf(fo, "%-25s ",
-                           address_to_symbol(entry->ktr_caller1));
+                   fprintf(fo, "%-25s ", 
+                           address_to_symbol(entry->ktr_caller2, &symctx));
+                   fprintf(fo, "%-25s ", 
+                           address_to_symbol(entry->ktr_caller1, &symctx));
                }
        }
        if (iflag) {
-               info = kvm_ktrinfo(entry->ktr_info);
+               info = kvm_ktrinfo(entry->ktr_info, &infoctx);
                if (info)
-                       fprintf(fo, "%-20s ", kvm_string(buf, info->kf_name));
+                       fprintf(fo, "%-20s ", kvm_string(info->kf_name, &nctx));
                else
                        fprintf(fo, "%-20s ", "<empty>");
        }
        if (fflag)
-               fprintf(fo, "%34s:%-4d ", trunc_path(kvm_string(buf, entry->ktr_file), 34), entry->ktr_line);
+               fprintf(fo, "%34s:%-4d ",
+                       trunc_path(kvm_string(entry->ktr_file, &pctx), 34),
+                       entry->ktr_line);
        if (pflag) {
                if (info == NULL)
-                       info = kvm_ktrinfo(entry->ktr_info);
+                       info = kvm_ktrinfo(entry->ktr_info, &infoctx);
                if (info)
-                       kvmfprintf(fo, kvm_string(buf, info->kf_format), (void *)&entry->ktr_data);
+                       kvmfprintf(fo, kvm_string(info->kf_format, &fmtctx),
+                                (void *)&entry->ktr_data);
        }
        fprintf(fo, "\n");
        *last_timestamp = entry->ktr_timestamp;
 }
 
 static
+void
+print_callback(void *ctx, int n, int row, struct ktr_entry *entry, uint64_t *last_ts)
+{
+       FILE *fo = (FILE *)ctx;
+       print_header(fo, row);
+       print_entry(fo, n, row, entry, last_ts);
+}
+
+/*
+ * If free == 0, replace all (kvm) string pointers in fmtdata with pointers
+ * to user-allocated copies of the strings.
+ * If free != 0, free those pointers.
+ */
+static
+int
+mangle_string_ptrs(const char *fmt, uint8_t *fmtdata, int dofree)
+{
+       const char *f, *p;
+       size_t skipsize, intsz;
+       static struct save_ctx strctx;
+       int ret = 0;
+
+       for (f = fmt; f[0] != '\0'; ++f) {
+               if (f[0] != '%')
+                       continue;
+               ++f;
+               skipsize = 0;
+               for (p = f; p[0]; ++p) {
+                       int again = 0;
+                       /*
+                        * Eat flags. Notice this will accept duplicate
+                        * flags.
+                        */
+                       switch (p[0]) {
+                       case '#':
+                       case '0':
+                       case '-':
+                       case ' ':
+                       case '+':
+                       case '\'':
+                               again = !0;
+                               break;
+                       }
+                       if (!again)
+                               break;
+               }
+               /* Eat minimum field width, if any */
+               for (; isdigit(p[0]); ++p)
+                       ;
+               if (p[0] == '.')
+                       ++p;
+               /* Eat precision, if any */
+               for (; isdigit(p[0]); ++p)
+                       ;
+               intsz = 0;
+               switch (p[0]) {
+               case 'l':
+                       if (p[1] == 'l') {
+                               ++p;
+                               intsz = sizeof(long long);
+                       } else {
+                               intsz = sizeof(long);
+                       }
+                       break;
+               case 'j':
+                       intsz = sizeof(intmax_t);
+                       break;
+               case 't':
+                       intsz = sizeof(ptrdiff_t);
+                       break;
+               case 'z':
+                       intsz = sizeof(size_t);
+                       break;
+               default:
+                       break;
+               }
+               if (intsz != 0)
+                       ++p;
+               else
+                       intsz = sizeof(int);
+
+               switch (p[0]) {
+               case 'd':
+               case 'i':
+               case 'o':
+               case 'u':
+               case 'x':
+               case 'X':
+               case 'c':
+                       skipsize = intsz;
+                       break;
+               case 'p':
+                       skipsize = sizeof(void *);
+                       break;
+               case 'f':
+                       if (p[-1] == 'l')
+                               skipsize = sizeof(double);
+                       else
+                               skipsize = sizeof(float);
+                       break;
+               case 's':
+                       if (dofree) {
+                         char *t = ((char **)fmtdata)[0];
+                         free(t);
+                         skipsize = sizeof(char *);
+                       } else {
+                         char *t = strdup(kvm_string(((char **)fmtdata)[0],
+                                                         &strctx));
+                         ((const char **)fmtdata)[0] = t;
+                                       
+                               skipsize = sizeof(char *);
+                       }
+                       ++ret;
+                       break;
+               default:
+                       fprintf(stderr, "Unknown conversion specifier %c "
+                               "in fmt starting with %s", p[0], f - 1);
+                       return -1;
+               }
+               fmtdata += skipsize;
+       }
+       return ret;
+}
+
+static
+void
+dump_callback(void *ctx, int n, int row __unused, struct ktr_entry *entry,
+             uint64_t *last_ts __unused)
+{
+       evtr_t evtr = (evtr_t)ctx;
+       struct evtr_event ev;
+       static struct save_ctx pctx, fmtctx, infoctx;
+       struct ktr_info *ki;
+       int conv = 0;   /* pointless */
+
+       ev.ts = entry->ktr_timestamp;
+       ev.type = EVTR_TYPE_PROBE;
+       ev.line = entry->ktr_line;
+       ev.file = kvm_string(entry->ktr_file, &pctx);
+       ev.func = NULL;
+       ev.cpu = n;
+       if ((ki = kvm_ktrinfo(entry->ktr_info, &infoctx))) {
+               ev.fmt = kvm_string(ki->kf_format, &fmtctx);
+               ev.fmtdata = entry->ktr_data;
+               if ((conv = mangle_string_ptrs(ev.fmt,
+                                              __DECONST(uint8_t *, ev.fmtdata),
+                                              0)) < 0)
+                       errx(1, "Can't parse format string\n");
+               ev.fmtdatalen = ki->kf_data_size;
+       } else {
+               ev.fmt = ev.fmtdata = NULL;
+               ev.fmtdatalen = 0;
+       }
+       if (evtr_dump_event(evtr, &ev)) {
+               err(1, evtr_errmsg(evtr));
+       }
+       if (ev.fmtdata && conv) {
+               mangle_string_ptrs(ev.fmt, __DECONST(uint8_t *, ev.fmtdata),
+                                  !0);
+       }
+}
+
+static
 struct ktr_info *
-kvm_ktrinfo(void *kptr)
+kvm_ktrinfo(void *kptr, struct save_ctx *ctx)
 {
-       static struct ktr_info save_info;
-       static void *save_kptr;
+       struct ktr_info *ki = (void *)ctx->save_buf;
 
        if (kptr == NULL)
                return(NULL);
-       if (save_kptr != kptr) {
-               if (kvm_read(kd, (uintptr_t)kptr, &save_info, sizeof(save_info)) == -1) {
-                       bzero(&save_info, sizeof(save_info));
+       if (ctx->save_kptr != kptr) {
+               if (kvm_read(kd, (uintptr_t)kptr, ki, sizeof(*ki)) == -1) {
+                       bzero(&ki, sizeof(*ki));
                } else {
-                       save_kptr = kptr;
+                       ctx->save_kptr = kptr;
                }
        }
-       return(&save_info);
+       return(ki);
 }
 
 static
 const char *
-kvm_string(char *save_str, const char *kptr)
+kvm_string(const char *kptr, struct save_ctx *ctx)
 {
-       static const char *save_kptr;
        u_int l;
        u_int n;
 
        if (kptr == NULL)
                return("?");
-       if (save_kptr != kptr) {
-               save_kptr = kptr;
+       if (ctx->save_kptr != (const void *)kptr) {
+               ctx->save_kptr = (const void *)kptr;
                l = 0;
-               while (l < SBUFLEN - 1) {
-                       n = SBUFLEN -
-                           ((intptr_t)(kptr + l) & SBUFMASK);
-                       if (n > SBUFLEN - l - 1)
-                               n = SBUFLEN - l - 1;
-                       if (kvm_read(kd, (uintptr_t)(kptr + l), save_str + l, n) < 0)
+               while (l < sizeof(ctx->save_buf) - 1) {
+                       n = 256 - ((intptr_t)(kptr + l) & 255);
+                       if (n > sizeof(ctx->save_buf) - l - 1)
+                               n = sizeof(ctx->save_buf) - l - 1;
+                       if (kvm_read(kd, (uintptr_t)(kptr + l), ctx->save_buf + l, n) < 0)
                                break;
-                       while (l < SBUFLEN && n) {
-                           if (save_str[l] == 0)
+                       while (l < sizeof(ctx->save_buf) && n) {
+                           if (ctx->save_buf[l] == 0)
                                    break;
                            --n;
                            ++l;
@@ -418,9 +623,9 @@ kvm_string(char *save_str, const char *kptr)
                        if (n)
                            break;
                }
-               save_str[l] = 0;
+               ctx->save_buf[l] = 0;
        }
-       return(save_str);
+       return(ctx->save_buf);
 }
 
 static
@@ -493,14 +698,15 @@ read_symbols(const char *file)
 
 static
 const char *
-address_to_symbol(void *kptr)
+address_to_symbol(void *kptr, struct save_ctx *ctx)
 {
-       static char buf[64];
+       char *buf = ctx->save_buf;
+       int size = sizeof(ctx->save_buf);
 
        if (symcache == NULL ||
           (char *)kptr < symbegin || (char *)kptr >= symend
        ) {
-               snprintf(buf, sizeof(buf), "%p", kptr);
+               snprintf(buf, size, "%p", kptr);
                return(buf);
        }
        while ((char *)symcache->symaddr < (char *)kptr) {
@@ -512,7 +718,7 @@ address_to_symbol(void *kptr)
                if (symcache != TAILQ_FIRST(&symlist))
                        symcache = TAILQ_PREV(symcache, symlist, link);
        }
-       snprintf(buf, sizeof(buf), "%s+%d", symcache->symname,
+       snprintf(buf, size, "%s+%d", symcache->symname,
                (int)((char *)kptr - symcache->symaddr));
        return(buf);
 }
@@ -641,8 +847,8 @@ earliest_ts(struct ktr_buffer *buf)
 
 static
 void
-print_buf(FILE *fo, struct ktr_buffer *ktr_bufs, int cpu,
-         u_int64_t *last_timestamp)
+iterate_buf(FILE *fo, struct ktr_buffer *ktr_bufs, int cpu,
+           u_int64_t *last_timestamp, ktr_iter_cb_t cb)
 {
        struct ktr_buffer *buf = ktr_bufs + cpu;
 
@@ -653,10 +859,9 @@ print_buf(FILE *fo, struct ktr_buffer *ktr_bufs, int cpu,
                        buf->ents[buf->beg_idx & fifo_mask].ktr_timestamp;
        }
        while (buf->beg_idx != buf->end_idx) {
-               print_header(fo, buf->beg_idx);
-               print_entry(fo, cpu, buf->beg_idx,
-                           &buf->ents[buf->beg_idx & fifo_mask],
-                           last_timestamp);
+               cb(fo, cpu, buf->beg_idx,
+                  &buf->ents[buf->beg_idx & fifo_mask],
+                  last_timestamp);
                ++buf->beg_idx;
        }
        buf->modified = 0;
@@ -664,8 +869,8 @@ print_buf(FILE *fo, struct ktr_buffer *ktr_bufs, int cpu,
 
 static
 void
-print_bufs_timesorted(FILE *fo, struct ktr_buffer *ktr_bufs,
-                     u_int64_t *last_timestamp)
+iterate_bufs_timesorted(FILE *fo, struct ktr_buffer *ktr_bufs,
+                       u_int64_t *last_timestamp, ktr_iter_cb_t cb)
 {
        struct ktr_entry *ent;
        struct ktr_buffer *buf;
@@ -689,10 +894,9 @@ print_bufs_timesorted(FILE *fo, struct ktr_buffer *ktr_bufs,
                if ((bestn < 0) || (ts < *last_timestamp))
                        break;
                buf = ktr_bufs + bestn;
-               print_header(fo, row);
-               print_entry(fo, bestn, row,
-                           &buf->ents[buf->beg_idx & fifo_mask],
-                           last_timestamp);
+               cb(fo, bestn, row,
+                  &buf->ents[buf->beg_idx & fifo_mask],
+                  last_timestamp);
                ++buf->beg_idx;
                *last_timestamp = ts;
                ++row;
@@ -707,7 +911,8 @@ kvmfprintf(FILE *fp, const char *ctl, va_list va)
        int is_long;
        int is_done;
        char fmt[256];
-       char buf[256];
+       static struct save_ctx strctx;
+       const char *s;
 
        while (*ctl) {
                for (n = 0; ctl[n]; ++n) {
@@ -760,8 +965,8 @@ kvmfprintf(FILE *fp, const char *ctl, va_list va)
                                        /*
                                         * String
                                         */
-                                       kvm_string(buf, va_arg(va, char *));
-                                       fwrite(buf, 1, strlen(buf), fp);
+                                       s = kvm_string(va_arg(va, char *), &strctx);
+                                       fwrite(s, 1, strlen(s), fp);
                                        ++n;
                                        is_done = 1;
                                        break;