diff --git a/lib/Makefile b/lib/Makefile index d200ab9..8a4c6f7 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -28,7 +28,7 @@ SUBDIR= csu \ libpcap libposix1e libsdp libthread_xu libpthread librpcsvc ${_libsm} \ ${_libsmb} ${_libsmdb} ${_libsmutil} libstand libtelnet libusbhid \ ${_libvgl} libwrap libxpg4 liby libypclnt libz i18n_module pam_module \ - libc_rtld libsctp + libc_rtld libsctp libevtr .if exists(${.CURDIR}/compat/${MACHINE_ARCH}/Makefile) SUBDIR+= compat/${MACHINE_ARCH} diff --git a/lib/libevtr/Makefile b/lib/libevtr/Makefile new file mode 100644 index 0000000..8dfcdf7 --- /dev/null +++ b/lib/libevtr/Makefile @@ -0,0 +1,9 @@ +LIB=evtr +SHLIB_MAJOR=1 + +SRCS=evtr.c +INCS=evtr.h + +CFLAGS+= -W -Wall + +.include diff --git a/lib/libevtr/evtr.c b/lib/libevtr/evtr.c new file mode 100644 index 0000000..62b70b1 --- /dev/null +++ b/lib/libevtr/evtr.c @@ -0,0 +1,1430 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "evtr.h" + +#define printd(...) + +enum { + MAX_EVHDR_SIZE = PATH_MAX + 200, + /* string namespaces */ + EVTR_NS_PATH = 0x1, + EVTR_NS_FUNC, + 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 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); + +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; \ + } \ + /* 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, "tdnew %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 = "tdnew %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; +} + +/* 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; + } + + 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; +} + +static +int +evtr_dump_probe(evtr_t evtr, evtr_event_t ev) +{ + struct probe_event_header kev; + + 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) + kev.datalen = ev->fmtdatalen; + 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, ev->fmtdata, 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; ++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; + } + 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; + } + if (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; + } + evtr->err = string_map_insert(&evtr->maps[evh->ns].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].root, evh->file); + if (!ev->file) { + evtr->errmsg = "unknown id for file path"; + evtr->err = !0; + ev->file = ""; + } + } else { + ev->file = ""; + } + 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; + } + } + if (evh->datalen) { + if (evtr_query_reserve_buf(q, evh->datalen + 1)) { + evtr->err = ENOMEM; + } else if (!evtr_read(evtr, q->buf, evh->datalen)) { + ev->fmtdata = q->buf; + ((char *)ev->fmtdata)[evh->datalen] = '\0'; + ev->fmtdatalen = evh->datalen; + } + } + evtr_run_callbacks(ev, evtr); + /* we can't filter before running the callbacks */ + if (!evtr_match_filters(q, evh)) { + return -1; /* no match */ + } + + return evtr->err; +} + +static +int +evtr_skip_to_record(evtr_t evtr) +{ + int skip; + + skip = REC_ALIGN - (evtr->bytes % REC_ALIGN); + if (skip > 0) { + if (fseek(evtr->f, skip, SEEK_CUR)) { + evtr->err = errno; + evtr->errmsg = strerror(errno); + return !0; + } + evtr->bytes += skip; + } + return 0; +} + +static +int +evtr_load_cpuinfo(evtr_t evtr) +{ + uint16_t ncpus; + int i; + + if (evtr_read(evtr, &ncpus, sizeof(ncpus))) { + return !0; + } + if (evtr->cpus) + return 0; + evtr->cpus = malloc(ncpus * sizeof(struct cpu)); + if (!evtr->cpus) { + evtr->err = ENOMEM; + return !0; + } + evtr->ncpus = ncpus; + for (i = 0; i < ncpus; ++i) { + evtr->cpus[i].td = NULL; + } + return 0; +} + +static +int +_evtr_next_event(evtr_t evtr, evtr_event_t ev, struct evtr_query *q) +{ + char buf[MAX_EVHDR_SIZE]; + int ret, err, ntried, nmatched; + struct trace_event_header *evhdr = (struct trace_event_header *)buf; + + for (ret = 0; !ret;) { + /* + * skip pad records -- this will only happen if there's a + * variable sized record close to the boundary + */ + if (evtr_read(evtr, &evhdr->type, 1)) + return feof(evtr->f) ? -1 : !0; + if (evhdr->type == EVTR_TYPE_PAD) { + evtr_skip_to_record(evtr); + continue; + } + if (evhdr->type == EVTR_TYPE_CPUINFO) { + evtr_load_cpuinfo(evtr); + continue; + } + if (evtr_read(evtr, buf + 1, sizeof(*evhdr) - 1)) + return feof(evtr->f) ? -1 : !0; + switch (evhdr->type) { + case EVTR_TYPE_PROBE: + ntried = q->ntried; + nmatched = q->nmatched; + if ((err = evtr_load_probe(evtr, ev, buf, q))) { + if (err == -1) { + /* no match */ + ret = 0; + } else { + return !0; + } + } else { + ret = !0; + } + break; + case EVTR_TYPE_STR: + if (evtr_load_string(evtr, buf)) { + return !0; + } + break; + case EVTR_TYPE_FMT: + if (evtr_load_fmt(evtr, buf)) { + return !0; + } + break; + default: + evtr->err = !0; + evtr->errmsg = "unknown event type (corrupt input?)"; + return !0; + } + evtr_skip_to_record(evtr); + if (ret) { + q->off = evtr->bytes; + return 0; + } + } + /* can't get here */ + return !0; +} + +int +evtr_next_event(evtr_t evtr, evtr_event_t ev) +{ + struct evtr_query *q; + int ret; + + if (!(q = evtr_query_init(evtr, NULL, 0))) { + evtr->err = ENOMEM; + return !0; + } + ret = _evtr_next_event(evtr, ev, q); + evtr_query_destroy(q); + return ret; +} + +int +evtr_last_event(evtr_t evtr, evtr_event_t ev) +{ + struct stat st; + int fd; + off_t last_boundary; + + fd = fileno(evtr->f); + if (fstat(fd, &st)) + return !0; + /* + * This skips pseudo records, so we can't provide + * an event with all fields filled in this way. + * It's doable, just needs some care. TBD. + */ + if (0 && (st.st_mode & S_IFREG)) { + /* + * Skip to last boundary, that's the closest to the EOF + * location that we are sure contains a header so we can + * pick up the stream. + */ + last_boundary = (st.st_size / REC_BOUNDARY) * REC_BOUNDARY; + /* XXX: ->bytes should be in query */ + assert(evtr->bytes == 0); + evtr_skip(evtr, last_boundary); + } + + + /* + * If we can't seek, we need to go through the whole file. + * Since you can't seek back, this is pretty useless unless + * you really are interested only in the last event. + */ + while (!evtr_next_event(evtr, ev)) + ; + if (evtr_error(evtr)) + return !0; + evtr_rewind(evtr); + return 0; +} + +struct evtr_query * +evtr_query_init(evtr_t evtr, evtr_filter_t filt, int nfilt) +{ + struct evtr_query *q; + int i; + + if (!(q = malloc(sizeof(*q)))) { + return q; + } + q->bufsize = 2; + if (!(q->buf = malloc(q->bufsize))) { + goto free_q; + } + q->evtr = evtr; + q->off = 0; + q->filt = filt; + q->nfilt = nfilt; + q->nmatched = 0; + for (i = 0; i < nfilt; ++i) { + filt[i].flags = 0; + if (filt[i].fmt == NULL) + continue; + if (evtr_filter_register(evtr, &filt[i])) { + evtr_deregister_filters(evtr, filt, i); + goto free_buf; + } + } + + return q; +free_buf: + free(q->buf); +free_q: + free(q); + return NULL; +} + +void +evtr_query_destroy(struct evtr_query *q) +{ + evtr_deregister_filters(q->evtr, q->filt, q->nfilt); + free(q->buf); + free(q); +} + +int +evtr_query_next(struct evtr_query *q, evtr_event_t ev) +{ + /* we may support that in the future */ + if (q->off != q->evtr->bytes) + return !0; + return _evtr_next_event(q->evtr, ev, q); +} + +int +evtr_ncpus(evtr_t evtr) +{ + return evtr->ncpus; +} diff --git a/lib/libevtr/evtr.h b/lib/libevtr/evtr.h new file mode 100644 index 0000000..af3c8cf --- /dev/null +++ b/lib/libevtr/evtr.h @@ -0,0 +1,122 @@ +#ifndef EVTR_H +#define EVTR_H + +#include +#include +/* XXX: remove */ +#include + +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); + + +#endif /* EVTR_H */ diff --git a/usr.bin/Makefile b/usr.bin/Makefile index c19ca50..e37106c 100644 --- a/usr.bin/Makefile +++ b/usr.bin/Makefile @@ -50,6 +50,7 @@ SUBDIR= alias \ ee \ enigma \ env \ + evtranalyze \ expand \ false \ fetch \ diff --git a/usr.bin/evtranalyze/Makefile b/usr.bin/evtranalyze/Makefile new file mode 100644 index 0000000..3099607 --- /dev/null +++ b/usr.bin/evtranalyze/Makefile @@ -0,0 +1,6 @@ +PROG= evtranalyze + +SRCS= xml.c svg.c evtranalyze.c +DPADD= ${LIBEVTR} +LDADD= -levtr -lm +.include diff --git a/usr.bin/evtranalyze/evtranalyze.1 b/usr.bin/evtranalyze/evtranalyze.1 new file mode 100644 index 0000000..6806391 --- /dev/null +++ b/usr.bin/evtranalyze/evtranalyze.1 @@ -0,0 +1,54 @@ +.\"- +.\" 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 September 28, 2009 +.Dt EVTRANALYZE 8 +.Os +.Sh NAME +.Nm evtranalyze +.Nd analyze a trace stream +.Sh SYNOPSIS +.Nm +.Op Fl f Ar infile +.Sh DESCRIPTION +The +.Nm +utility is used to analyze an event trace stream. +.Sh SEE ALSO +.Xr ktrdump 8 , +.Sh HISTORY +The +.Nm +utility first appeared in +.Dx 2.5.0 . +.Sh AUTHORS +.An -nosplit +The +.Nm +utility was implemented by +.An Aggelos Economopoulos Aq aggelos@dragonflybsd.org . +for +.Dx . diff --git a/usr.bin/evtranalyze/evtranalyze.c b/usr.bin/evtranalyze/evtranalyze.c new file mode 100644 index 0000000..3ac84d0 --- /dev/null +++ b/usr.bin/evtranalyze/evtranalyze.c @@ -0,0 +1,672 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include "xml.h" +#include "svg.h" + +#define printd printf + +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 +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, fs, 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 = 800.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("timestamp = %llu, cpu = %d, file = %s, " + "line = %d, tdcomm =\"%s\", fmt=\"%s\"", ev.ts, ev.cpu, + ev.file, ev.line, ev.td ? ev.td->comm : "n/a", + ev.fmt ? ev.fmt : "no fmt"); + if (ev.fmt) { + evtr_event_data(&ev, buf, sizeof(buf)); + printf(" data=\"%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:")) != -1) { + switch (ch) { + case 'f': + opt_infile = optarg; + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (argc == 0) { + err(2, "need to specify a command\n"); + } + if (!opt_infile || !strcmp(opt_infile, "-")) { + inf = stdin; + } else { + inf = fopen(opt_infile, "r"); + if (!inf) { + err(2, "Can't open input file\n"); + } + } + + if (!(evtr = evtr_open_read(inf))) { + err(1, "Can't open evtr stream\n"); + } + + + for (cmd = commands; cmd->name != NULL; ++cmd) { + if (strcmp(argv[0], cmd->name)) + continue; + cmd->func(argc, argv); + break; + } + if (!cmd->name) { + err(2, "no such command: %s\n", argv[0]); + } + + evtr_close(evtr); + return 0; +} diff --git a/usr.bin/evtranalyze/svg.c b/usr.bin/evtranalyze/svg.c new file mode 100644 index 0000000..b625394 --- /dev/null +++ b/usr.bin/evtranalyze/svg.c @@ -0,0 +1,314 @@ +#include +#include +#include +#include + +#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[] = + ""; + +static struct svg_transform noop_transform = { + .tx = 0.0, + .ty = 0.0, + .sx = 1.0, + .sy = 1.0, + .rot = 0.0, +}; + +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 + * element at this point and then it in the rest + * of the document. + */ +struct svg_rect * +svg_rect_new(const char *cl) +{ + struct svg_rect *r; + + if (!(r = malloc(sizeof(*r)))) + return r; + svg_rect_init(r, cl); + return r; +} + + +int +svg_rect_draw(svg_document_t doc, struct svg_rect *rect, double x, + double y, double w, double h) +{ + snprintf(&rect->x_val[0], sizeof(rect->x_val), "%.20lf", x); + xml_attribute_set_value(&rect->x, &rect->x_val[0]); + snprintf(&rect->y_val[0], sizeof(rect->y_val), "%lf", y); + xml_attribute_set_value(&rect->y, &rect->y_val[0]); + snprintf(&rect->w_val[0], sizeof(rect->w_val), "%.20lf", w); + xml_attribute_set_value(&rect->w, &rect->w_val[0]); + snprintf(&rect->h_val[0], sizeof(rect->h_val), "%lf", h); + xml_attribute_set_value(&rect->h, &rect->h_val[0]); + + xml_elem_closed(doc->xml, &rect->el); + return 0; +} + +static +void +svg_text_init(struct svg_text *text, const char *cl) +{ + xml_elem_init(&text->el, "text"); +#if remove + xml_attribute_init(&text->x, "x", NULL); + xml_elem_set_attribute(&text->el, &text->x); + xml_attribute_init(&text->y, "y", NULL); + xml_elem_set_attribute(&text->el, &text->y); +#endif + xml_attribute_init(&text->fontsize, "font-size", NULL); + xml_elem_set_attribute(&text->el, &text->fontsize); + xml_attribute_init(&text->transform, "transform", NULL); + xml_elem_set_attribute(&text->el, &text->transform); + + if (cl) { + xml_attribute_init(&text->cl, "class", cl); + xml_elem_set_attribute(&text->el, &text->cl); + } + +} + +struct svg_text * +svg_text_new(const char *cl) +{ + svg_text_t text; + + if (!(text = malloc(sizeof(*text)))) + return text; + svg_text_init(text, cl); + return text; +} + +int +svg_text_draw(svg_document_t doc, svg_text_t text, svg_transform_t tf, + const char *str, double fontsize) +{ +#if remove + snprintf(&text->x_val[0], sizeof(text->x_val), "%.20lf", x); + xml_attribute_set_value(&text->x, &text->x_val[0]); + snprintf(&text->y_val[0], sizeof(text->y_val), "%.20lf", y); + xml_attribute_set_value(&text->y, &text->y_val[0]); +#endif + snprintf(&text->fontsize_val[0], sizeof(text->fontsize_val), "%.20lf", + fontsize); + xml_attribute_set_value(&text->fontsize, &text->fontsize_val[0]); + if (svg_transform_print(tf, &text->transform_val[0], + sizeof(text->transform_val))) + return !0; + xml_attribute_set_value(&text->transform, &text->transform_val[0]); + xml_elem_set_value(&text->el, str); + + xml_elem_closed(doc->xml, &text->el); + return 0; +} + +static +void +svg_line_init(struct svg_line *line, const char *cl) +{ + xml_elem_init(&line->el, "line"); + xml_attribute_init(&line->x1, "x1", NULL); + xml_elem_set_attribute(&line->el, &line->x1); + xml_attribute_init(&line->x2, "x2", NULL); + xml_elem_set_attribute(&line->el, &line->x2); + xml_attribute_init(&line->y1, "y1", NULL); + xml_elem_set_attribute(&line->el, &line->y1); + xml_attribute_init(&line->y2, "y2", NULL); + xml_elem_set_attribute(&line->el, &line->y2); + + xml_attribute_init(&line->transform, "transform", NULL); + xml_elem_set_attribute(&line->el, &line->transform); + + if (cl) { + xml_attribute_init(&line->cl, "class", cl); + xml_elem_set_attribute(&line->el, &line->cl); + } + +} + +struct svg_line * +svg_line_new(const char *cl) +{ + svg_line_t line; + + if (!(line = malloc(sizeof(*line)))) + return line; + svg_line_init(line, cl); + return line; +} + +int +svg_line_draw(svg_document_t doc, svg_line_t line, double x1, double _y1, + double x2, double y2, svg_transform_t tf) +{ + snprintf(&line->x1_val[0], sizeof(line->x1_val), "%.20lf", x1); + xml_attribute_set_value(&line->x1, &line->x1_val[0]); + + snprintf(&line->x2_val[0], sizeof(line->x2_val), "%.20lf", x2); + xml_attribute_set_value(&line->x2, &line->x2_val[0]); + + snprintf(&line->y1_val[0], sizeof(line->y1_val), "%.10lf", _y1); + xml_attribute_set_value(&line->y1, &line->y1_val[0]); + + snprintf(&line->y2_val[0], sizeof(line->y2_val), "%.20lf", y2); + xml_attribute_set_value(&line->y2, &line->y2_val[0]); + + xml_attribute_set_value(&line->transform, &line->transform_val[0]); + if (svg_transform_print(tf, + &line->transform_val[0], + sizeof(line->transform_val))) + return !0; + xml_elem_closed(doc->xml, &line->el); + return 0; +} + +svg_document_t +svg_document_create(const char *path) +{ + svg_document_t svg; + struct xml_element style, defs; + struct xml_attribute type; + + if (!(svg = malloc(sizeof(*svg)))) + return NULL; + if (!(svg->xml = xml_document_create(path))) { + free(svg); + return NULL; + } + svg->css = &default_css[0]; + xml_attribute_init(&type, "type", "text/css"); + xml_elem_init(&defs, "defs"); + xml_elem_init(&style, "style"); + xml_elem_set_attribute(&style, &type); + xml_elem_init(&svg->svg, "svg"); + xml_attribute_init(&svg->svg_attrs[0], "version", "1.1"); + xml_elem_set_attribute(&svg->svg, &svg->svg_attrs[0]); + xml_attribute_init(&svg->svg_attrs[1], "xmlns", + "http://www.w3.org/2000/svg"); + xml_elem_set_attribute(&svg->svg, &svg->svg_attrs[1]); + xml_elem_begin(svg->xml, &svg->svg); + xml_elem_begin(svg->xml, &defs); + xml_elem_set_value(&style, svg->css); + xml_elem_closed(svg->xml, &style); + xml_elem_close(svg->xml, &defs); + + return svg; +} + +int +svg_document_close(svg_document_t svg) +{ + xml_elem_close(svg->xml, &svg->svg); + xml_document_close(svg->xml); + return 0; +} diff --git a/usr.bin/evtranalyze/svg.h b/usr.bin/evtranalyze/svg.h new file mode 100644 index 0000000..eeec4be --- /dev/null +++ b/usr.bin/evtranalyze/svg.h @@ -0,0 +1,33 @@ +#ifndef SVG_H +#define SVG_H + +#include + +struct svg_document; +struct svg_rect; +struct svg_text; +struct svg_line; +typedef struct svg_document *svg_document_t; +typedef struct svg_rect *svg_rect_t; +typedef struct svg_text *svg_text_t; +typedef struct svg_line *svg_line_t; + +typedef struct svg_transform { + double tx, ty; + double sx, sy; + double rot; +} *svg_transform_t; + +svg_document_t svg_document_create(const char *); +int svg_document_close(svg_document_t); +struct svg_rect *svg_rect_new(const char *); +int svg_rect_draw(svg_document_t, svg_rect_t, double, double, double, + double); +struct svg_text *svg_text_new(const char *); +int svg_text_draw(svg_document_t, svg_text_t, svg_transform_t, + const char *, double); +struct svg_line *svg_line_new(const char *); +int svg_line_draw(svg_document_t, svg_line_t, double, double, double, double, + svg_transform_t); + +#endif /* SVG_H */ diff --git a/usr.bin/evtranalyze/xml.c b/usr.bin/evtranalyze/xml.c new file mode 100644 index 0000000..616b18a --- /dev/null +++ b/usr.bin/evtranalyze/xml.c @@ -0,0 +1,144 @@ +#include +#include +#include + +#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, "\n"); + fprintf(doc->file, "\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, "\n", el->name); + return 0; +} + +int +xml_elem_close(xml_document_t doc, xml_element_t el) +{ + return _xml_elem_close(doc, el, !0); +} + +int +xml_elem_begin(xml_document_t doc, xml_element_t el) +{ + if (el->value) { + return !0; + } + return _xml_elem_begin(doc, el, 0, !0); +} + +int +xml_elem_closed(xml_document_t doc, xml_element_t el) +{ + if (el->value) { + _xml_elem_begin(doc, el, 0, 0); + fprintf(doc->file, "%s", el->value); + _xml_elem_close(doc, el, 0); + return 0; + } + indent(doc); + return xml_elem_print(doc, el, !0, !0); +} + diff --git a/usr.bin/evtranalyze/xml.h b/usr.bin/evtranalyze/xml.h new file mode 100644 index 0000000..8f4a8e9 --- /dev/null +++ b/usr.bin/evtranalyze/xml.h @@ -0,0 +1,73 @@ +#ifndef _EVTRANALYZE_XML_H_ +#define _EVTRANALYZE_XML_H_ + +#include +#include + + +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_ */ diff --git a/usr.bin/ktrdump/Makefile b/usr.bin/ktrdump/Makefile index 806fa6b..72a74b4 100644 --- a/usr.bin/ktrdump/Makefile +++ b/usr.bin/ktrdump/Makefile @@ -2,8 +2,8 @@ # $FreeBSD: src/usr.bin/ktrdump/Makefile,v 1.3 2002/06/06 11:27:03 ru Exp $ PROG= ktrdump -DPADD= ${LIBKVM} -LDADD= -lkvm +DPADD= ${LIBKVM} ${LIBEVTR} +LDADD= -lkvm -levtr MAN= ktrdump.8 .include diff --git a/usr.bin/ktrdump/ktrdump.8 b/usr.bin/ktrdump/ktrdump.8 index 5e5f45c..66909f1 100644 --- a/usr.bin/ktrdump/ktrdump.8 +++ b/usr.bin/ktrdump/ktrdump.8 @@ -34,7 +34,7 @@ .Nd print kernel ktr trace buffer .Sh SYNOPSIS .Nm -.Op Fl acfilnpqrstx +.Op Fl acdfilnpqrstx .Op Fl A Ar factor .Op Fl N Ar execfile .Op Fl M Ar corefile @@ -61,6 +61,11 @@ Note that is not included. .It Fl c Print the CPU number that each entry was logged from. +.It Fl d +Dump an event stream to the file specified with +.Fl o . +This stream can be examined with +.Xr ktrdump 8 . .It Fl f Print the file and line number that each entry was logged from. .It Fl i diff --git a/usr.bin/ktrdump/ktrdump.c b/usr.bin/ktrdump/ktrdump.c index 31020fb..a5612a9 100644 --- a/usr.bin/ktrdump/ktrdump.c +++ b/usr.bin/ktrdump/ktrdump.c @@ -46,6 +46,7 @@ #include #include #include +#include #define SBUFLEN 128 @@ -80,7 +81,15 @@ static struct nlist nl_version_ktr_cpu[] = { { .n_name = NULL } }; +struct save_ctx { + char save_buf[128]; + 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; @@ -108,18 +117,21 @@ static int ktr_version; static void usage(void); static int earliest_ts(struct ktr_buffer *); +static void dump_machine_info(evtr_t); static void print_header(FILE *, int); static void print_entry(FILE *, int, int, struct ktr_entry *, u_int64_t *); -static struct ktr_info *kvm_ktrinfo(void *); -static const char *kvm_string(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); /* @@ -130,8 +142,10 @@ main(int ac, char **av) { struct ktr_buffer *ktr_bufs; struct ktr_entry **ktr_kbuf; + ktr_iter_cb_t callback = &print_callback; int *ktr_idx; FILE *fo; + void *ctx; int64_t tts; int *ktr_start_index; int c; @@ -141,7 +155,7 @@ main(int ac, char **av) * Parse commandline arguments. */ fo = stdout; - while ((c = getopt(ac, av, "acfinqrtxpslA:N:M:o:")) != -1) { + while ((c = getopt(ac, av, "acfinqrtxpslA:N:M:o:d")) != -1) { switch (c) { case 'a': cflag = 1; @@ -155,6 +169,10 @@ main(int ac, char **av) case 'c': cflag = 1; break; + case 'd': + dflag = 1; + callback = &dump_callback; + break; case 'N': if (strlcpy(execfile, optarg, sizeof(execfile)) >= sizeof(execfile)) @@ -209,6 +227,13 @@ main(int ac, char **av) usage(); } } + ctx = fo; + if (dflag) { + ctx = evtr_open_write(fo); + if (!ctx) { + err(1, "Can't create event stream"); + } + } if (cflag + iflag + tflag + xflag + fflag + pflag == 0) { cflag = 1; iflag = 1; @@ -252,6 +277,9 @@ main(int ac, char **av) printf("TSC frequency is %6.3f MHz\n", tsc_frequency / 1000000.0); + if (dflag) { + dump_machine_info((evtr_t)ctx); + } ktr_kbuf = calloc(ncpus, sizeof(*ktr_kbuf)); ktr_idx = calloc(ncpus, sizeof(*ktr_idx)); @@ -274,7 +302,8 @@ main(int ac, char **av) u_int64_t last_timestamp = 0; do { load_bufs(ktr_bufs, ktr_kbuf, ktr_idx); - print_bufs_timesorted(fo, ktr_bufs, &last_timestamp); + iterate_bufs_timesorted(ctx, ktr_bufs, &last_timestamp, + callback); if (lflag) usleep(1000000 / 10); } while (lflag); @@ -283,14 +312,29 @@ main(int ac, char **av) do { load_bufs(ktr_bufs, ktr_kbuf, ktr_idx); for (n = 0; n < ncpus; ++n) - print_buf(fo, ktr_bufs, n, &last_timestamp[n]); + iterate_buf(ctx, ktr_bufs, n, &last_timestamp[n], + callback); if (lflag) usleep(1000000 / 10); } while (lflag); } + if (dflag) + evtr_close(ctx); return (0); } +static +void +dump_machine_info(evtr_t evtr) +{ + struct evtr_event ev; + + ev.type = EVTR_TYPE_CPUINFO; + ev.ncpus = ncpus; + + evtr_dump_event(evtr, &ev); +} + static void print_header(FILE *fo, int row) { @@ -321,6 +365,8 @@ print_entry(FILE *fo, int n, int row, struct ktr_entry *entry, u_int64_t *last_timestamp) { struct ktr_info *info = NULL; + static struct save_ctx nctx, pctx, fmtctx, symctx, infoctx; + fprintf(fo, " %06x ", row & 0x00FFFFFF); if (cflag) @@ -343,71 +389,111 @@ print_entry(FILE *fo, int n, int row, struct ktr_entry *entry, entry->ktr_caller2, entry->ktr_caller1); } else { fprintf(fo, "%-20s ", - address_to_symbol(entry->ktr_caller2)); + address_to_symbol(entry->ktr_caller2, &symctx)); fprintf(fo, "%-20s ", - address_to_symbol(entry->ktr_caller1)); + 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(info->kf_name)); + fprintf(fo, "%-20s ", kvm_string(info->kf_name, &nctx)); else fprintf(fo, "%-20s ", ""); } if (fflag) - fprintf(fo, "%34s:%-4d ", trunc_path(kvm_string(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) - vfprintf(fo, kvm_string(info->kf_format), (void *)&entry->ktr_data); + vfprintf(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); +} + + +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; + + 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; + 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)); + } +} + +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(const char *kptr) +kvm_string(const char *kptr, struct save_ctx *ctx) { - static char save_str[128]; - 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 < sizeof(save_str) - 1) { + while (l < sizeof(ctx->save_buf) - 1) { n = 256 - ((intptr_t)(kptr + l) & 255); - if (n > sizeof(save_str) - l - 1) - n = sizeof(save_str) - l - 1; - if (kvm_read(kd, (uintptr_t)(kptr + l), save_str + l, n) < 0) + 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 < sizeof(save_str) && n) { - if (save_str[l] == 0) + while (l < sizeof(ctx->save_buf) && n) { + if (ctx->save_buf[l] == 0) break; --n; ++l; @@ -415,9 +501,9 @@ kvm_string(const char *kptr) if (n) break; } - save_str[l] = 0; + ctx->save_buf[l] = 0; } - return(save_str); + return(ctx->save_buf); } static @@ -490,14 +576,15 @@ read_symbols(const char *file) static const char * -address_to_symbol(void *kptr) +address_to_symbol(void *kptr, struct save_ctx *ctx) { - static char buf[64]; + char *buf = ctx->save_buf; + int size = sizeof(ctx->save_buf); if (symcache == NULL || (char *)kptr < symbegin || (char *)kptr >= symend ) { - snprintf(buf, sizeof(buf), "%p", kptr); + snprintf(buf, size, "%p", kptr); return(buf); } while ((char *)symcache->symaddr < (char *)kptr) { @@ -509,7 +596,7 @@ address_to_symbol(void *kptr) if (symcache != TAILQ_FIRST(&symlist)) symcache = TAILQ_PREV(symcache, symlist, link); } - snprintf(buf, sizeof(buf), "%s+%d", symcache->symname, + snprintf(buf, size, "%s+%d", symcache->symname, (int)((char *)kptr - symcache->symaddr)); return(buf); } @@ -638,8 +725,8 @@ earliest_ts(struct ktr_buffer *buf) static void -print_buf(FILE *fo, struct ktr_buffer *ktr_bufs, int cpu, - u_int64_t *last_timestamp) +iterate_buf(FILE *fo, struct ktr_buffer *ktr_bufs, int cpu, + u_int64_t *last_timestamp, ktr_iter_cb_t cb) { struct ktr_buffer *buf = ktr_bufs + cpu; @@ -650,10 +737,9 @@ print_buf(FILE *fo, struct ktr_buffer *ktr_bufs, int cpu, buf->ents[buf->beg_idx & fifo_mask].ktr_timestamp; } while (buf->beg_idx != buf->end_idx) { - print_header(fo, buf->beg_idx); - print_entry(fo, cpu, buf->beg_idx, - &buf->ents[buf->beg_idx & fifo_mask], - last_timestamp); + cb(fo, cpu, buf->beg_idx, + &buf->ents[buf->beg_idx & fifo_mask], + last_timestamp); ++buf->beg_idx; } buf->modified = 0; @@ -661,8 +747,8 @@ print_buf(FILE *fo, struct ktr_buffer *ktr_bufs, int cpu, static void -print_bufs_timesorted(FILE *fo, struct ktr_buffer *ktr_bufs, - u_int64_t *last_timestamp) +iterate_bufs_timesorted(FILE *fo, struct ktr_buffer *ktr_bufs, + u_int64_t *last_timestamp, ktr_iter_cb_t cb) { struct ktr_entry *ent; struct ktr_buffer *buf; @@ -686,10 +772,9 @@ print_bufs_timesorted(FILE *fo, struct ktr_buffer *ktr_bufs, if ((bestn < 0) || (ts < *last_timestamp)) break; buf = ktr_bufs + bestn; - print_header(fo, row); - print_entry(fo, bestn, row, - &buf->ents[buf->beg_idx & fifo_mask], - last_timestamp); + cb(fo, bestn, row, + &buf->ents[buf->beg_idx & fifo_mask], + last_timestamp); ++buf->beg_idx; *last_timestamp = ts; ++row;