2 * Copyright (c) 2009, 2010 Aggelos Economopoulos. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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
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
56 #define CMD_PROTO(name) \
57 static int cmd_ ## name(int, char **)
65 int (*func)(int argc, char **argv);
86 static unsigned evtranalyze_debug;
88 #define DEFINE_DEBUG_FLAG(nam, chr)\
92 DEFINE_DEBUG_FLAG(INTV, 'i'),
93 DEFINE_DEBUG_FLAG(SVG, 's'),
94 DEFINE_DEBUG_FLAG(MISC, 'm'),
97 #define printd(subsys, ...) \
99 if (evtranalyze_debug & (subsys)) { \
100 fprintf(stderr, __VA_ARGS__); \
106 printd_set_flags(const char *str, unsigned int *flags)
109 * This is suboptimal as we don't detect
112 for (; *str; ++str) {
118 err(2, "invalid debug flag %c\n", *str);
119 *flags |= *str - 'a';
127 fprintf(stderr, "bad usage :P\n");
133 rows_init(struct rows *rows, int n, double height, double perc)
136 rows->row_increment = height / n;
137 /* actual row height */
138 row_h = perc * rows->row_increment;
139 rows->row_off = (rows->row_increment - row_h) / 2.0;
140 assert(!isnan(rows->row_increment));
141 assert(!isnan(rows->row_off));
146 rows_n(struct rows *rows, int n, double *y, double *height)
148 *y = n * rows->row_increment + rows->row_off;
149 *height = rows->row_increment - 2 * rows->row_off;
153 * Which fontsize to use so that the string fits in the
158 fontsize_for_rect(double width, double height, int textlen)
162 * We start with a font size equal to the height
163 * of the rectangle and round down so that we only
164 * use a limited number of sizes.
166 * For a rectangle width comparable to the height,
167 * the text might extend outside of the rectangle.
168 * In that case we need to limit it.
170 /* available width per character */
171 wpc = width / textlen;
173 * Assuming a rough hight/width ratio for characters,
174 * calculate the available height and round it down
175 * just to be on the safe side.
177 #define GLYPH_HIGHT_TO_WIDTH 1.5
178 maxh = GLYPH_HIGHT_TO_WIDTH * wpc * 0.9;
181 } else if (height < 0.01) {
184 /* rounding (XXX: make cheaper)*/
185 height = log(height);
186 height = round(height);
187 height = exp(height);
194 void (*event)(void *, evtr_event_t);
195 void (*post)(void *);
197 struct evtr_filter *filts;
210 struct td_switch_ctx {
212 struct rows *cpu_rows;
213 struct rows *thread_rows;
214 /* which events the user cares about */
215 struct ts_interval interval;
216 /* first/last event timestamps on any cpu */
217 struct ts_interval firstlast;
219 double xscale; /* scale factor applied to x */
220 svg_rect_t cpu_sw_rect;
221 svg_rect_t thread_rect;
222 svg_rect_t inactive_rect;
223 svg_text_t thread_label;
228 struct evtr_thread **top_threads;
230 double thread_rows_yoff;
234 struct evtr_thread *td;
235 int i; /* cpu index */
236 uint64_t ts; /* time cpu switched to td */
237 /* timestamp for first/last event on this cpu */
238 struct ts_interval firstlast;
245 do_pass(struct pass_hook *hooks, int nhooks)
247 struct evtr_filter *filts = NULL;
249 struct evtr_query *q;
250 struct evtr_event ev;
252 for (i = 0; i < nhooks; ++i) {
253 struct pass_hook *h = &hooks[i];
257 filts = realloc(filts, (nfilts + h->nfilts) *
258 sizeof(struct evtr_filter));
260 err(1, "Out of memory");
261 memcpy(filts + nfilts, h->filts,
262 h->nfilts * sizeof(struct evtr_filter));
266 q = evtr_query_init(evtr, filts, nfilts);
268 err(1, "Can't initialize query\n");
269 while(!evtr_query_next(q, &ev)) {
270 for (i = 0; i < nhooks; ++i) {
272 hooks[i].event(hooks[i].data, &ev);
275 if (evtr_error(evtr)) {
276 err(1, evtr_errmsg(evtr));
278 evtr_query_destroy(q);
280 for (i = 0; i < nhooks; ++i) {
282 hooks[i].post(hooks[i].data);
284 if (evtr_rewind(evtr))
285 err(1, "Can't rewind event stream\n");
290 draw_thread_run(struct td_switch_ctx *ctx, struct cpu *c, evtr_event_t ev, int row)
292 double x, w, y, height;
293 w = (ev->ts - c->ts) * ctx->xscale;
294 x = (ev->ts - ctx->firstlast.start) * ctx->xscale;
295 rows_n(ctx->thread_rows, row, &y, &height);
296 svg_rect_draw(ctx->svg, ctx->thread_rect, x - w,
297 y + ctx->thread_rows_yoff, w, height);
302 draw_ctx_switch(struct td_switch_ctx *ctx, struct cpu *c, evtr_event_t ev)
304 struct svg_transform textrot;
306 double x, w, fs, y, height;
309 assert(ctx->xscale > 0.0);
312 /* distance to previous context switch */
313 w = (ev->ts - c->ts) * ctx->xscale;
314 x = (ev->ts - ctx->firstlast.start) * ctx->xscale;
316 fprintf(stderr, "(%ju - %ju) * %.20lf\n",
318 (uintmax_t)ctx->firstlast.start, ctx->xscale);
322 rows_n(ctx->cpu_rows, c->i, &y, &height);
324 assert(!isnan(height));
326 svg_rect_draw(ctx->svg, ctx->cpu_sw_rect, x - w, y, w, height);
329 * Draw the text label describing the thread we
337 textlen = snprintf(comm, sizeof(comm) - 1, "%s (%p)",
338 c->td ? c->td->comm : "unknown",
339 c->td ? c->td->id: NULL);
340 if (textlen > (int)sizeof(comm))
341 textlen = sizeof(comm) - 1;
342 comm[sizeof(comm) - 1] = '\0';
344 * Note the width and hight are exchanged because
345 * the bounding rectangle is rotated by 90 degrees.
347 fs = fontsize_for_rect(height, w, textlen);
348 svg_text_draw(ctx->svg, ctx->thread_label, &textrot, comm,
354 * The stats for ntd have changed, update ->top_threads
358 top_threads_update(struct td_switch_ctx *ctx, struct evtr_thread *ntd)
360 struct thread_info *tdi = ntd->userdata;
362 for (i = 0; i < ctx->nr_top_threads; ++i) {
363 struct evtr_thread *td = ctx->top_threads[i];
366 * ntd is already in top_threads and it is at
367 * the correct ranking
372 /* empty slot -- just insert our thread */
373 ctx->top_threads[i] = ntd;
376 if (((struct thread_info *)td->userdata)->runtime >=
378 /* this thread ranks higher than we do. Move on */
382 * OK, we've found the first thread that we outrank, so we
383 * need to replace it w/ our thread.
385 td = ntd; /* td holds the thread we will insert next */
386 for (j = i + 1; j < ctx->nr_top_threads; ++j, ++i) {
387 struct evtr_thread *tmp;
389 /* tmp holds the thread we replace */
390 tmp = ctx->top_threads[j];
391 ctx->top_threads[j] = td;
394 * Our thread was already in the top list,
395 * and we just removed the second instance.
396 * Nothing more to do.
408 ctxsw_prepare_event(void *_ctx, evtr_event_t ev)
410 struct td_switch_ctx *ctx = _ctx;
411 struct cpu *c, *cpus = ctx->cputab.cpus;
412 struct thread_info *tdi;
415 printd(INTV, "test1 (%ju:%ju) : %ju\n",
416 (uintmax_t)ctx->interval.start,
417 (uintmax_t)ctx->interval.end,
419 if ((ev->ts > ctx->interval.end) ||
420 (ev->ts < ctx->interval.start))
422 printd(INTV, "PREPEV on %d\n", ev->cpu);
424 /* update first/last timestamps */
426 if (!c->firstlast.start) {
427 c->firstlast.start = ev->ts;
429 c->firstlast.end = ev->ts;
431 * c->td can be null when this is the first ctxsw event we
435 /* update thread stats */
436 if (!c->td->userdata) {
437 if (!(tdi = malloc(sizeof(struct thread_info))))
438 err(1, "Out of memory");
439 c->td->userdata = tdi;
442 tdi = c->td->userdata;
443 tdi->runtime += ev->ts - c->ts;
444 top_threads_update(ctx, c->td);
447 /* Notice that ev->td is the new thread for ctxsw events */
454 find_first_last_ts(struct cpu_table *cputab, struct ts_interval *fl)
456 struct cpu *cpus = &cputab->cpus[0];
461 for (i = 0; i < cputab->ncpus; ++i) {
462 printd(INTV, "cpu%d: (%ju, %ju)\n", i,
463 (uintmax_t)cpus[i].firstlast.start,
464 (uintmax_t)cpus[i].firstlast.end);
465 if (cpus[i].firstlast.start &&
466 (cpus[i].firstlast.start < fl->start))
467 fl->start = cpus[i].firstlast.start;
468 if (cpus[i].firstlast.end &&
469 (cpus[i].firstlast.end > fl->end))
470 fl->end = cpus[i].firstlast.end;
474 printd(INTV, "global (%jd, %jd)\n", (uintmax_t)fl->start, (uintmax_t)fl->end);
479 ctxsw_prepare_post(void *_ctx)
481 struct td_switch_ctx *ctx = _ctx;
483 find_first_last_ts(&ctx->cputab, &ctx->firstlast);
488 ctxsw_draw_pre(void *_ctx)
490 struct td_switch_ctx *ctx = _ctx;
491 struct svg_transform textrot;
493 double y, height, fs;
495 struct evtr_thread *td;
497 textrot.tx = 0.0 - 0.2; /* XXX */
502 for (i = 0; i < ctx->nr_top_threads; ++i) {
503 td = ctx->top_threads[i];
506 rows_n(ctx->thread_rows, i, &y, &height);
507 svg_rect_draw(ctx->svg, ctx->inactive_rect, 0.0,
508 y + ctx->thread_rows_yoff, ctx->width, height);
509 textlen = snprintf(comm, sizeof(comm) - 1, "%s (%p)",
511 if (textlen > (int)sizeof(comm))
512 textlen = sizeof(comm) - 1;
513 comm[sizeof(comm) - 1] = '\0';
514 fs = fontsize_for_rect(height, 100.0, textlen);
516 textrot.ty = y + ctx->thread_rows_yoff + height;
517 svg_text_draw(ctx->svg, ctx->thread_label, &textrot,
524 ctxsw_draw_event(void *_ctx, evtr_event_t ev)
526 struct td_switch_ctx *ctx = _ctx;
527 struct cpu *c = &ctx->cputab.cpus[ev->cpu];
531 * ctx->firstlast.end can be 0 if there were no events
532 * in the specified interval, in which case
533 * ctx->firstlast.start is invalid too.
535 assert(!ctx->firstlast.end || (ev->ts >= ctx->firstlast.start));
536 printd(INTV, "test2 (%ju:%ju) : %ju\n", (uintmax_t)ctx->interval.start,
537 (uintmax_t)ctx->interval.end, (uintmax_t)ev->ts);
538 if ((ev->ts > ctx->interval.end) ||
539 (ev->ts < ctx->interval.start))
541 printd(INTV, "DRAWEV %d\n", ev->cpu);
542 if (c->td != ev->td) { /* thread switch (or preemption) */
543 draw_ctx_switch(ctx, c, ev);
544 /* XXX: this is silly */
545 for (i = 0; i < ctx->nr_top_threads; ++i) {
546 if (!ctx->top_threads[i])
548 if (ctx->top_threads[i] == c->td) {
549 draw_thread_run(ctx, c, ev, i);
560 cputab_init(struct cpu_table *ct)
566 if ((ct->ncpus = evtr_ncpus(evtr)) <= 0)
567 err(1, "No cpu information!\n");
568 printd(MISC, "evtranalyze: ncpus %d\n", ct->ncpus);
569 if (!(ct->cpus = malloc(sizeof(struct cpu) * ct->ncpus))) {
570 err(1, "Can't allocate memory\n");
573 if (!(freqs = malloc(sizeof(double) * ct->ncpus))) {
574 err(1, "Can't allocate memory\n");
576 if ((i = evtr_cpufreqs(evtr, freqs))) {
577 warnc(i, "Can't get cpu frequencies\n");
578 for (i = 0; i < ct->ncpus; ++i) {
583 /* initialize cpu array */
584 for (i = 0; i < ct->ncpus; ++i) {
588 cpus[i].firstlast.start = 0;
589 cpus[i].firstlast.end = 0;
591 cpus[i].freq = freqs[i];
598 parse_interval(const char *_str, struct ts_interval *ts,
599 struct cpu_table *cputab)
602 const char *str = _str + 1;
604 if ('c' == *_str) { /* cycles */
605 if (sscanf(str, "%" SCNu64 ":%" SCNu64,
609 } else if ('m' == *_str) { /* miliseconds */
610 if (sscanf(str, "%lf:%lf", &s, &e) == 2) {
611 freq = cputab->cpus[0].freq;
612 freq *= 1000.0; /* msecs */
614 fprintf(stderr, "No frequency information"
616 err(2, "Can't convert time to cycles\n");
618 ts->start = s * freq;
623 fprintf(stderr, "invalid interval format: %s\n", _str);
630 cmd_svg(int argc, char **argv)
634 double height, width;
635 struct rows cpu_rows, thread_rows;
636 struct td_switch_ctx td_ctx;
637 const char *outpath = "output.svg";
638 struct evtr_filter ctxsw_filts[2] = {
648 struct pass_hook ctxsw_prepare = {
650 .event = ctxsw_prepare_event,
651 .post = ctxsw_prepare_post,
653 .filts = ctxsw_filts,
654 .nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
656 .pre = ctxsw_draw_pre,
657 .event = ctxsw_draw_event,
660 .filts = ctxsw_filts,
661 .nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
665 * We are interested in thread switch and preemption
666 * events, but we don't use the data directly. Instead
669 ctxsw_filts[0].fmt = "sw %p > %p";
670 ctxsw_filts[1].fmt = "pre %p > %p";
671 td_ctx.interval.start = 0;
672 td_ctx.interval.end = -1; /* i.e. no interval given */
673 td_ctx.nr_top_threads = NR_TOP_THREADS;
674 cputab_init(&td_ctx.cputab); /* needed for parse_interval() */
678 while ((ch = getopt(argc, argv, "i:o:")) != -1) {
681 parse_interval(optarg, &td_ctx.interval,
697 td_ctx.width = width;
699 if (!(td_ctx.top_threads = calloc(td_ctx.nr_top_threads,
700 sizeof(struct evtr_thread *))))
701 err(1, "Can't allocate memory\n");
702 if (!(svg = svg_document_create(outpath)))
703 err(1, "Can't open svg document\n");
706 * Create rectangles to use for output.
708 if (!(td_ctx.cpu_sw_rect = svg_rect_new("generic")))
709 err(1, "Can't create rectangle\n");
710 if (!(td_ctx.thread_rect = svg_rect_new("thread")))
711 err(1, "Can't create rectangle\n");
712 if (!(td_ctx.inactive_rect = svg_rect_new("inactive")))
713 err(1, "Can't create rectangle\n");
714 /* text for thread names */
715 if (!(td_ctx.thread_label = svg_text_new("generic")))
716 err(1, "Can't create text\n");
717 rows_init(&cpu_rows, td_ctx.cputab.ncpus, height, 0.9);
719 td_ctx.xscale = -1.0;
720 td_ctx.cpu_rows = &cpu_rows;
722 do_pass(&ctxsw_prepare, 1);
723 td_ctx.thread_rows_yoff = height;
724 td_ctx.thread_rows = &thread_rows;
725 rows_init(td_ctx.thread_rows, td_ctx.nr_top_threads, 300, 0.9);
726 td_ctx.xscale = width / (td_ctx.firstlast.end - td_ctx.firstlast.start);
727 printd(SVG, "first %ju, last %ju, xscale %lf\n",
728 (uintmax_t)td_ctx.firstlast.start, (uintmax_t)td_ctx.firstlast.end,
731 do_pass(&ctxsw_draw, 1);
733 svg_document_close(svg);
739 cmd_show(int argc, char **argv)
741 struct evtr_event ev;
742 struct evtr_query *q;
743 struct evtr_filter filt;
744 struct cpu_table cputab;
747 uint64_t last_ts = 0;
749 cputab_init(&cputab);
751 * Assume all cores run on the same frequency
752 * for now. There's no reason to complicate
753 * things unless we can detect frequency change
756 * Note that the code is very simplistic and will
757 * produce garbage if the kernel doesn't fixup
758 * the timestamps for cores running with different
761 freq = cputab.cpus[0].freq;
762 freq /= 1000000; /* we want to print out usecs */
763 printd(MISC, "using freq = %lf\n", freq);
767 while ((ch = getopt(argc, argv, "f:")) != -1) {
776 q = evtr_query_init(evtr, &filt, 1);
778 err(1, "Can't initialize query\n");
779 while(!evtr_query_next(q, &ev)) {
785 printf("%s\t%llu cycles\t[%.3d]\t%s:%d",
786 ev.td ? ev.td->comm : "unknown",
787 ev.ts - last_ts, ev.cpu,
788 basename(ev.file), ev.line);
790 printf("%s\t%.3lf usecs\t[%.3d]\t%s:%d",
791 ev.td ? ev.td->comm : "unknown",
792 (ev.ts - last_ts) / freq, ev.cpu,
793 basename(ev.file), ev.line);
796 evtr_event_data(&ev, buf, sizeof(buf));
797 printf(" !\t%s\n", buf);
803 if (evtr_error(evtr)) {
804 err(1, evtr_errmsg(evtr));
806 evtr_query_destroy(q);
812 cmd_summary(int argc, char **argv)
814 struct evtr_filter filt;
815 struct evtr_event ev;
816 struct evtr_query *q;
818 struct cpu_table cputab;
819 struct ts_interval global;
820 uintmax_t global_evcnt;
826 cputab_init(&cputab);
831 q = evtr_query_init(evtr, &filt, 1);
833 err(1, "Can't initialize query\n");
834 while(!evtr_query_next(q, &ev)) {
835 struct cpu *c = &cputab.cpus[ev.cpu];
836 if (!c->firstlast.start)
837 c->firstlast.start = ev.ts;
839 c->firstlast.end = ev.ts;
841 if (evtr_error(evtr)) {
842 err(1, evtr_errmsg(evtr));
844 evtr_query_destroy(q);
846 find_first_last_ts(&cputab, &global);
848 freq = cputab.cpus[0].freq;
850 for (i = 0; i < cputab.ncpus; ++i) {
851 struct cpu *c = &cputab.cpus[i];
852 printf("CPU %d: %jd events in %.3lf secs\n", i,
853 c->evcnt, (c->firstlast.end - c->firstlast.start)
855 global_evcnt += c->evcnt;
857 printf("Total: %jd events on %d cpus in %.3lf secs\n", global_evcnt,
858 cputab.ncpus, (global.end - global.start) / freq);
865 main(int argc, char **argv)
872 while ((ch = getopt(argc, argv, "f:D:")) != -1) {
878 if ((tmp = strchr(optarg, ':'))) {
882 printd_set_flags(optarg, &evtranalyze_debug);
892 err(2, "need to specify a command\n");
894 if (!opt_infile || !strcmp(opt_infile, "-")) {
897 inf = fopen(opt_infile, "r");
899 err(2, "Can't open input file\n");
903 if (!(evtr = evtr_open_read(inf))) {
904 err(1, "Can't open evtr stream\n");
908 for (cmd = commands; cmd->name != NULL; ++cmd) {
909 if (strcmp(argv[0], cmd->name))
911 cmd->func(argc, argv);
915 err(2, "no such command: %s\n", argv[0]);