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
54 #define CMD_PROTO(name) \
55 static int cmd_ ## name(int, char **)
62 int (*func)(int argc, char **argv);
79 static int evtranalyze_debug;
83 if (evtranalyze_debug) { \
84 fprintf(stderr, __VA_ARGS__); \
92 fprintf(stderr, "bad usage :P\n");
98 rows_init(struct rows *rows, int n, double height, double perc)
101 rows->row_increment = height / n;
102 /* actual row height */
103 row_h = perc * rows->row_increment;
104 rows->row_off = (rows->row_increment - row_h) / 2.0;
105 assert(!isnan(rows->row_increment));
106 assert(!isnan(rows->row_off));
111 rows_n(struct rows *rows, int n, double *y, double *height)
113 *y = n * rows->row_increment + rows->row_off;
114 *height = rows->row_increment - 2 * rows->row_off;
118 * Which fontsize to use so that the string fits in the
123 fontsize_for_rect(double width, double height, int textlen)
127 * We start with a font size equal to the height
128 * of the rectangle and round down so that we only
129 * use a limited number of sizes.
131 * For a rectangle width comparable to the height,
132 * the text might extend outside of the rectangle.
133 * In that case we need to limit it.
135 /* available width per character */
136 wpc = width / textlen;
138 * Assuming a rough hight/width ratio for characters,
139 * calculate the available height and round it down
140 * just to be on the safe side.
142 #define GLYPH_HIGHT_TO_WIDTH 1.5
143 maxh = GLYPH_HIGHT_TO_WIDTH * wpc * 0.9;
146 } else if (height < 0.01) {
149 /* rounding (XXX: make cheaper)*/
150 height = log(height);
151 height = round(height);
152 height = exp(height);
159 void (*event)(void *, evtr_event_t);
160 void (*post)(void *);
162 struct evtr_filter *filts;
170 struct td_switch_ctx {
172 struct rows *cpu_rows;
173 struct rows *thread_rows;
174 uint64_t interval_start, interval_end;
175 uint64_t first_ts, last_ts;
177 double xscale; /* scale factor applied to x */
178 svg_rect_t cpu_sw_rect;
179 svg_rect_t thread_rect;
180 svg_rect_t inactive_rect;
181 svg_text_t thread_label;
186 struct evtr_thread **top_threads;
188 double thread_rows_yoff;
192 struct evtr_thread *td;
193 int i; /* cpu index */
194 uint64_t ts; /* time cpu switched to td */
195 uint64_t first_ts, last_ts;
201 do_pass(struct pass_hook *hooks, int nhooks)
203 struct evtr_filter *filts = NULL;
205 struct evtr_query *q;
206 struct evtr_event ev;
208 for (i = 0; i < nhooks; ++i) {
209 struct pass_hook *h = &hooks[i];
213 filts = realloc(filts, (nfilts + h->nfilts) *
214 sizeof(struct evtr_filter));
216 err(1, "Out of memory");
217 memcpy(filts + nfilts, h->filts,
218 h->nfilts * sizeof(struct evtr_filter));
222 q = evtr_query_init(evtr, filts, nfilts);
224 err(1, "Can't initialize query\n");
225 while(!evtr_query_next(q, &ev)) {
226 for (i = 0; i < nhooks; ++i) {
228 hooks[i].event(hooks[i].data, &ev);
231 if (evtr_error(evtr)) {
232 err(1, evtr_errmsg(evtr));
234 evtr_query_destroy(q);
236 for (i = 0; i < nhooks; ++i) {
238 hooks[i].post(hooks[i].data);
240 if (evtr_rewind(evtr))
241 err(1, "Can't rewind event stream\n");
246 draw_thread_run(struct td_switch_ctx *ctx, struct cpu *c, evtr_event_t ev, int row)
248 double x, w, y, height;
249 w = (ev->ts - c->ts) * ctx->xscale;
250 x = (ev->ts - ctx->first_ts) * ctx->xscale;
251 rows_n(ctx->thread_rows, row, &y, &height);
252 svg_rect_draw(ctx->svg, ctx->thread_rect, x - w,
253 y + ctx->thread_rows_yoff, w, height);
258 draw_ctx_switch(struct td_switch_ctx *ctx, struct cpu *c, evtr_event_t ev)
260 struct svg_transform textrot;
262 double x, w, fs, y, height;
265 assert(ctx->xscale > 0.0);
268 /* distance to previous context switch */
269 w = (ev->ts - c->ts) * ctx->xscale;
270 x = (ev->ts - ctx->first_ts) * ctx->xscale;
272 fprintf(stderr, "(%llu - %llu) * %.20lf\n", ev->ts,
273 ctx->first_ts, ctx->xscale);
277 rows_n(ctx->cpu_rows, c->i, &y, &height);
279 assert(!isnan(height));
281 svg_rect_draw(ctx->svg, ctx->cpu_sw_rect, x - w, y, w, height);
284 * Draw the text label describing the thread we
292 textlen = snprintf(comm, sizeof(comm) - 1, "%s (%p)",
293 c->td ? c->td->comm : "unknown",
294 c->td ? c->td->id: NULL);
295 if (textlen > (int)sizeof(comm))
296 textlen = sizeof(comm) - 1;
297 comm[sizeof(comm) - 1] = '\0';
299 * Note the width and hight are exchanged because
300 * the bounding rectangle is rotated by 90 degrees.
302 fs = fontsize_for_rect(height, w, textlen);
303 svg_text_draw(ctx->svg, ctx->thread_label, &textrot, comm,
309 * The stats for ntd have changed, update ->top_threads
313 top_threads_update(struct td_switch_ctx *ctx, struct evtr_thread *ntd)
315 struct thread_info *tdi = ntd->userdata;
317 for (i = 0; i < ctx->nr_top_threads; ++i) {
318 struct evtr_thread *td = ctx->top_threads[i];
321 * ntd is already in top_threads and it is at
322 * the correct ranking
327 /* empty slot -- just insert our thread */
328 ctx->top_threads[i] = ntd;
331 if (((struct thread_info *)td->userdata)->runtime >=
333 /* this thread ranks higher than we do. Move on */
337 * OK, we've found the first thread that we outrank, so we
338 * need to replace it w/ our thread.
340 td = ntd; /* td holds the thread we will insert next */
341 for (j = i + 1; j < ctx->nr_top_threads; ++j, ++i) {
342 struct evtr_thread *tmp;
344 /* tmp holds the thread we replace */
345 tmp = ctx->top_threads[j];
346 ctx->top_threads[j] = td;
349 * Our thread was already in the top list,
350 * and we just removed the second instance.
351 * Nothing more to do.
363 ctxsw_prepare_event(void *_ctx, evtr_event_t ev)
365 struct td_switch_ctx *ctx = _ctx;
366 struct cpu *c, *cpus = ctx->cputab.cpus;
367 struct thread_info *tdi;
370 printd("test1 (%llu:%llu) : %llu\n", ctx->interval_start,
371 ctx->interval_end, ev->ts);
372 if ((ev->ts > ctx->interval_end) ||
373 (ev->ts < ctx->interval_start))
375 printd("PREPEV on %d\n", ev->cpu);
377 /* update first/last timestamps */
380 c->first_ts = ev->ts;
381 printd("setting first_ts (%d) = %llu\n", ev->cpu,
386 * c->td can be null when this is the first ctxsw event we
390 /* update thread stats */
391 if (!c->td->userdata) {
392 if (!(tdi = malloc(sizeof(struct thread_info))))
393 err(1, "Out of memory");
394 c->td->userdata = tdi;
397 tdi = c->td->userdata;
398 tdi->runtime += ev->ts - c->ts;
399 top_threads_update(ctx, c->td);
402 /* Notice that ev->td is the new thread for ctxsw events */
409 ctxsw_prepare_post(void *_ctx)
411 struct td_switch_ctx *ctx = _ctx;
412 struct cpu *cpus = ctx->cputab.cpus;
418 printd("first_ts[0] = %llu\n",cpus[0].first_ts);
419 for (i = 0; i < ctx->cputab.ncpus; ++i) {
420 printd("first_ts[%d] = %llu\n", i, cpus[i].first_ts);
421 if (cpus[i].first_ts && (cpus[i].first_ts < ctx->first_ts))
422 ctx->first_ts = cpus[i].first_ts;
423 if (cpus[i].last_ts && (cpus[i].last_ts > ctx->last_ts))
424 ctx->last_ts = cpus[i].last_ts;
432 ctxsw_draw_pre(void *_ctx)
434 struct td_switch_ctx *ctx = _ctx;
435 struct svg_transform textrot;
437 double y, height, fs;
439 struct evtr_thread *td;
441 textrot.tx = 0.0 - 0.2; /* XXX */
446 for (i = 0; i < ctx->nr_top_threads; ++i) {
447 td = ctx->top_threads[i];
448 rows_n(ctx->thread_rows, i, &y, &height);
449 svg_rect_draw(ctx->svg, ctx->inactive_rect, 0.0,
450 y + ctx->thread_rows_yoff, ctx->width, height);
451 textlen = snprintf(comm, sizeof(comm) - 1, "%s (%p)",
453 if (textlen > (int)sizeof(comm))
454 textlen = sizeof(comm) - 1;
455 comm[sizeof(comm) - 1] = '\0';
456 fs = fontsize_for_rect(height, 100.0, textlen);
458 textrot.ty = y + ctx->thread_rows_yoff + height;
459 svg_text_draw(ctx->svg, ctx->thread_label, &textrot,
466 ctxsw_draw_event(void *_ctx, evtr_event_t ev)
468 struct td_switch_ctx *ctx = _ctx;
469 struct cpu *c = &ctx->cputab.cpus[ev->cpu];
473 * ctx->last_ts can be 0 if there were no events
474 * in the specified interval, in which case
475 * ctx->first_ts is invalid too.
477 assert(!ctx->last_ts || (ev->ts >= ctx->first_ts));
478 printd("test2 (%llu:%llu) : %llu\n", ctx->interval_start,
479 ctx->interval_end, ev->ts);
480 if ((ev->ts > ctx->interval_end) ||
481 (ev->ts < ctx->interval_start))
483 printd("DRAWEV %d\n", ev->cpu);
484 if (c->td != ev->td) { /* thread switch (or preemption) */
485 draw_ctx_switch(ctx, c, ev);
486 /* XXX: this is silly */
487 for (i = 0; i < ctx->nr_top_threads; ++i) {
488 if (ctx->top_threads[i] == c->td) {
489 draw_thread_run(ctx, c, ev, i);
500 cputab_init(struct cpu_table *ct)
506 if ((ct->ncpus = evtr_ncpus(evtr)) <= 0)
507 err(1, "No cpu information!\n");
508 printd("evtranalyze: ncpus %d\n", ct->ncpus);
509 if (!(ct->cpus = malloc(sizeof(struct cpu) * ct->ncpus))) {
510 err(1, "Can't allocate memory\n");
513 if (!(freqs = malloc(sizeof(double) * ct->ncpus))) {
514 err(1, "Can't allocate memory\n");
516 if ((i = evtr_cpufreqs(evtr, freqs))) {
517 warnc(i, "Can't get cpu frequencies\n");
518 for (i = 0; i < ct->ncpus; ++i) {
523 /* initialize cpu array */
524 for (i = 0; i < ct->ncpus; ++i) {
528 cpus[i].first_ts = 0;
530 cpus[i].freq = freqs[i];
538 cmd_svg(int argc, char **argv)
542 double height, width;
543 struct rows cpu_rows, thread_rows;
544 struct td_switch_ctx td_ctx;
545 struct evtr_filter ctxsw_filts[2] = {
555 struct pass_hook ctxsw_prepare = {
557 .event = ctxsw_prepare_event,
558 .post = ctxsw_prepare_post,
560 .filts = ctxsw_filts,
561 .nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
563 .pre = ctxsw_draw_pre,
564 .event = ctxsw_draw_event,
567 .filts = ctxsw_filts,
568 .nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
572 * We are interested in thread switch and preemption
573 * events, but we don't use the data directly. Instead
576 ctxsw_filts[0].fmt = "sw %p > %p";
577 ctxsw_filts[1].fmt = "pre %p > %p";
578 td_ctx.interval_start = 0;
579 td_ctx.interval_end = -1; /* i.e. no interval given */
580 td_ctx.nr_top_threads = NR_TOP_THREADS;
582 printd("argc: %d, argv[0] = %s\n", argc, argv[0] ? argv[0] : "NULL");
585 while ((ch = getopt(argc, argv, "i:")) != -1) {
588 if (sscanf(optarg, "%llu:%llu", &td_ctx.interval_start,
589 &td_ctx.interval_end) != 2) {
603 td_ctx.width = width;
605 cputab_init(&td_ctx.cputab);
606 if (!(td_ctx.top_threads = calloc(td_ctx.nr_top_threads, sizeof(struct evtr_thread *))))
607 err(1, "Can't allocate memory\n");
608 if (!(svg = svg_document_create("output.svg")))
609 err(1, "Can't open svg document\n");
612 * Create rectangles to use for output.
614 if (!(td_ctx.cpu_sw_rect = svg_rect_new("generic")))
615 err(1, "Can't create rectangle\n");
616 if (!(td_ctx.thread_rect = svg_rect_new("thread")))
617 err(1, "Can't create rectangle\n");
618 if (!(td_ctx.inactive_rect = svg_rect_new("inactive")))
619 err(1, "Can't create rectangle\n");
620 /* text for thread names */
621 if (!(td_ctx.thread_label = svg_text_new("generic")))
622 err(1, "Can't create text\n");
623 rows_init(&cpu_rows, td_ctx.cputab.ncpus, height, 0.9);
625 td_ctx.xscale = -1.0;
626 td_ctx.cpu_rows = &cpu_rows;
628 do_pass(&ctxsw_prepare, 1);
629 td_ctx.thread_rows_yoff = height;
630 td_ctx.thread_rows = &thread_rows;
631 rows_init(td_ctx.thread_rows, td_ctx.nr_top_threads, 300, 0.9);
632 td_ctx.xscale = width / (td_ctx.last_ts - td_ctx.first_ts);
633 printd("first %llu, last %llu, xscale %lf\n", td_ctx.first_ts,
634 td_ctx.last_ts, td_ctx.xscale);
636 do_pass(&ctxsw_draw, 1);
638 svg_document_close(svg);
644 cmd_show(int argc, char **argv)
646 struct evtr_event ev;
647 struct evtr_query *q;
648 struct evtr_filter filt;
649 struct cpu_table cputab;
652 uint64_t last_ts = 0;
654 cputab_init(&cputab);
656 * Assume all cores run on the same frequency
657 * for now. There's no reason to complicate
658 * things unless we can detect frequency change
661 * Note that the code is very simplistic and will
662 * produce garbage if the kernel doesn't fixup
663 * the timestamps for cores running with different
666 freq = cputab.cpus[0].freq;
667 freq /= 1000000; /* we want to print out usecs */
668 printd("using freq = %lf\n", freq);
672 while ((ch = getopt(argc, argv, "f:")) != -1) {
681 printd("fmt = %s\n", filt.fmt ? filt.fmt : "NULL");
682 q = evtr_query_init(evtr, &filt, 1);
684 err(1, "Can't initialize query\n");
685 while(!evtr_query_next(q, &ev)) {
691 printf("%s\t%llu cycles\t[%.3d]\t%s:%d",
692 ev.td ? ev.td->comm : "unknown",
693 ev.ts - last_ts, ev.cpu,
694 basename(ev.file), ev.line);
696 printf("%s\t%.3lf usecs\t[%.3d]\t%s:%d",
697 ev.td ? ev.td->comm : "unknown",
698 (ev.ts - last_ts) / freq, ev.cpu,
699 basename(ev.file), ev.line);
702 evtr_event_data(&ev, buf, sizeof(buf));
703 printf(" !\t%s\n", buf);
709 if (evtr_error(evtr)) {
710 err(1, evtr_errmsg(evtr));
712 evtr_query_destroy(q);
717 main(int argc, char **argv)
723 while ((ch = getopt(argc, argv, "f:D:")) != -1) {
729 evtranalyze_debug = atoi(optarg);
730 evtr_set_debug(evtranalyze_debug);
740 err(2, "need to specify a command\n");
742 if (!opt_infile || !strcmp(opt_infile, "-")) {
745 inf = fopen(opt_infile, "r");
747 err(2, "Can't open input file\n");
751 if (!(evtr = evtr_open_read(inf))) {
752 err(1, "Can't open evtr stream\n");
756 for (cmd = commands; cmd->name != NULL; ++cmd) {
757 if (strcmp(argv[0], cmd->name))
759 cmd->func(argc, argv);
763 err(2, "no such command: %s\n", argv[0]);