s/NO_MAN/NOMAN/ in various Makefiles.
[dragonfly.git] / usr.bin / evtranalyze / evtranalyze.c
blobdd8b030e72354641b51163e0ce9338512bb7ca85
1 /*
2 * Copyright (c) 2009, 2010 Aggelos Economopoulos. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in
12 * the documentation and/or other materials provided with the
13 * distribution.
14 * 3. Neither the name of The DragonFly Project nor the names of its
15 * contributors may be used to endorse or promote products derived
16 * from this software without specific, prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
28 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
32 #include <assert.h>
33 #include <ctype.h>
34 #include <err.h>
35 #include <inttypes.h>
36 #include <libgen.h>
37 #include <math.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
43 #include <evtr.h>
44 #include "xml.h"
45 #include "svg.h"
46 #include "plotter.h"
47 #include "trivial.h"
49 enum {
50 NR_TOP_THREADS = 5,
51 NR_BUCKETS = 1021,
54 struct rows {
55 double row_increment;
56 double row_off;
59 #define CMD_PROTO(name) \
60 static int cmd_ ## name(int, char **)
62 CMD_PROTO(show);
63 CMD_PROTO(svg);
64 CMD_PROTO(stats);
65 CMD_PROTO(summary);
67 struct command {
68 const char *name;
69 int (*func)(int argc, char **argv);
70 } commands[] = {
72 .name = "show",
73 .func = &cmd_show,
76 .name = "svg",
77 .func = &cmd_svg,
80 .name = "stats",
81 .func = &cmd_stats,
84 .name = "summary",
85 .func = &cmd_summary,
88 .name = NULL,
92 evtr_t evtr;
93 static char *opt_infile;
94 unsigned evtranalyze_debug;
96 static
97 void
98 printd_set_flags(const char *str, unsigned int *flags)
101 * This is suboptimal as we don't detect
102 * invalid flags.
104 for (; *str; ++str) {
105 if ('A' == *str) {
106 *flags = -1;
107 return;
109 if (!islower(*str))
110 err(2, "invalid debug flag %c\n", *str);
111 *flags |= 1 << (*str - 'a');
115 struct hashentry {
116 uintptr_t key;
117 uintptr_t val;
118 struct hashentry *next;
121 struct hashtab {
122 struct hashentry *buckets[NR_BUCKETS];
123 uintptr_t (*hashfunc)(uintptr_t);
124 uintptr_t (*cmpfunc)(uintptr_t, uintptr_t);
127 static int
128 ehash_find(const struct hashtab *tab, uintptr_t key, uintptr_t *val)
130 struct hashentry *ent;
132 for(ent = tab->buckets[tab->hashfunc(key)];
133 ent && tab->cmpfunc(ent->key, key);
134 ent = ent->next);
136 if (!ent)
137 return !0;
138 *val = ent->val;
139 return 0;
142 static struct hashentry *
143 ehash_insert(struct hashtab *tab, uintptr_t key, uintptr_t val)
145 struct hashentry *ent;
146 int hsh;
148 if (!(ent = malloc(sizeof(*ent)))) {
149 fprintf(stderr, "out of memory\n");
150 return NULL;
152 hsh = tab->hashfunc(key);
153 ent->next = tab->buckets[hsh];
154 ent->key = key;
155 ent->val = val;
156 tab->buckets[hsh] = ent;
157 return ent;
159 static
161 ehash_delete(struct hashtab *tab, uintptr_t key)
163 struct hashentry *ent, *prev;
165 prev = NULL;
166 for(ent = tab->buckets[tab->hashfunc(key)];
167 ent && tab->cmpfunc(ent->key, key);
168 prev = ent, ent = ent->next);
169 if (!ent)
170 return !0;
171 if (prev)
172 prev->next = ent->next;
173 else
174 tab->buckets[tab->hashfunc(key)] = ent->next;
175 free(ent);
176 return 0;
179 static
180 uintptr_t
181 cmpfunc_pointer(uintptr_t a, uintptr_t b)
183 return b - a;
186 static
187 uintptr_t
188 hashfunc_pointer(uintptr_t p)
190 return p % NR_BUCKETS;
193 static
194 uintptr_t
195 hashfunc_string(uintptr_t p)
197 const char *str = (char *)p;
198 unsigned long hash = 5381;
199 int c;
201 while ((c = *str++))
202 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
203 return hash % NR_BUCKETS;
206 static struct hashtab *
207 ehash_new(void)
209 struct hashtab *tab;
210 if (!(tab = calloc(sizeof(struct hashtab), 1)))
211 return tab;
212 tab->hashfunc = &hashfunc_pointer;
213 tab->cmpfunc = &cmpfunc_pointer;
214 return tab;
217 /* returns 0 if equal */
218 static
220 cmp_vals(evtr_variable_value_t a, evtr_variable_value_t b)
222 if (a->type != b->type)
223 return !0;
224 switch (a->type) {
225 case EVTR_VAL_NIL:
226 return 0;
227 case EVTR_VAL_INT:
228 return !(a->num == b->num);
229 case EVTR_VAL_STR:
230 return strcmp(a->str, b->str);
231 case EVTR_VAL_HASH:
232 return !0; /* come on! */
233 case EVTR_VAL_CTOR:
234 err(3, "not implemented");
236 err(3, "can't get here");
239 static
240 uintptr_t
241 cmpfunc_ctor(uintptr_t _a, uintptr_t _b)
243 evtr_variable_value_t vala, valb;
244 vala = (evtr_variable_value_t)_a;
245 valb = (evtr_variable_value_t)_b;
246 if (strcmp(vala->ctor.name, valb->ctor.name))
247 return !0;
248 vala = TAILQ_FIRST(&vala->ctor.args);
249 valb = TAILQ_FIRST(&valb->ctor.args);
250 for (;;) {
251 if (!vala && !valb)
252 return 0;
253 if ((vala && !valb) || (valb && !vala))
254 return !0;
255 if (cmp_vals(vala, valb))
256 return !0;
257 vala = TAILQ_NEXT(vala, link);
258 valb = TAILQ_NEXT(valb, link);
262 static
263 uintptr_t
264 hashfunc_ctor(uintptr_t _p)
266 evtr_variable_value_t val, ctor_val = (evtr_variable_value_t)_p;
267 char buf[1024], *p = &buf[0];
268 size_t len;
270 p = buf;
271 assert(ctor_val->type == EVTR_VAL_CTOR);
272 len = strlcpy(buf, ctor_val->ctor.name, sizeof(buf));
273 if (len >= sizeof(buf))
274 goto done;
276 TAILQ_FOREACH(val, &ctor_val->ctor.args, link) {
277 switch (val->type) {
278 case EVTR_VAL_NIL:
279 assert(!"can't happen");
280 break;
281 case EVTR_VAL_INT:
282 len += snprintf(p + len, sizeof(buf) - len - 1,
283 "%jd", val->num);
284 break;
285 case EVTR_VAL_STR:
286 len = strlcat(p, val->str, sizeof(buf));
287 break;
288 case EVTR_VAL_HASH:
289 break; /* come on! */
290 case EVTR_VAL_CTOR:
291 err(3, "not implemented");
293 if (len >= (sizeof(buf) - 1))
294 break;
296 done:
297 buf[sizeof(buf) - 1] = '\0';
298 return hashfunc_string((uintptr_t)buf);
301 typedef struct vector {
302 uintmax_t *vals;
303 int used;
304 int allocated;
305 } *vector_t;
307 static vector_t
308 vector_new(void)
310 vector_t v;
311 if (!(v = malloc(sizeof(*v))))
312 return v;
313 v->allocated = 2;
314 if (!(v->vals = malloc(v->allocated * sizeof(v->vals[0])))) {
315 free(v);
316 return NULL;
318 v->allocated =
319 v->used = 0;
320 return v;
323 static
324 void
325 vector_push(vector_t v, uintmax_t val)
327 uintmax_t *tmp;
328 if (v->used == v->allocated) {
329 tmp = realloc(v->vals, 2 * v->allocated * sizeof(v->vals[0]));
330 if (!tmp)
331 err(1, "out of memory");
332 v->vals = tmp;
333 v->allocated *= 2;
335 v->vals[v->used++] = val;
338 static
339 void
340 vector_destroy(vector_t v)
342 free(v->vals);
343 free(v);
346 static
348 vector_nelems(vector_t v)
350 return v->used;
353 #define vector_foreach(v, val, i) \
354 for (i = 0, val = v->vals[0]; i < v->used; val = v->vals[++i])
356 static
357 double
358 stddev(vector_t v, double avg)
360 uintmax_t val;
361 int i;
362 double diff, sqr_sum = 0.0;
364 if (vector_nelems(v) < 2)
365 return 1 / 0.0;
366 vector_foreach(v, val, i) {
367 diff = val - avg;
368 sqr_sum += diff * diff;
370 return sqrt(sqr_sum / (vector_nelems(v) - 1));
373 static
374 void
375 usage(void)
377 fprintf(stderr, "bad usage :P\n");
378 exit(2);
381 static
382 void
383 rows_init(struct rows *rows, int n, double height, double perc)
385 double row_h;
386 rows->row_increment = height / n;
387 /* actual row height */
388 row_h = perc * rows->row_increment;
389 rows->row_off = (rows->row_increment - row_h) / 2.0;
390 assert(!isnan(rows->row_increment));
391 assert(!isnan(rows->row_off));
394 static
395 void
396 rows_n(struct rows *rows, int n, double *y, double *height)
398 *y = n * rows->row_increment + rows->row_off;
399 *height = rows->row_increment - 2 * rows->row_off;
403 * Which fontsize to use so that the string fits in the
404 * given rect.
406 static
407 double
408 fontsize_for_rect(double width, double height, int textlen)
410 double wpc, maxh;
412 * We start with a font size equal to the height
413 * of the rectangle and round down so that we only
414 * use a limited number of sizes.
416 * For a rectangle width comparable to the height,
417 * the text might extend outside of the rectangle.
418 * In that case we need to limit it.
420 /* available width per character */
421 wpc = width / textlen;
423 * Assuming a rough hight/width ratio for characters,
424 * calculate the available height and round it down
425 * just to be on the safe side.
427 #define GLYPH_HIGHT_TO_WIDTH 1.5
428 maxh = GLYPH_HIGHT_TO_WIDTH * wpc * 0.9;
429 if (height > maxh) {
430 height = maxh;
431 } else if (height < 0.01) {
432 height = 0.01;
433 } else {
434 /* rounding (XXX: make cheaper)*/
435 height = log(height);
436 height = round(height);
437 height = exp(height);
439 return height;
442 struct pass_hook {
443 void (*pre)(void *);
444 void (*event)(void *, evtr_event_t);
445 void (*post)(void *);
446 void *data;
447 struct evtr_filter *filts;
448 int nfilts;
451 struct thread_info {
452 uint64_t runtime;
455 struct ts_interval {
456 uint64_t start;
457 uint64_t end;
460 struct td_switch_ctx {
461 svg_document_t svg;
462 struct rows *cpu_rows;
463 struct rows *thread_rows;
464 /* which events the user cares about */
465 struct ts_interval interval;
466 /* first/last event timestamps on any cpu */
467 struct ts_interval firstlast;
468 double width;
469 double xscale; /* scale factor applied to x */
470 svg_rect_t cpu_sw_rect;
471 svg_rect_t thread_rect;
472 svg_rect_t inactive_rect;
473 svg_text_t thread_label;
474 struct cpu_table {
475 struct cpu *cpus;
476 int ncpus;
477 } cputab;
478 struct evtr_thread **top_threads;
479 int nr_top_threads;
480 double thread_rows_yoff;
483 struct cpu {
484 struct evtr_thread *td;
485 int i; /* cpu index */
486 uint64_t ts; /* time cpu switched to td */
487 /* timestamp for first/last event on this cpu */
488 struct ts_interval firstlast;
489 double freq;
490 uintmax_t evcnt;
493 static
494 void
495 do_pass(struct pass_hook *hooks, int nhooks)
497 struct evtr_filter *filts = NULL;
498 int nfilts = 0, i;
499 struct evtr_query *q;
500 struct evtr_event ev;
502 for (i = 0; i < nhooks; ++i) {
503 struct pass_hook *h = &hooks[i];
504 if (h->pre)
505 h->pre(h->data);
506 if (h->nfilts > 0) {
507 filts = realloc(filts, (nfilts + h->nfilts) *
508 sizeof(struct evtr_filter));
509 if (!filts)
510 err(1, "Out of memory");
511 memcpy(filts + nfilts, h->filts,
512 h->nfilts * sizeof(struct evtr_filter));
513 nfilts += h->nfilts;
516 q = evtr_query_init(evtr, filts, nfilts);
517 if (!q)
518 err(1, "Can't initialize query\n");
519 while(!evtr_query_next(q, &ev)) {
520 for (i = 0; i < nhooks; ++i) {
521 if (hooks[i].event)
522 hooks[i].event(hooks[i].data, &ev);
525 if (evtr_query_error(q)) {
526 err(1, "%s", evtr_query_errmsg(q));
528 evtr_query_destroy(q);
530 for (i = 0; i < nhooks; ++i) {
531 if (hooks[i].post)
532 hooks[i].post(hooks[i].data);
534 if (evtr_rewind(evtr))
535 err(1, "Can't rewind event stream\n");
538 static
539 void
540 draw_thread_run(struct td_switch_ctx *ctx, struct cpu *c, evtr_event_t ev, int row)
542 double x, w, y, height;
543 w = (ev->ts - c->ts) * ctx->xscale;
544 x = (ev->ts - ctx->firstlast.start) * ctx->xscale;
545 rows_n(ctx->thread_rows, row, &y, &height);
546 svg_rect_draw(ctx->svg, ctx->thread_rect, x - w,
547 y + ctx->thread_rows_yoff, w, height);
550 static
551 void
552 draw_ctx_switch(struct td_switch_ctx *ctx, struct cpu *c, evtr_event_t ev)
554 struct svg_transform textrot;
555 char comm[100];
556 double x, w, fs, y, height;
557 int textlen;
559 assert(ctx->xscale > 0.0);
560 if (!c->ts)
561 return;
562 /* distance to previous context switch */
563 w = (ev->ts - c->ts) * ctx->xscale;
564 x = (ev->ts - ctx->firstlast.start) * ctx->xscale;
565 if ((x - w) < 0) {
566 fprintf(stderr, "(%ju - %ju) * %.20lf\n",
567 (uintmax_t)ev->ts,
568 (uintmax_t)ctx->firstlast.start, ctx->xscale);
569 abort();
572 rows_n(ctx->cpu_rows, c->i, &y, &height);
573 assert(!isnan(y));
574 assert(!isnan(height));
576 svg_rect_draw(ctx->svg, ctx->cpu_sw_rect, x - w, y, w, height);
579 * Draw the text label describing the thread we
580 * switched out of.
582 textrot.tx = x - w;
583 textrot.ty = y;
584 textrot.sx = 1.0;
585 textrot.sy = 1.0;
586 textrot.rot = 90.0;
587 textlen = snprintf(comm, sizeof(comm) - 1, "%s (%p)",
588 c->td ? c->td->comm : "unknown",
589 c->td ? c->td->id: NULL);
590 if (textlen > (int)sizeof(comm))
591 textlen = sizeof(comm) - 1;
592 comm[sizeof(comm) - 1] = '\0';
594 * Note the width and hight are exchanged because
595 * the bounding rectangle is rotated by 90 degrees.
597 fs = fontsize_for_rect(height, w, textlen);
598 svg_text_draw(ctx->svg, ctx->thread_label, &textrot, comm,
599 fs);
604 * The stats for ntd have changed, update ->top_threads
606 static
607 void
608 top_threads_update(struct td_switch_ctx *ctx, struct evtr_thread *ntd)
610 struct thread_info *tdi = ntd->userdata;
611 int i, j;
612 for (i = 0; i < ctx->nr_top_threads; ++i) {
613 struct evtr_thread *td = ctx->top_threads[i];
614 if (td == ntd) {
616 * ntd is already in top_threads and it is at
617 * the correct ranking
619 break;
621 if (!td) {
622 /* empty slot -- just insert our thread */
623 ctx->top_threads[i] = ntd;
624 break;
626 if (((struct thread_info *)td->userdata)->runtime >=
627 tdi->runtime) {
628 /* this thread ranks higher than we do. Move on */
629 continue;
632 * OK, we've found the first thread that we outrank, so we
633 * need to replace it w/ our thread.
635 td = ntd; /* td holds the thread we will insert next */
636 for (j = i + 1; j < ctx->nr_top_threads; ++j, ++i) {
637 struct evtr_thread *tmp;
639 /* tmp holds the thread we replace */
640 tmp = ctx->top_threads[j];
641 ctx->top_threads[j] = td;
642 if (tmp == ntd) {
644 * Our thread was already in the top list,
645 * and we just removed the second instance.
646 * Nothing more to do.
648 break;
650 td = tmp;
652 break;
656 static
657 void
658 ctxsw_prepare_event(void *_ctx, evtr_event_t ev)
660 struct td_switch_ctx *ctx = _ctx;
661 struct cpu *c, *cpus = ctx->cputab.cpus;
662 struct thread_info *tdi;
664 (void)evtr;
665 printd(INTV, "test1 (%ju:%ju) : %ju\n",
666 (uintmax_t)ctx->interval.start,
667 (uintmax_t)ctx->interval.end,
668 (uintmax_t)ev->ts);
669 if ((ev->ts > ctx->interval.end) ||
670 (ev->ts < ctx->interval.start))
671 return;
672 printd(INTV, "PREPEV on %d\n", ev->cpu);
674 /* update first/last timestamps */
675 c = &cpus[ev->cpu];
676 if (!c->firstlast.start) {
677 c->firstlast.start = ev->ts;
679 c->firstlast.end = ev->ts;
681 * c->td can be null when this is the first ctxsw event we
682 * observe for a cpu
684 if (c->td) {
685 /* update thread stats */
686 if (!c->td->userdata) {
687 if (!(tdi = malloc(sizeof(struct thread_info))))
688 err(1, "Out of memory");
689 c->td->userdata = tdi;
690 tdi->runtime = 0;
692 tdi = c->td->userdata;
693 tdi->runtime += ev->ts - c->ts;
694 top_threads_update(ctx, c->td);
697 /* Notice that ev->td is the new thread for ctxsw events */
698 c->td = ev->td;
699 c->ts = ev->ts;
702 static
703 void
704 find_first_last_ts(struct cpu_table *cputab, struct ts_interval *fl)
706 struct cpu *cpus = &cputab->cpus[0];
707 int i;
709 fl->start = -1;
710 fl->end = 0;
711 for (i = 0; i < cputab->ncpus; ++i) {
712 printd(INTV, "cpu%d: (%ju, %ju)\n", i,
713 (uintmax_t)cpus[i].firstlast.start,
714 (uintmax_t)cpus[i].firstlast.end);
715 if (cpus[i].firstlast.start &&
716 (cpus[i].firstlast.start < fl->start))
717 fl->start = cpus[i].firstlast.start;
718 if (cpus[i].firstlast.end &&
719 (cpus[i].firstlast.end > fl->end))
720 fl->end = cpus[i].firstlast.end;
721 cpus[i].td = NULL;
722 cpus[i].ts = 0;
724 printd(INTV, "global (%jd, %jd)\n", (uintmax_t)fl->start, (uintmax_t)fl->end);
727 static
728 void
729 ctxsw_prepare_post(void *_ctx)
731 struct td_switch_ctx *ctx = _ctx;
733 find_first_last_ts(&ctx->cputab, &ctx->firstlast);
736 static
737 void
738 ctxsw_draw_pre(void *_ctx)
740 struct td_switch_ctx *ctx = _ctx;
741 struct svg_transform textrot;
742 char comm[100];
743 double y, height, fs;
744 int i, textlen;
745 struct evtr_thread *td;
747 textrot.tx = 0.0 - 0.2; /* XXX */
748 textrot.sx = 1.0;
749 textrot.sy = 1.0;
750 textrot.rot = 270.0;
752 for (i = 0; i < ctx->nr_top_threads; ++i) {
753 td = ctx->top_threads[i];
754 if (!td)
755 break;
756 rows_n(ctx->thread_rows, i, &y, &height);
757 svg_rect_draw(ctx->svg, ctx->inactive_rect, 0.0,
758 y + ctx->thread_rows_yoff, ctx->width, height);
759 textlen = snprintf(comm, sizeof(comm) - 1, "%s (%p)",
760 td->comm, td->id);
761 if (textlen > (int)sizeof(comm))
762 textlen = sizeof(comm) - 1;
763 comm[sizeof(comm) - 1] = '\0';
764 fs = fontsize_for_rect(height, 100.0, textlen);
766 textrot.ty = y + ctx->thread_rows_yoff + height;
767 svg_text_draw(ctx->svg, ctx->thread_label, &textrot,
768 comm, fs);
772 static
773 void
774 ctxsw_draw_event(void *_ctx, evtr_event_t ev)
776 struct td_switch_ctx *ctx = _ctx;
777 struct cpu *c = &ctx->cputab.cpus[ev->cpu];
778 int i;
781 * ctx->firstlast.end can be 0 if there were no events
782 * in the specified interval, in which case
783 * ctx->firstlast.start is invalid too.
785 assert(!ctx->firstlast.end || (ev->ts >= ctx->firstlast.start));
786 printd(INTV, "test2 (%ju:%ju) : %ju\n", (uintmax_t)ctx->interval.start,
787 (uintmax_t)ctx->interval.end, (uintmax_t)ev->ts);
788 if ((ev->ts > ctx->interval.end) ||
789 (ev->ts < ctx->interval.start))
790 return;
791 printd(INTV, "DRAWEV %d\n", ev->cpu);
792 if (c->td != ev->td) { /* thread switch (or preemption) */
793 draw_ctx_switch(ctx, c, ev);
794 /* XXX: this is silly */
795 for (i = 0; i < ctx->nr_top_threads; ++i) {
796 if (!ctx->top_threads[i])
797 break;
798 if (ctx->top_threads[i] == c->td) {
799 draw_thread_run(ctx, c, ev, i);
800 break;
803 c->td = ev->td;
804 c->ts = ev->ts;
808 static
809 void
810 cputab_init(struct cpu_table *ct)
812 struct cpu *cpus;
813 double *freqs;
814 int i;
816 if ((ct->ncpus = evtr_ncpus(evtr)) <= 0)
817 err(1, "No cpu information!\n");
818 printd(MISC, "evtranalyze: ncpus %d\n", ct->ncpus);
819 if (!(ct->cpus = malloc(sizeof(struct cpu) * ct->ncpus))) {
820 err(1, "Can't allocate memory\n");
822 cpus = ct->cpus;
823 if (!(freqs = malloc(sizeof(double) * ct->ncpus))) {
824 err(1, "Can't allocate memory\n");
826 if ((i = evtr_cpufreqs(evtr, freqs))) {
827 warnc(i, "Can't get cpu frequencies\n");
828 for (i = 0; i < ct->ncpus; ++i) {
829 freqs[i] = -1.0;
833 /* initialize cpu array */
834 for (i = 0; i < ct->ncpus; ++i) {
835 cpus[i].td = NULL;
836 cpus[i].ts = 0;
837 cpus[i].i = i;
838 cpus[i].firstlast.start = 0;
839 cpus[i].firstlast.end = 0;
840 cpus[i].evcnt = 0;
841 cpus[i].freq = freqs[i];
843 free(freqs);
846 static
847 void
848 parse_interval(const char *_str, struct ts_interval *ts,
849 struct cpu_table *cputab)
851 double s, e, freq;
852 const char *str = _str + 1;
854 if ('c' == *_str) { /* cycles */
855 if (sscanf(str, "%" SCNu64 ":%" SCNu64,
856 &ts->start,
857 &ts->end) == 2)
858 return;
859 } else if ('m' == *_str) { /* miliseconds */
860 if (sscanf(str, "%lf:%lf", &s, &e) == 2) {
861 freq = cputab->cpus[0].freq;
862 freq *= 1000.0; /* msecs */
863 if (freq < 0.0) {
864 fprintf(stderr, "No frequency information"
865 " available\n");
866 err(2, "Can't convert time to cycles\n");
868 ts->start = s * freq;
869 ts->end = e * freq;
870 return;
873 fprintf(stderr, "invalid interval format: %s\n", _str);
874 usage();
878 static
880 cmd_svg(int argc, char **argv)
882 svg_document_t svg;
883 int ch;
884 double height, width;
885 struct rows cpu_rows, thread_rows;
886 struct td_switch_ctx td_ctx;
887 const char *outpath = "output.svg";
888 struct evtr_filter ctxsw_filts[2] = {
890 .flags = 0,
891 .cpu = -1,
892 .ev_type = EVTR_TYPE_PROBE,
895 .flags = 0,
896 .cpu = -1,
897 .ev_type = EVTR_TYPE_PROBE,
900 struct pass_hook ctxsw_prepare = {
901 .pre = NULL,
902 .event = ctxsw_prepare_event,
903 .post = ctxsw_prepare_post,
904 .data = &td_ctx,
905 .filts = ctxsw_filts,
906 .nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
907 }, ctxsw_draw = {
908 .pre = ctxsw_draw_pre,
909 .event = ctxsw_draw_event,
910 .post = NULL,
911 .data = &td_ctx,
912 .filts = ctxsw_filts,
913 .nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
917 * We are interested in thread switch and preemption
918 * events, but we don't use the data directly. Instead
919 * we rely on ev->td.
921 ctxsw_filts[0].fmt = "sw %p > %p";
922 ctxsw_filts[1].fmt = "pre %p > %p";
923 td_ctx.interval.start = 0;
924 td_ctx.interval.end = -1; /* i.e. no interval given */
925 td_ctx.nr_top_threads = NR_TOP_THREADS;
926 cputab_init(&td_ctx.cputab); /* needed for parse_interval() */
928 optind = 0;
929 optreset = 1;
930 while ((ch = getopt(argc, argv, "i:o:")) != -1) {
931 switch (ch) {
932 case 'i':
933 parse_interval(optarg, &td_ctx.interval,
934 &td_ctx.cputab);
935 break;
936 case 'o':
937 outpath = optarg;
938 break;
939 default:
940 usage();
944 argc -= optind;
945 argv += optind;
947 height = 200.0;
948 width = 700.0;
949 td_ctx.width = width;
951 if (!(td_ctx.top_threads = calloc(td_ctx.nr_top_threads,
952 sizeof(struct evtr_thread *))))
953 err(1, "Can't allocate memory\n");
954 if (!(svg = svg_document_create(outpath)))
955 err(1, "Can't open svg document\n");
958 * Create rectangles to use for output.
960 if (!(td_ctx.cpu_sw_rect = svg_rect_new("generic")))
961 err(1, "Can't create rectangle\n");
962 if (!(td_ctx.thread_rect = svg_rect_new("thread")))
963 err(1, "Can't create rectangle\n");
964 if (!(td_ctx.inactive_rect = svg_rect_new("inactive")))
965 err(1, "Can't create rectangle\n");
966 /* text for thread names */
967 if (!(td_ctx.thread_label = svg_text_new("generic")))
968 err(1, "Can't create text\n");
969 rows_init(&cpu_rows, td_ctx.cputab.ncpus, height, 0.9);
970 td_ctx.svg = svg;
971 td_ctx.xscale = -1.0;
972 td_ctx.cpu_rows = &cpu_rows;
974 do_pass(&ctxsw_prepare, 1);
975 td_ctx.thread_rows_yoff = height;
976 td_ctx.thread_rows = &thread_rows;
977 rows_init(td_ctx.thread_rows, td_ctx.nr_top_threads, 300, 0.9);
978 td_ctx.xscale = width / (td_ctx.firstlast.end - td_ctx.firstlast.start);
979 printd(SVG, "first %ju, last %ju, xscale %lf\n",
980 (uintmax_t)td_ctx.firstlast.start, (uintmax_t)td_ctx.firstlast.end,
981 td_ctx.xscale);
983 do_pass(&ctxsw_draw, 1);
985 svg_document_close(svg);
986 return 0;
989 static
991 cmd_show(int argc, char **argv)
993 struct evtr_event ev;
994 struct evtr_query *q;
995 struct evtr_filter filt;
996 struct cpu_table cputab;
997 double freq;
998 int ch;
999 uint64_t last_ts = 0;
1001 cputab_init(&cputab);
1003 * Assume all cores run on the same frequency
1004 * for now. There's no reason to complicate
1005 * things unless we can detect frequency change
1006 * events as well.
1008 * Note that the code is very simplistic and will
1009 * produce garbage if the kernel doesn't fixup
1010 * the timestamps for cores running with different
1011 * frequencies.
1013 freq = cputab.cpus[0].freq;
1014 freq /= 1000000; /* we want to print out usecs */
1015 printd(MISC, "using freq = %lf\n", freq);
1016 filt.flags = 0;
1017 filt.cpu = -1;
1018 filt.ev_type = EVTR_TYPE_PROBE;
1019 filt.fmt = NULL;
1020 optind = 0;
1021 optreset = 1;
1022 while ((ch = getopt(argc, argv, "f:")) != -1) {
1023 switch (ch) {
1024 case 'f':
1025 filt.fmt = optarg;
1026 break;
1029 q = evtr_query_init(evtr, &filt, 1);
1030 if (!q)
1031 err(1, "Can't initialize query\n");
1032 while(!evtr_query_next(q, &ev)) {
1033 char buf[1024];
1035 if (!last_ts)
1036 last_ts = ev.ts;
1037 if (freq < 0.0) {
1038 printf("%s\t%ju cycles\t[%.3d]\t%s:%d",
1039 ev.td ? ev.td->comm : "unknown",
1040 (uintmax_t)(ev.ts - last_ts), ev.cpu,
1041 basename(ev.file), ev.line);
1042 } else {
1043 printf("%s\t%.3lf usecs\t[%.3d]\t%s:%d",
1044 ev.td ? ev.td->comm : "unknown",
1045 (ev.ts - last_ts) / freq, ev.cpu,
1046 basename(ev.file), ev.line);
1048 if (ev.fmt) {
1049 evtr_event_data(&ev, buf, sizeof(buf));
1050 printf(" !\t%s\n", buf);
1051 } else {
1052 printf("\n");
1054 last_ts = ev.ts;
1056 if (evtr_query_error(q)) {
1057 err(1, "%s", evtr_query_errmsg(q));
1059 evtr_query_destroy(q);
1060 return 0;
1063 struct stats_ops {
1064 const char *statscmd;
1065 void *(*prepare)(int, char **, struct evtr_filter *);
1066 void (*each_event)(void *, evtr_event_t);
1067 void (*report)(void *);
1070 struct stats_integer_ctx {
1071 const char *varname;
1072 struct {
1073 int plot;
1074 const char *path;
1075 } opts;
1076 void *plotter_ctx;
1077 struct plotter *plotter;
1078 plotid_t time_plot;
1079 uintmax_t sum;
1080 uintmax_t occurrences;
1083 static
1084 void *
1085 stats_integer_prepare(int argc, char **argv, struct evtr_filter *filt)
1087 struct stats_integer_ctx *ctx;
1088 int ch;
1090 if (!(ctx = calloc(1, sizeof(*ctx))))
1091 return ctx;
1093 optind = 0;
1094 optreset = 1;
1095 while ((ch = getopt(argc, argv, "p:")) != -1) {
1096 switch (ch) {
1097 case 'p':
1098 ctx->opts.plot = !0;
1099 ctx->opts.path = optarg;
1100 break;
1101 default:
1102 usage();
1105 argc -= optind;
1106 argv += optind;
1108 if (argc != 1)
1109 err(2, "Need exactly one variable");
1110 ctx->varname = argv[0];
1111 ctx->sum = ctx->occurrences = 0;
1112 filt->flags = 0;
1113 filt->cpu = -1;
1114 filt->ev_type = EVTR_TYPE_STMT;
1115 filt->var = ctx->varname;
1116 if (!ctx->opts.plot)
1117 return ctx;
1119 if (!(ctx->plotter = plotter_factory()))
1120 err(1, "can't allocate plotter");
1121 if (!(ctx->plotter_ctx = ctx->plotter->plot_init(ctx->opts.path)))
1122 err(1, "can't allocate plotter context");
1124 if ((ctx->time_plot = ctx->plotter->plot_new(ctx->plotter_ctx,
1125 PLOT_TYPE_LINE,
1126 ctx->varname)) < 0)
1127 err(1, "can't create histogram");
1128 return ctx;
1131 static
1132 void
1133 stats_integer_each(void *_ctx, evtr_event_t ev)
1135 struct stats_integer_ctx *ctx = _ctx;
1136 if (EVTR_VAL_INT != ev->stmt.val->type) {
1137 fprintf(stderr, "event at %jd (cpu %d) does not treat %s as an"
1138 "integer variable; ignored\n", ev->ts, ev->cpu,
1139 ctx->varname);
1140 return;
1142 if (ctx->plotter)
1143 ctx->plotter->plot_line(ctx->plotter_ctx, ctx->time_plot,
1144 (double)ev->ts, (double)ev->stmt.val->num);
1145 ctx->sum += ev->stmt.val->num;
1146 ++ctx->occurrences;
1149 static
1150 void
1151 stats_integer_report(void *_ctx)
1153 struct stats_integer_ctx *ctx = _ctx;
1154 printf("median for variable %s is %lf\n", ctx->varname,
1155 (double)ctx->sum / ctx->occurrences);
1156 if (ctx->plotter)
1157 ctx->plotter->plot_finish(ctx->plotter_ctx);
1159 free(ctx);
1162 struct stats_completion_ctx {
1163 struct stats_completion_options {
1164 int plot;
1165 const char *path;
1166 } opts;
1167 struct plotter *plotter;
1168 void *plotter_ctx;
1169 plotid_t durations_plot;
1170 const char *varname;
1171 const char *ctor;
1172 const char *dtor;
1173 struct hashtab *ctors;
1174 uintmax_t historical_dtors;
1175 uintmax_t uncompleted_events;
1176 uintmax_t begun_events;
1177 uintmax_t completed_events;
1178 uintmax_t completed_duration_sum;
1179 vector_t durations;
1182 struct ctor_data {
1183 evtr_variable_value_t val;
1184 uintmax_t ts;
1187 static
1188 struct ctor_data *
1189 ctor_data_new(evtr_event_t ev)
1191 struct ctor_data *cd;
1193 if (!(cd = malloc(sizeof(*cd))))
1194 return cd;
1195 cd->val = ev->stmt.val;
1196 cd->ts = ev->ts;
1197 return cd;
1200 static
1201 void *
1202 stats_completion_prepare(int argc, char **argv, struct evtr_filter *filt)
1204 struct stats_completion_ctx *ctx;
1205 int ch;
1207 if (!(ctx = calloc(1, sizeof(*ctx))))
1208 return ctx;
1210 optind = 0;
1211 optreset = 1;
1212 while ((ch = getopt(argc, argv, "p:")) != -1) {
1213 switch (ch) {
1214 case 'p':
1215 ctx->opts.plot = !0;
1216 ctx->opts.path = optarg;
1217 break;
1218 default:
1219 usage();
1222 argc -= optind;
1223 argv += optind;
1224 if (argc != 3)
1225 err(2, "need a variable, a constructor and a destructor");
1226 if (!(ctx->ctors = ehash_new()))
1227 goto free_ctx;
1228 ctx->ctors->hashfunc = &hashfunc_ctor;
1229 ctx->ctors->cmpfunc = &cmpfunc_ctor;
1230 if (!(ctx->durations = vector_new()))
1231 goto free_ctors;
1232 ctx->varname = argv[0];
1233 ctx->ctor = argv[1];
1234 ctx->dtor = argv[2];
1236 filt->flags = 0;
1237 filt->cpu = -1;
1238 filt->ev_type = EVTR_TYPE_STMT;
1239 filt->var = ctx->varname;
1241 if (!ctx->opts.plot)
1242 return ctx;
1244 if (!(ctx->plotter = plotter_factory()))
1245 err(1, "can't allocate plotter");
1246 if (!(ctx->plotter_ctx = ctx->plotter->plot_init(ctx->opts.path)))
1247 err(1, "can't allocate plotter context");
1249 if ((ctx->durations_plot = ctx->plotter->plot_new(ctx->plotter_ctx,
1250 PLOT_TYPE_HIST,
1251 ctx->varname)) < 0)
1252 err(1, "can't create histogram");
1253 return ctx;
1254 free_ctors:
1255 ; /* XXX */
1256 free_ctx:
1257 free(ctx);
1258 return NULL;
1261 static
1262 void
1263 stats_completion_each(void *_ctx, evtr_event_t ev)
1265 struct stats_completion_ctx *ctx = _ctx;
1266 struct ctor_data *cd;
1268 if (ev->stmt.val->type != EVTR_VAL_CTOR) {
1269 fprintf(stderr, "event at %jd (cpu %d) does not assign to %s "
1270 "with a data constructor; ignored\n", ev->ts, ev->cpu,
1271 ctx->varname);
1272 return;
1274 if (!strcmp(ev->stmt.val->ctor.name, ctx->ctor)) {
1275 uintptr_t v;
1276 if (!ehash_find(ctx->ctors, (uintptr_t)ev->stmt.val, &v)) {
1277 /* XXX:better diagnostic */
1278 fprintf(stderr, "duplicate ctor\n");
1279 err(3, "giving up");
1281 if (!(cd = ctor_data_new(ev)))
1282 err(1, "out of memory");
1283 v = (uintptr_t)cd;
1284 if (!ehash_insert(ctx->ctors, (uintptr_t)ev->stmt.val, v))
1285 err(1, "out of memory");
1286 ++ctx->begun_events;
1287 } else if (!strcmp(ev->stmt.val->ctor.name, ctx->dtor)) {
1288 uintptr_t v;
1289 const char *tmp = ev->stmt.val->ctor.name;
1290 ev->stmt.val->ctor.name = ctx->ctor;
1291 if (ehash_find(ctx->ctors, (uintptr_t)ev->stmt.val, &v)) {
1292 ++ctx->historical_dtors;
1293 ev->stmt.val->ctor.name = tmp;
1294 return;
1296 cd = (struct ctor_data *)v;
1297 if (cd->ts >= ev->ts) {
1298 /* XXX:better diagnostic */
1299 fprintf(stderr, "destructor preceds constructor;"
1300 " ignored\n");
1301 ev->stmt.val->ctor.name = tmp;
1302 return;
1304 if (ctx->plotter)
1305 ctx->plotter->plot_histogram(ctx->plotter_ctx,
1306 ctx->durations_plot,
1307 (double)(ev->ts - cd->ts));
1308 vector_push(ctx->durations, ev->ts - cd->ts);
1309 ++ctx->completed_events;
1310 ctx->completed_duration_sum += ev->ts - cd->ts;
1311 if (ehash_delete(ctx->ctors, (uintptr_t)ev->stmt.val))
1312 err(3, "ctor disappeared from hash!");
1313 ev->stmt.val->ctor.name = tmp;
1314 } else {
1315 fprintf(stderr, "event at %jd (cpu %d) assigns to %s "
1316 "with an unexpected data constructor; ignored\n",
1317 ev->ts, ev->cpu, ctx->varname);
1318 return;
1322 static
1323 void
1324 stats_completion_report(void *_ctx)
1326 struct stats_completion_ctx *ctx = _ctx;
1327 double avg;
1329 printf("Events completed without having started:\t%jd\n",
1330 ctx->historical_dtors);
1331 printf("Events started but didn't complete:\t%jd\n",
1332 ctx->begun_events - ctx->completed_events);
1333 avg = (double)ctx->completed_duration_sum / ctx->completed_events;
1334 printf("Average event duration:\t%lf (stddev %lf)\n", avg,
1335 stddev(ctx->durations, avg));
1337 if (ctx->plotter)
1338 ctx->plotter->plot_finish(ctx->plotter_ctx);
1339 vector_destroy(ctx->durations);
1340 /* XXX: hash */
1341 free(ctx);
1344 static struct stats_ops cmd_stat_ops[] = {
1346 .statscmd = "integer",
1347 .prepare = &stats_integer_prepare,
1348 .each_event = &stats_integer_each,
1349 .report = &stats_integer_report,
1352 .statscmd = "completion",
1353 .prepare = &stats_completion_prepare,
1354 .each_event = &stats_completion_each,
1355 .report = &stats_completion_report,
1358 .statscmd = NULL,
1362 static
1364 cmd_stats(int argc, char **argv)
1366 struct evtr_event ev;
1367 struct evtr_query *q;
1368 struct evtr_filter filt;
1369 struct cpu_table cputab;
1370 double freq;
1371 uint64_t last_ts = 0;
1372 struct stats_ops *statsops = &cmd_stat_ops[0];
1373 void *statctx;
1375 for (; statsops->statscmd; ++statsops) {
1376 if (!strcmp(statsops->statscmd, argv[1]))
1377 break;
1379 if (!statsops->statscmd)
1380 err(2, "No such stats type: %s", argv[1]);
1382 --argc;
1383 ++argv;
1384 cputab_init(&cputab);
1386 * Assume all cores run on the same frequency
1387 * for now. There's no reason to complicate
1388 * things unless we can detect frequency change
1389 * events as well.
1391 * Note that the code is very simplistic and will
1392 * produce garbage if the kernel doesn't fixup
1393 * the timestamps for cores running with different
1394 * frequencies.
1396 freq = cputab.cpus[0].freq;
1397 freq /= 1000000; /* we want to print out usecs */
1398 printd(MISC, "using freq = %lf\n", freq);
1400 if (!(statctx = statsops->prepare(argc, argv, &filt)))
1401 err(1, "Can't allocate stats context");
1402 q = evtr_query_init(evtr, &filt, 1);
1403 if (!q)
1404 err(1, "Can't initialize query");
1405 while(!evtr_query_next(q, &ev)) {
1407 if (!last_ts)
1408 last_ts = ev.ts;
1410 assert(ev.type == EVTR_TYPE_STMT);
1411 statsops->each_event(statctx, &ev);
1412 last_ts = ev.ts;
1414 if (evtr_query_error(q)) {
1415 err(1, "%s", evtr_query_errmsg(q));
1417 evtr_query_destroy(q);
1418 statsops->report(statctx);
1419 return 0;
1423 static
1425 cmd_summary(int argc, char **argv)
1427 struct evtr_filter filt;
1428 struct evtr_event ev;
1429 struct evtr_query *q;
1430 double freq;
1431 struct cpu_table cputab;
1432 struct ts_interval global;
1433 uintmax_t global_evcnt;
1434 int i;
1436 (void)argc;
1437 (void)argv;
1439 cputab_init(&cputab);
1440 filt.ev_type = EVTR_TYPE_PROBE;
1441 filt.fmt = NULL;
1442 filt.flags = 0;
1443 filt.cpu = -1;
1445 q = evtr_query_init(evtr, &filt, 1);
1446 if (!q)
1447 err(1, "Can't initialize query\n");
1448 while(!evtr_query_next(q, &ev)) {
1449 struct cpu *c = &cputab.cpus[ev.cpu];
1450 if (!c->firstlast.start)
1451 c->firstlast.start = ev.ts;
1452 ++c->evcnt;
1453 c->firstlast.end = ev.ts;
1455 if (evtr_query_error(q)) {
1456 err(1, "%s", evtr_query_errmsg(q));
1458 evtr_query_destroy(q);
1460 find_first_last_ts(&cputab, &global);
1462 freq = cputab.cpus[0].freq;
1463 global_evcnt = 0;
1464 for (i = 0; i < cputab.ncpus; ++i) {
1465 struct cpu *c = &cputab.cpus[i];
1466 printf("CPU %d: %jd events in %.3lf secs\n", i,
1467 c->evcnt, (c->firstlast.end - c->firstlast.start)
1468 / freq);
1469 global_evcnt += c->evcnt;
1471 printf("Total: %jd events on %d cpus in %.3lf secs\n", global_evcnt,
1472 cputab.ncpus, (global.end - global.start) / freq);
1473 return 0;
1478 main(int argc, char **argv)
1480 int ch;
1481 FILE *inf;
1482 struct command *cmd;
1483 char *tmp;
1485 while ((ch = getopt(argc, argv, "f:D:")) != -1) {
1486 switch (ch) {
1487 case 'f':
1488 opt_infile = optarg;
1489 break;
1490 case 'D':
1491 if ((tmp = strchr(optarg, ':'))) {
1492 *tmp++ = '\0';
1493 evtr_set_debug(tmp);
1495 printd_set_flags(optarg, &evtranalyze_debug);
1496 break;
1497 default:
1498 usage();
1501 argc -= optind;
1502 argv += optind;
1504 if (argc == 0) {
1505 err(2, "need to specify a command\n");
1507 if (!opt_infile) {
1508 err(2, "you need to specify an input file\n");
1509 } else if (!strcmp(opt_infile, "-")) {
1510 inf = stdin;
1511 } else {
1512 inf = fopen(opt_infile, "r");
1513 if (!inf) {
1514 err(2, "Can't open input file\n");
1518 if (!(evtr = evtr_open_read(inf))) {
1519 err(1, "Can't open evtr stream\n");
1523 for (cmd = commands; cmd->name != NULL; ++cmd) {
1524 if (strcmp(argv[0], cmd->name))
1525 continue;
1526 cmd->func(argc, argv);
1527 break;
1529 if (!cmd->name) {
1530 err(2, "no such command: %s\n", argv[0]);
1533 evtr_close(evtr);
1534 return 0;