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;
184 struct evtr_thread **top_threads;
186 double thread_rows_yoff;
190 struct evtr_thread *td;
191 int i; /* cpu index */
192 uint64_t ts; /* time cpu switched to td */
193 uint64_t first_ts, last_ts;
198 do_pass(struct pass_hook *hooks, int nhooks)
200 struct evtr_filter *filts = NULL;
202 struct evtr_query *q;
203 struct evtr_event ev;
205 for (i = 0; i < nhooks; ++i) {
206 struct pass_hook *h = &hooks[i];
210 filts = realloc(filts, (nfilts + h->nfilts) *
211 sizeof(struct evtr_filter));
213 err(1, "Out of memory");
214 memcpy(filts + nfilts, &h->filts,
215 h->nfilts * sizeof(struct evtr_filter));
219 q = evtr_query_init(evtr, filts, nfilts);
221 err(1, "Can't initialize query\n");
222 while(!evtr_query_next(q, &ev)) {
223 for (i = 0; i < nhooks; ++i) {
225 hooks[i].event(hooks[i].data, &ev);
228 if (evtr_error(evtr)) {
229 err(1, evtr_errmsg(evtr));
231 evtr_query_destroy(q);
233 for (i = 0; i < nhooks; ++i) {
235 hooks[i].post(hooks[i].data);
237 if (evtr_rewind(evtr))
238 err(1, "Can't rewind event stream\n");
243 draw_thread_run(struct td_switch_ctx *ctx, struct cpu *c, evtr_event_t ev, int row)
245 double x, w, y, height;
246 w = (ev->ts - c->ts) * ctx->xscale;
247 x = (ev->ts - ctx->first_ts) * ctx->xscale;
248 rows_n(ctx->thread_rows, row, &y, &height);
249 svg_rect_draw(ctx->svg, ctx->thread_rect, x - w,
250 y + ctx->thread_rows_yoff, w, height);
255 draw_ctx_switch(struct td_switch_ctx *ctx, struct cpu *c, evtr_event_t ev)
257 struct svg_transform textrot;
259 double x, w, fs, y, height;
262 assert(ctx->xscale > 0.0);
265 /* distance to previous context switch */
266 w = (ev->ts - c->ts) * ctx->xscale;
267 x = (ev->ts - ctx->first_ts) * ctx->xscale;
269 fprintf(stderr, "(%llu - %llu) * %.20lf\n", ev->ts,
270 ctx->first_ts, ctx->xscale);
274 rows_n(ctx->cpu_rows, c->i, &y, &height);
276 assert(!isnan(height));
278 svg_rect_draw(ctx->svg, ctx->cpu_sw_rect, x - w, y, w, height);
281 * Draw the text label describing the thread we
289 textlen = snprintf(comm, sizeof(comm) - 1, "%s (%p)",
290 c->td ? c->td->comm : "unknown",
291 c->td ? c->td->id: NULL);
292 if (textlen > (int)sizeof(comm))
293 textlen = sizeof(comm) - 1;
294 comm[sizeof(comm) - 1] = '\0';
296 * Note the width and hight are exchanged because
297 * the bounding rectangle is rotated by 90 degrees.
299 fs = fontsize_for_rect(height, w, textlen);
300 svg_text_draw(ctx->svg, ctx->thread_label, &textrot, comm,
306 * The stats for ntd have changed, update ->top_threads
310 top_threads_update(struct td_switch_ctx *ctx, struct evtr_thread *ntd)
312 struct thread_info *tdi = ntd->userdata;
314 for (i = 0; i < ctx->nr_top_threads; ++i) {
315 struct evtr_thread *td = ctx->top_threads[i];
318 * ntd is already in top_threads and it is at
319 * the correct ranking
324 /* empty slot -- just insert our thread */
325 ctx->top_threads[i] = ntd;
328 if (((struct thread_info *)td->userdata)->runtime >=
330 /* this thread ranks higher than we do. Move on */
334 * OK, we've found the first thread that we outrank, so we
335 * need to replace it w/ our thread.
337 td = ntd; /* td holds the thread we will insert next */
338 for (j = i + 1; j < ctx->nr_top_threads; ++j, ++i) {
339 struct evtr_thread *tmp;
341 /* tmp holds the thread we replace */
342 tmp = ctx->top_threads[j];
343 ctx->top_threads[j] = td;
346 * Our thread was already in the top list,
347 * and we just removed the second instance.
348 * Nothing more to do.
360 ctxsw_prepare_event(void *_ctx, evtr_event_t ev)
362 struct td_switch_ctx *ctx = _ctx;
363 struct cpu *c, *cpus = ctx->cpus;
364 struct thread_info *tdi;
367 printd("test1 (%llu:%llu) : %llu\n", ctx->interval_start,
368 ctx->interval_end, ev->ts);
369 if ((ev->ts > ctx->interval_end) ||
370 (ev->ts < ctx->interval_start))
374 /* update first/last timestamps */
377 c->first_ts = ev->ts;
378 printd("setting first_ts (%d) = %llu\n", ev->cpu,
383 * c->td can be null when this is the first ctxsw event we
387 /* update thread stats */
388 if (!c->td->userdata) {
389 if (!(tdi = malloc(sizeof(struct thread_info))))
390 err(1, "Out of memory");
391 c->td->userdata = tdi;
394 tdi = c->td->userdata;
395 tdi->runtime += ev->ts - c->ts;
396 printd("EVCPU %d\n", c->i);
397 top_threads_update(ctx, c->td);
400 /* Notice that ev->td is the new thread for ctxsw events */
407 ctxsw_prepare_post(void *_ctx)
409 struct td_switch_ctx *ctx = _ctx;
410 struct cpu *cpus = ctx->cpus;
416 printd("first_ts[0] = %llu\n",cpus[0].first_ts);
417 for (i = 0; i < ctx->ncpus; ++i) {
418 printd("first_ts[%d] = %llu\n", i, cpus[i].first_ts);
419 if (cpus[i].first_ts && (cpus[i].first_ts < ctx->first_ts))
420 ctx->first_ts = cpus[i].first_ts;
421 if (cpus[i].last_ts && (cpus[i].last_ts > ctx->last_ts))
422 ctx->last_ts = cpus[i].last_ts;
430 ctxsw_draw_pre(void *_ctx)
432 struct td_switch_ctx *ctx = _ctx;
433 struct svg_transform textrot;
435 double y, height, fs;
437 struct evtr_thread *td;
439 textrot.tx = 0.0 - 0.2; /* XXX */
444 for (i = 0; i < ctx->nr_top_threads; ++i) {
445 td = ctx->top_threads[i];
446 rows_n(ctx->thread_rows, i, &y, &height);
447 svg_rect_draw(ctx->svg, ctx->inactive_rect, 0.0,
448 y + ctx->thread_rows_yoff, ctx->width, height);
449 textlen = snprintf(comm, sizeof(comm) - 1, "%s (%p)",
451 if (textlen > (int)sizeof(comm))
452 textlen = sizeof(comm) - 1;
453 comm[sizeof(comm) - 1] = '\0';
454 fs = fontsize_for_rect(height, 100.0, textlen);
456 textrot.ty = y + ctx->thread_rows_yoff + height;
457 svg_text_draw(ctx->svg, ctx->thread_label, &textrot,
464 ctxsw_draw_event(void *_ctx, evtr_event_t ev)
466 struct td_switch_ctx *ctx = _ctx;
467 struct cpu *c = &ctx->cpus[ev->cpu];
471 * ctx->last_ts can be 0 if there were no events
472 * in the specified interval, in which case
473 * ctx->first_ts is invalid too.
475 assert(!ctx->last_ts || (ev->ts >= ctx->first_ts));
476 printd("test2 (%llu:%llu) : %llu\n", ctx->interval_start,
477 ctx->interval_end, ev->ts);
478 if ((ev->ts > ctx->interval_end) ||
479 (ev->ts < ctx->interval_start))
481 printd("SPEV %d\n", ev->cpu);
482 if (c->td != ev->td) { /* thread switch (or preemption) */
483 draw_ctx_switch(ctx, c, ev);
484 /* XXX: this is silly */
485 for (i = 0; i < ctx->nr_top_threads; ++i) {
486 if (ctx->top_threads[i] == c->td) {
487 draw_thread_run(ctx, c, ev, i);
498 cmd_svg(int argc, char **argv)
502 double height, width;
503 struct rows cpu_rows, thread_rows;
505 struct td_switch_ctx td_ctx;
506 struct evtr_filter ctxsw_filts[2] = {
516 struct pass_hook ctxsw_prepare = {
518 .event = ctxsw_prepare_event,
519 .post = ctxsw_prepare_post,
521 .filts = ctxsw_filts,
522 .nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
524 .pre = ctxsw_draw_pre,
525 .event = ctxsw_draw_event,
528 .filts = ctxsw_filts,
529 .nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
533 * We are interested in thread switch and preemption
534 * events, but we don't use the data directly. Instead
537 ctxsw_filts[0].fmt = "sw %p > %p";
538 ctxsw_filts[1].fmt = "pre %p > %p";
539 td_ctx.interval_start = 0;
540 td_ctx.interval_end = -1; /* i.e. no interval given */
541 td_ctx.nr_top_threads = NR_TOP_THREADS;
543 printd("argc: %d, argv[0] = %s\n", argc, argv[0] ? argv[0] : "NULL");
546 while ((ch = getopt(argc, argv, "i:")) != -1) {
549 if (sscanf(optarg, "%llu:%llu", &td_ctx.interval_start,
550 &td_ctx.interval_end) != 2) {
564 td_ctx.width = width;
565 if ((ncpus = evtr_ncpus(evtr)) <= 0)
566 err(1, "No cpu information!\n");
567 printd("evtranalyze: ncpus %d\n", ncpus);
569 if (!(cpus = malloc(ncpus * sizeof(struct cpu))))
570 err(1, "Can't allocate memory\n");
571 /* initialize cpu array */
572 for (i = 0; i < ncpus; ++i) {
576 cpus[i].first_ts = 0;
580 td_ctx.ncpus = ncpus;
581 if (!(td_ctx.top_threads = calloc(td_ctx.nr_top_threads, sizeof(struct evtr_thread *))))
582 err(1, "Can't allocate memory\n");
583 if (!(svg = svg_document_create("output.svg")))
584 err(1, "Can't open svg document\n");
587 * Create rectangles to use for output.
589 if (!(td_ctx.cpu_sw_rect = svg_rect_new("generic")))
590 err(1, "Can't create rectangle\n");
591 if (!(td_ctx.thread_rect = svg_rect_new("thread")))
592 err(1, "Can't create rectangle\n");
593 if (!(td_ctx.inactive_rect = svg_rect_new("inactive")))
594 err(1, "Can't create rectangle\n");
595 /* text for thread names */
596 if (!(td_ctx.thread_label = svg_text_new("generic")))
597 err(1, "Can't create text\n");
598 rows_init(&cpu_rows, ncpus, height, 0.9);
600 td_ctx.xscale = -1.0;
601 td_ctx.cpu_rows = &cpu_rows;
603 do_pass(&ctxsw_prepare, 1);
604 td_ctx.thread_rows_yoff = height;
605 td_ctx.thread_rows = &thread_rows;
606 rows_init(td_ctx.thread_rows, td_ctx.nr_top_threads, 300, 0.9);
607 td_ctx.xscale = width / (td_ctx.last_ts - td_ctx.first_ts);
608 printd("first %llu, last %llu, xscale %lf\n", td_ctx.first_ts,
609 td_ctx.last_ts, td_ctx.xscale);
611 do_pass(&ctxsw_draw, 1);
613 svg_document_close(svg);
619 cmd_show(int argc, char **argv)
621 struct evtr_event ev;
622 struct evtr_query *q;
623 struct evtr_filter filt;
629 while ((ch = getopt(argc, argv, "f:")) != -1) {
638 printd("fmt = %s\n", filt.fmt ? filt.fmt : "NULL");
639 q = evtr_query_init(evtr, &filt, 1);
641 err(1, "Can't initialize query\n");
642 while(!evtr_query_next(q, &ev)) {
644 printf("%s\t%llu cycles\t[%.3d]\t%s:%d",
645 ev.td ? ev.td->comm : "unknown",
647 basename(ev.file), ev.line);
649 evtr_event_data(&ev, buf, sizeof(buf));
650 printf(" !\t%s\n", buf);
655 if (evtr_error(evtr)) {
656 err(1, evtr_errmsg(evtr));
658 evtr_query_destroy(q);
663 main(int argc, char **argv)
669 while ((ch = getopt(argc, argv, "f:D:")) != -1) {
675 evtranalyze_debug = atoi(optarg);
676 evtr_set_debug(evtranalyze_debug);
686 err(2, "need to specify a command\n");
688 if (!opt_infile || !strcmp(opt_infile, "-")) {
691 inf = fopen(opt_infile, "r");
693 err(2, "Can't open input file\n");
697 if (!(evtr = evtr_open_read(inf))) {
698 err(1, "Can't open evtr stream\n");
702 for (cmd = commands; cmd->name != NULL; ++cmd) {
703 if (strcmp(argv[0], cmd->name))
705 cmd->func(argc, argv);
709 err(2, "no such command: %s\n", argv[0]);