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))
372 printd("PREPEV on %d\n", ev->cpu);
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 top_threads_update(ctx, c->td);
399 /* Notice that ev->td is the new thread for ctxsw events */
406 ctxsw_prepare_post(void *_ctx)
408 struct td_switch_ctx *ctx = _ctx;
409 struct cpu *cpus = ctx->cpus;
415 printd("first_ts[0] = %llu\n",cpus[0].first_ts);
416 for (i = 0; i < ctx->ncpus; ++i) {
417 printd("first_ts[%d] = %llu\n", i, cpus[i].first_ts);
418 if (cpus[i].first_ts && (cpus[i].first_ts < ctx->first_ts))
419 ctx->first_ts = cpus[i].first_ts;
420 if (cpus[i].last_ts && (cpus[i].last_ts > ctx->last_ts))
421 ctx->last_ts = cpus[i].last_ts;
429 ctxsw_draw_pre(void *_ctx)
431 struct td_switch_ctx *ctx = _ctx;
432 struct svg_transform textrot;
434 double y, height, fs;
436 struct evtr_thread *td;
438 textrot.tx = 0.0 - 0.2; /* XXX */
443 for (i = 0; i < ctx->nr_top_threads; ++i) {
444 td = ctx->top_threads[i];
445 rows_n(ctx->thread_rows, i, &y, &height);
446 svg_rect_draw(ctx->svg, ctx->inactive_rect, 0.0,
447 y + ctx->thread_rows_yoff, ctx->width, height);
448 textlen = snprintf(comm, sizeof(comm) - 1, "%s (%p)",
450 if (textlen > (int)sizeof(comm))
451 textlen = sizeof(comm) - 1;
452 comm[sizeof(comm) - 1] = '\0';
453 fs = fontsize_for_rect(height, 100.0, textlen);
455 textrot.ty = y + ctx->thread_rows_yoff + height;
456 svg_text_draw(ctx->svg, ctx->thread_label, &textrot,
463 ctxsw_draw_event(void *_ctx, evtr_event_t ev)
465 struct td_switch_ctx *ctx = _ctx;
466 struct cpu *c = &ctx->cpus[ev->cpu];
470 * ctx->last_ts can be 0 if there were no events
471 * in the specified interval, in which case
472 * ctx->first_ts is invalid too.
474 assert(!ctx->last_ts || (ev->ts >= ctx->first_ts));
475 printd("test2 (%llu:%llu) : %llu\n", ctx->interval_start,
476 ctx->interval_end, ev->ts);
477 if ((ev->ts > ctx->interval_end) ||
478 (ev->ts < ctx->interval_start))
480 printd("DRAWEV %d\n", ev->cpu);
481 if (c->td != ev->td) { /* thread switch (or preemption) */
482 draw_ctx_switch(ctx, c, ev);
483 /* XXX: this is silly */
484 for (i = 0; i < ctx->nr_top_threads; ++i) {
485 if (ctx->top_threads[i] == c->td) {
486 draw_thread_run(ctx, c, ev, i);
497 cmd_svg(int argc, char **argv)
501 double height, width;
502 struct rows cpu_rows, thread_rows;
504 struct td_switch_ctx td_ctx;
505 struct evtr_filter ctxsw_filts[2] = {
515 struct pass_hook ctxsw_prepare = {
517 .event = ctxsw_prepare_event,
518 .post = ctxsw_prepare_post,
520 .filts = ctxsw_filts,
521 .nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
523 .pre = ctxsw_draw_pre,
524 .event = ctxsw_draw_event,
527 .filts = ctxsw_filts,
528 .nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
532 * We are interested in thread switch and preemption
533 * events, but we don't use the data directly. Instead
536 ctxsw_filts[0].fmt = "sw %p > %p";
537 ctxsw_filts[1].fmt = "pre %p > %p";
538 td_ctx.interval_start = 0;
539 td_ctx.interval_end = -1; /* i.e. no interval given */
540 td_ctx.nr_top_threads = NR_TOP_THREADS;
542 printd("argc: %d, argv[0] = %s\n", argc, argv[0] ? argv[0] : "NULL");
545 while ((ch = getopt(argc, argv, "i:")) != -1) {
548 if (sscanf(optarg, "%llu:%llu", &td_ctx.interval_start,
549 &td_ctx.interval_end) != 2) {
563 td_ctx.width = width;
564 if ((ncpus = evtr_ncpus(evtr)) <= 0)
565 err(1, "No cpu information!\n");
566 printd("evtranalyze: ncpus %d\n", ncpus);
568 if (!(cpus = malloc(ncpus * sizeof(struct cpu))))
569 err(1, "Can't allocate memory\n");
570 /* initialize cpu array */
571 for (i = 0; i < ncpus; ++i) {
575 cpus[i].first_ts = 0;
579 td_ctx.ncpus = ncpus;
580 if (!(td_ctx.top_threads = calloc(td_ctx.nr_top_threads, sizeof(struct evtr_thread *))))
581 err(1, "Can't allocate memory\n");
582 if (!(svg = svg_document_create("output.svg")))
583 err(1, "Can't open svg document\n");
586 * Create rectangles to use for output.
588 if (!(td_ctx.cpu_sw_rect = svg_rect_new("generic")))
589 err(1, "Can't create rectangle\n");
590 if (!(td_ctx.thread_rect = svg_rect_new("thread")))
591 err(1, "Can't create rectangle\n");
592 if (!(td_ctx.inactive_rect = svg_rect_new("inactive")))
593 err(1, "Can't create rectangle\n");
594 /* text for thread names */
595 if (!(td_ctx.thread_label = svg_text_new("generic")))
596 err(1, "Can't create text\n");
597 rows_init(&cpu_rows, ncpus, height, 0.9);
599 td_ctx.xscale = -1.0;
600 td_ctx.cpu_rows = &cpu_rows;
602 do_pass(&ctxsw_prepare, 1);
603 td_ctx.thread_rows_yoff = height;
604 td_ctx.thread_rows = &thread_rows;
605 rows_init(td_ctx.thread_rows, td_ctx.nr_top_threads, 300, 0.9);
606 td_ctx.xscale = width / (td_ctx.last_ts - td_ctx.first_ts);
607 printd("first %llu, last %llu, xscale %lf\n", td_ctx.first_ts,
608 td_ctx.last_ts, td_ctx.xscale);
610 do_pass(&ctxsw_draw, 1);
612 svg_document_close(svg);
618 cmd_show(int argc, char **argv)
620 struct evtr_event ev;
621 struct evtr_query *q;
622 struct evtr_filter filt;
628 while ((ch = getopt(argc, argv, "f:")) != -1) {
637 printd("fmt = %s\n", filt.fmt ? filt.fmt : "NULL");
638 q = evtr_query_init(evtr, &filt, 1);
640 err(1, "Can't initialize query\n");
641 while(!evtr_query_next(q, &ev)) {
643 printf("%s\t%llu cycles\t[%.3d]\t%s:%d",
644 ev.td ? ev.td->comm : "unknown",
646 basename(ev.file), ev.line);
648 evtr_event_data(&ev, buf, sizeof(buf));
649 printf(" !\t%s\n", buf);
654 if (evtr_error(evtr)) {
655 err(1, evtr_errmsg(evtr));
657 evtr_query_destroy(q);
662 main(int argc, char **argv)
668 while ((ch = getopt(argc, argv, "f:D:")) != -1) {
674 evtranalyze_debug = atoi(optarg);
675 evtr_set_debug(evtranalyze_debug);
685 err(2, "need to specify a command\n");
687 if (!opt_infile || !strcmp(opt_infile, "-")) {
690 inf = fopen(opt_infile, "r");
692 err(2, "Can't open input file\n");
696 if (!(evtr = evtr_open_read(inf))) {
697 err(1, "Can't open evtr stream\n");
701 for (cmd = commands; cmd->name != NULL; ++cmd) {
702 if (strcmp(argv[0], cmd->name))
704 cmd->func(argc, argv);
708 err(2, "no such command: %s\n", argv[0]);