- 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.
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}
--- /dev/null
+LIB=evtr
+SHLIB_MAJOR=1
+
+SRCS=evtr.c
+INCS=evtr.h
+
+CFLAGS+= -W -Wall
+
+.include <bsd.lib.mk>
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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 */
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");
__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
td->td_kstack = NULL;
td->td_kstack_size = 0;
}
+ KTR_LOG(ctxsw_deadtd, td);
}
ee \
enigma \
env \
+ evtranalyze \
expand \
false \
fetch \
--- /dev/null
+PROG= evtranalyze
+
+SRCS= xml.c svg.c evtranalyze.c
+DPADD= ${LIBEVTR}
+LDADD= -levtr -lm
+.include <bsd.prog.mk>
--- /dev/null
+.\"-
+.\" 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 .
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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);
+}
+
--- /dev/null
+/*
+ * 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_ */
# $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>
.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
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
#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;
{ .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;
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);
/*
{
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;
* 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;
case 'c':
cflag = 1;
break;
+ case 'd':
+ dflag = 1;
+ callback = &dump_callback;
+ break;
case 'N':
if (strlcpy(execfile, optarg, sizeof(execfile))
>= sizeof(execfile))
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;
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));
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);
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)
{
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)
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;
if (n)
break;
}
- save_str[l] = 0;
+ ctx->save_buf[l] = 0;
}
- return(save_str);
+ return(ctx->save_buf);
}
static
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) {
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);
}
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;
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;
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;
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;
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) {
/*
* 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;