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
57 #define CMD_PROTO(name) \
58 static int cmd_ ## name(int, char **)
66 int (*func)(int argc, char **argv);
87 unsigned evtranalyze_debug;
91 printd_set_flags(const char *str, unsigned int *flags)
94 * This is suboptimal as we don't detect
103 err(2, "invalid debug flag %c\n", *str);
104 *flags |= *str - 'a';
112 fprintf(stderr, "bad usage :P\n");
118 rows_init(struct rows *rows, int n, double height, double perc)
121 rows->row_increment = height / n;
122 /* actual row height */
123 row_h = perc * rows->row_increment;
124 rows->row_off = (rows->row_increment - row_h) / 2.0;
125 assert(!isnan(rows->row_increment));
126 assert(!isnan(rows->row_off));
131 rows_n(struct rows *rows, int n, double *y, double *height)
133 *y = n * rows->row_increment + rows->row_off;
134 *height = rows->row_increment - 2 * rows->row_off;
138 * Which fontsize to use so that the string fits in the
143 fontsize_for_rect(double width, double height, int textlen)
147 * We start with a font size equal to the height
148 * of the rectangle and round down so that we only
149 * use a limited number of sizes.
151 * For a rectangle width comparable to the height,
152 * the text might extend outside of the rectangle.
153 * In that case we need to limit it.
155 /* available width per character */
156 wpc = width / textlen;
158 * Assuming a rough hight/width ratio for characters,
159 * calculate the available height and round it down
160 * just to be on the safe side.
162 #define GLYPH_HIGHT_TO_WIDTH 1.5
163 maxh = GLYPH_HIGHT_TO_WIDTH * wpc * 0.9;
166 } else if (height < 0.01) {
169 /* rounding (XXX: make cheaper)*/
170 height = log(height);
171 height = round(height);
172 height = exp(height);
179 void (*event)(void *, evtr_event_t);
180 void (*post)(void *);
182 struct evtr_filter *filts;
195 struct td_switch_ctx {
197 struct rows *cpu_rows;
198 struct rows *thread_rows;
199 /* which events the user cares about */
200 struct ts_interval interval;
201 /* first/last event timestamps on any cpu */
202 struct ts_interval firstlast;
204 double xscale; /* scale factor applied to x */
205 svg_rect_t cpu_sw_rect;
206 svg_rect_t thread_rect;
207 svg_rect_t inactive_rect;
208 svg_text_t thread_label;
213 struct evtr_thread **top_threads;
215 double thread_rows_yoff;
219 struct evtr_thread *td;
220 int i; /* cpu index */
221 uint64_t ts; /* time cpu switched to td */
222 /* timestamp for first/last event on this cpu */
223 struct ts_interval firstlast;
230 do_pass(struct pass_hook *hooks, int nhooks)
232 struct evtr_filter *filts = NULL;
234 struct evtr_query *q;
235 struct evtr_event ev;
237 for (i = 0; i < nhooks; ++i) {
238 struct pass_hook *h = &hooks[i];
242 filts = realloc(filts, (nfilts + h->nfilts) *
243 sizeof(struct evtr_filter));
245 err(1, "Out of memory");
246 memcpy(filts + nfilts, h->filts,
247 h->nfilts * sizeof(struct evtr_filter));
251 q = evtr_query_init(evtr, filts, nfilts);
253 err(1, "Can't initialize query\n");
254 while(!evtr_query_next(q, &ev)) {
255 for (i = 0; i < nhooks; ++i) {
257 hooks[i].event(hooks[i].data, &ev);
260 if (evtr_error(evtr)) {
261 err(1, evtr_errmsg(evtr));
263 evtr_query_destroy(q);
265 for (i = 0; i < nhooks; ++i) {
267 hooks[i].post(hooks[i].data);
269 if (evtr_rewind(evtr))
270 err(1, "Can't rewind event stream\n");
275 draw_thread_run(struct td_switch_ctx *ctx, struct cpu *c, evtr_event_t ev, int row)
277 double x, w, y, height;
278 w = (ev->ts - c->ts) * ctx->xscale;
279 x = (ev->ts - ctx->firstlast.start) * ctx->xscale;
280 rows_n(ctx->thread_rows, row, &y, &height);
281 svg_rect_draw(ctx->svg, ctx->thread_rect, x - w,
282 y + ctx->thread_rows_yoff, w, height);
287 draw_ctx_switch(struct td_switch_ctx *ctx, struct cpu *c, evtr_event_t ev)
289 struct svg_transform textrot;
291 double x, w, fs, y, height;
294 assert(ctx->xscale > 0.0);
297 /* distance to previous context switch */
298 w = (ev->ts - c->ts) * ctx->xscale;
299 x = (ev->ts - ctx->firstlast.start) * ctx->xscale;
301 fprintf(stderr, "(%ju - %ju) * %.20lf\n",
303 (uintmax_t)ctx->firstlast.start, ctx->xscale);
307 rows_n(ctx->cpu_rows, c->i, &y, &height);
309 assert(!isnan(height));
311 svg_rect_draw(ctx->svg, ctx->cpu_sw_rect, x - w, y, w, height);
314 * Draw the text label describing the thread we
322 textlen = snprintf(comm, sizeof(comm) - 1, "%s (%p)",
323 c->td ? c->td->comm : "unknown",
324 c->td ? c->td->id: NULL);
325 if (textlen > (int)sizeof(comm))
326 textlen = sizeof(comm) - 1;
327 comm[sizeof(comm) - 1] = '\0';
329 * Note the width and hight are exchanged because
330 * the bounding rectangle is rotated by 90 degrees.
332 fs = fontsize_for_rect(height, w, textlen);
333 svg_text_draw(ctx->svg, ctx->thread_label, &textrot, comm,
339 * The stats for ntd have changed, update ->top_threads
343 top_threads_update(struct td_switch_ctx *ctx, struct evtr_thread *ntd)
345 struct thread_info *tdi = ntd->userdata;
347 for (i = 0; i < ctx->nr_top_threads; ++i) {
348 struct evtr_thread *td = ctx->top_threads[i];
351 * ntd is already in top_threads and it is at
352 * the correct ranking
357 /* empty slot -- just insert our thread */
358 ctx->top_threads[i] = ntd;
361 if (((struct thread_info *)td->userdata)->runtime >=
363 /* this thread ranks higher than we do. Move on */
367 * OK, we've found the first thread that we outrank, so we
368 * need to replace it w/ our thread.
370 td = ntd; /* td holds the thread we will insert next */
371 for (j = i + 1; j < ctx->nr_top_threads; ++j, ++i) {
372 struct evtr_thread *tmp;
374 /* tmp holds the thread we replace */
375 tmp = ctx->top_threads[j];
376 ctx->top_threads[j] = td;
379 * Our thread was already in the top list,
380 * and we just removed the second instance.
381 * Nothing more to do.
393 ctxsw_prepare_event(void *_ctx, evtr_event_t ev)
395 struct td_switch_ctx *ctx = _ctx;
396 struct cpu *c, *cpus = ctx->cputab.cpus;
397 struct thread_info *tdi;
400 printd(INTV, "test1 (%ju:%ju) : %ju\n",
401 (uintmax_t)ctx->interval.start,
402 (uintmax_t)ctx->interval.end,
404 if ((ev->ts > ctx->interval.end) ||
405 (ev->ts < ctx->interval.start))
407 printd(INTV, "PREPEV on %d\n", ev->cpu);
409 /* update first/last timestamps */
411 if (!c->firstlast.start) {
412 c->firstlast.start = ev->ts;
414 c->firstlast.end = ev->ts;
416 * c->td can be null when this is the first ctxsw event we
420 /* update thread stats */
421 if (!c->td->userdata) {
422 if (!(tdi = malloc(sizeof(struct thread_info))))
423 err(1, "Out of memory");
424 c->td->userdata = tdi;
427 tdi = c->td->userdata;
428 tdi->runtime += ev->ts - c->ts;
429 top_threads_update(ctx, c->td);
432 /* Notice that ev->td is the new thread for ctxsw events */
439 find_first_last_ts(struct cpu_table *cputab, struct ts_interval *fl)
441 struct cpu *cpus = &cputab->cpus[0];
446 for (i = 0; i < cputab->ncpus; ++i) {
447 printd(INTV, "cpu%d: (%ju, %ju)\n", i,
448 (uintmax_t)cpus[i].firstlast.start,
449 (uintmax_t)cpus[i].firstlast.end);
450 if (cpus[i].firstlast.start &&
451 (cpus[i].firstlast.start < fl->start))
452 fl->start = cpus[i].firstlast.start;
453 if (cpus[i].firstlast.end &&
454 (cpus[i].firstlast.end > fl->end))
455 fl->end = cpus[i].firstlast.end;
459 printd(INTV, "global (%jd, %jd)\n", (uintmax_t)fl->start, (uintmax_t)fl->end);
464 ctxsw_prepare_post(void *_ctx)
466 struct td_switch_ctx *ctx = _ctx;
468 find_first_last_ts(&ctx->cputab, &ctx->firstlast);
473 ctxsw_draw_pre(void *_ctx)
475 struct td_switch_ctx *ctx = _ctx;
476 struct svg_transform textrot;
478 double y, height, fs;
480 struct evtr_thread *td;
482 textrot.tx = 0.0 - 0.2; /* XXX */
487 for (i = 0; i < ctx->nr_top_threads; ++i) {
488 td = ctx->top_threads[i];
491 rows_n(ctx->thread_rows, i, &y, &height);
492 svg_rect_draw(ctx->svg, ctx->inactive_rect, 0.0,
493 y + ctx->thread_rows_yoff, ctx->width, height);
494 textlen = snprintf(comm, sizeof(comm) - 1, "%s (%p)",
496 if (textlen > (int)sizeof(comm))
497 textlen = sizeof(comm) - 1;
498 comm[sizeof(comm) - 1] = '\0';
499 fs = fontsize_for_rect(height, 100.0, textlen);
501 textrot.ty = y + ctx->thread_rows_yoff + height;
502 svg_text_draw(ctx->svg, ctx->thread_label, &textrot,
509 ctxsw_draw_event(void *_ctx, evtr_event_t ev)
511 struct td_switch_ctx *ctx = _ctx;
512 struct cpu *c = &ctx->cputab.cpus[ev->cpu];
516 * ctx->firstlast.end can be 0 if there were no events
517 * in the specified interval, in which case
518 * ctx->firstlast.start is invalid too.
520 assert(!ctx->firstlast.end || (ev->ts >= ctx->firstlast.start));
521 printd(INTV, "test2 (%ju:%ju) : %ju\n", (uintmax_t)ctx->interval.start,
522 (uintmax_t)ctx->interval.end, (uintmax_t)ev->ts);
523 if ((ev->ts > ctx->interval.end) ||
524 (ev->ts < ctx->interval.start))
526 printd(INTV, "DRAWEV %d\n", ev->cpu);
527 if (c->td != ev->td) { /* thread switch (or preemption) */
528 draw_ctx_switch(ctx, c, ev);
529 /* XXX: this is silly */
530 for (i = 0; i < ctx->nr_top_threads; ++i) {
531 if (!ctx->top_threads[i])
533 if (ctx->top_threads[i] == c->td) {
534 draw_thread_run(ctx, c, ev, i);
545 cputab_init(struct cpu_table *ct)
551 if ((ct->ncpus = evtr_ncpus(evtr)) <= 0)
552 err(1, "No cpu information!\n");
553 printd(MISC, "evtranalyze: ncpus %d\n", ct->ncpus);
554 if (!(ct->cpus = malloc(sizeof(struct cpu) * ct->ncpus))) {
555 err(1, "Can't allocate memory\n");
558 if (!(freqs = malloc(sizeof(double) * ct->ncpus))) {
559 err(1, "Can't allocate memory\n");
561 if ((i = evtr_cpufreqs(evtr, freqs))) {
562 warnc(i, "Can't get cpu frequencies\n");
563 for (i = 0; i < ct->ncpus; ++i) {
568 /* initialize cpu array */
569 for (i = 0; i < ct->ncpus; ++i) {
573 cpus[i].firstlast.start = 0;
574 cpus[i].firstlast.end = 0;
576 cpus[i].freq = freqs[i];
583 parse_interval(const char *_str, struct ts_interval *ts,
584 struct cpu_table *cputab)
587 const char *str = _str + 1;
589 if ('c' == *_str) { /* cycles */
590 if (sscanf(str, "%" SCNu64 ":%" SCNu64,
594 } else if ('m' == *_str) { /* miliseconds */
595 if (sscanf(str, "%lf:%lf", &s, &e) == 2) {
596 freq = cputab->cpus[0].freq;
597 freq *= 1000.0; /* msecs */
599 fprintf(stderr, "No frequency information"
601 err(2, "Can't convert time to cycles\n");
603 ts->start = s * freq;
608 fprintf(stderr, "invalid interval format: %s\n", _str);
615 cmd_svg(int argc, char **argv)
619 double height, width;
620 struct rows cpu_rows, thread_rows;
621 struct td_switch_ctx td_ctx;
622 const char *outpath = "output.svg";
623 struct evtr_filter ctxsw_filts[2] = {
633 struct pass_hook ctxsw_prepare = {
635 .event = ctxsw_prepare_event,
636 .post = ctxsw_prepare_post,
638 .filts = ctxsw_filts,
639 .nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
641 .pre = ctxsw_draw_pre,
642 .event = ctxsw_draw_event,
645 .filts = ctxsw_filts,
646 .nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
650 * We are interested in thread switch and preemption
651 * events, but we don't use the data directly. Instead
654 ctxsw_filts[0].fmt = "sw %p > %p";
655 ctxsw_filts[1].fmt = "pre %p > %p";
656 td_ctx.interval.start = 0;
657 td_ctx.interval.end = -1; /* i.e. no interval given */
658 td_ctx.nr_top_threads = NR_TOP_THREADS;
659 cputab_init(&td_ctx.cputab); /* needed for parse_interval() */
663 while ((ch = getopt(argc, argv, "i:o:")) != -1) {
666 parse_interval(optarg, &td_ctx.interval,
682 td_ctx.width = width;
684 if (!(td_ctx.top_threads = calloc(td_ctx.nr_top_threads,
685 sizeof(struct evtr_thread *))))
686 err(1, "Can't allocate memory\n");
687 if (!(svg = svg_document_create(outpath)))
688 err(1, "Can't open svg document\n");
691 * Create rectangles to use for output.
693 if (!(td_ctx.cpu_sw_rect = svg_rect_new("generic")))
694 err(1, "Can't create rectangle\n");
695 if (!(td_ctx.thread_rect = svg_rect_new("thread")))
696 err(1, "Can't create rectangle\n");
697 if (!(td_ctx.inactive_rect = svg_rect_new("inactive")))
698 err(1, "Can't create rectangle\n");
699 /* text for thread names */
700 if (!(td_ctx.thread_label = svg_text_new("generic")))
701 err(1, "Can't create text\n");
702 rows_init(&cpu_rows, td_ctx.cputab.ncpus, height, 0.9);
704 td_ctx.xscale = -1.0;
705 td_ctx.cpu_rows = &cpu_rows;
707 do_pass(&ctxsw_prepare, 1);
708 td_ctx.thread_rows_yoff = height;
709 td_ctx.thread_rows = &thread_rows;
710 rows_init(td_ctx.thread_rows, td_ctx.nr_top_threads, 300, 0.9);
711 td_ctx.xscale = width / (td_ctx.firstlast.end - td_ctx.firstlast.start);
712 printd(SVG, "first %ju, last %ju, xscale %lf\n",
713 (uintmax_t)td_ctx.firstlast.start, (uintmax_t)td_ctx.firstlast.end,
716 do_pass(&ctxsw_draw, 1);
718 svg_document_close(svg);
724 cmd_show(int argc, char **argv)
726 struct evtr_event ev;
727 struct evtr_query *q;
728 struct evtr_filter filt;
729 struct cpu_table cputab;
732 uint64_t last_ts = 0;
734 cputab_init(&cputab);
736 * Assume all cores run on the same frequency
737 * for now. There's no reason to complicate
738 * things unless we can detect frequency change
741 * Note that the code is very simplistic and will
742 * produce garbage if the kernel doesn't fixup
743 * the timestamps for cores running with different
746 freq = cputab.cpus[0].freq;
747 freq /= 1000000; /* we want to print out usecs */
748 printd(MISC, "using freq = %lf\n", freq);
752 while ((ch = getopt(argc, argv, "f:")) != -1) {
761 q = evtr_query_init(evtr, &filt, 1);
763 err(1, "Can't initialize query\n");
764 while(!evtr_query_next(q, &ev)) {
770 printf("%s\t%llu cycles\t[%.3d]\t%s:%d",
771 ev.td ? ev.td->comm : "unknown",
772 ev.ts - last_ts, ev.cpu,
773 basename(ev.file), ev.line);
775 printf("%s\t%.3lf usecs\t[%.3d]\t%s:%d",
776 ev.td ? ev.td->comm : "unknown",
777 (ev.ts - last_ts) / freq, ev.cpu,
778 basename(ev.file), ev.line);
781 evtr_event_data(&ev, buf, sizeof(buf));
782 printf(" !\t%s\n", buf);
788 if (evtr_error(evtr)) {
789 err(1, evtr_errmsg(evtr));
791 evtr_query_destroy(q);
797 cmd_summary(int argc, char **argv)
799 struct evtr_filter filt;
800 struct evtr_event ev;
801 struct evtr_query *q;
803 struct cpu_table cputab;
804 struct ts_interval global;
805 uintmax_t global_evcnt;
811 cputab_init(&cputab);
816 q = evtr_query_init(evtr, &filt, 1);
818 err(1, "Can't initialize query\n");
819 while(!evtr_query_next(q, &ev)) {
820 struct cpu *c = &cputab.cpus[ev.cpu];
821 if (!c->firstlast.start)
822 c->firstlast.start = ev.ts;
824 c->firstlast.end = ev.ts;
826 if (evtr_error(evtr)) {
827 err(1, evtr_errmsg(evtr));
829 evtr_query_destroy(q);
831 find_first_last_ts(&cputab, &global);
833 freq = cputab.cpus[0].freq;
835 for (i = 0; i < cputab.ncpus; ++i) {
836 struct cpu *c = &cputab.cpus[i];
837 printf("CPU %d: %jd events in %.3lf secs\n", i,
838 c->evcnt, (c->firstlast.end - c->firstlast.start)
840 global_evcnt += c->evcnt;
842 printf("Total: %jd events on %d cpus in %.3lf secs\n", global_evcnt,
843 cputab.ncpus, (global.end - global.start) / freq);
850 main(int argc, char **argv)
857 while ((ch = getopt(argc, argv, "f:D:")) != -1) {
863 if ((tmp = strchr(optarg, ':'))) {
867 printd_set_flags(optarg, &evtranalyze_debug);
877 err(2, "need to specify a command\n");
879 if (!opt_infile || !strcmp(opt_infile, "-")) {
882 inf = fopen(opt_infile, "r");
884 err(2, "Can't open input file\n");
888 if (!(evtr = evtr_open_read(inf))) {
889 err(1, "Can't open evtr stream\n");
893 for (cmd = commands; cmd->name != NULL; ++cmd) {
894 if (strcmp(argv[0], cmd->name))
896 cmd->func(argc, argv);
900 err(2, "no such command: %s\n", argv[0]);