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 **)
67 int (*func)(int argc, char **argv);
92 unsigned evtranalyze_debug;
96 printd_set_flags(const char *str, unsigned int *flags)
99 * This is suboptimal as we don't detect
102 for (; *str; ++str) {
108 err(2, "invalid debug flag %c\n", *str);
109 *flags |= 1 << (*str - 'a');
117 fprintf(stderr, "bad usage :P\n");
123 rows_init(struct rows *rows, int n, double height, double perc)
126 rows->row_increment = height / n;
127 /* actual row height */
128 row_h = perc * rows->row_increment;
129 rows->row_off = (rows->row_increment - row_h) / 2.0;
130 assert(!isnan(rows->row_increment));
131 assert(!isnan(rows->row_off));
136 rows_n(struct rows *rows, int n, double *y, double *height)
138 *y = n * rows->row_increment + rows->row_off;
139 *height = rows->row_increment - 2 * rows->row_off;
143 * Which fontsize to use so that the string fits in the
148 fontsize_for_rect(double width, double height, int textlen)
152 * We start with a font size equal to the height
153 * of the rectangle and round down so that we only
154 * use a limited number of sizes.
156 * For a rectangle width comparable to the height,
157 * the text might extend outside of the rectangle.
158 * In that case we need to limit it.
160 /* available width per character */
161 wpc = width / textlen;
163 * Assuming a rough hight/width ratio for characters,
164 * calculate the available height and round it down
165 * just to be on the safe side.
167 #define GLYPH_HIGHT_TO_WIDTH 1.5
168 maxh = GLYPH_HIGHT_TO_WIDTH * wpc * 0.9;
171 } else if (height < 0.01) {
174 /* rounding (XXX: make cheaper)*/
175 height = log(height);
176 height = round(height);
177 height = exp(height);
184 void (*event)(void *, evtr_event_t);
185 void (*post)(void *);
187 struct evtr_filter *filts;
200 struct td_switch_ctx {
202 struct rows *cpu_rows;
203 struct rows *thread_rows;
204 /* which events the user cares about */
205 struct ts_interval interval;
206 /* first/last event timestamps on any cpu */
207 struct ts_interval firstlast;
209 double xscale; /* scale factor applied to x */
210 svg_rect_t cpu_sw_rect;
211 svg_rect_t thread_rect;
212 svg_rect_t inactive_rect;
213 svg_text_t thread_label;
218 struct evtr_thread **top_threads;
220 double thread_rows_yoff;
224 struct evtr_thread *td;
225 int i; /* cpu index */
226 uint64_t ts; /* time cpu switched to td */
227 /* timestamp for first/last event on this cpu */
228 struct ts_interval firstlast;
235 do_pass(struct pass_hook *hooks, int nhooks)
237 struct evtr_filter *filts = NULL;
239 struct evtr_query *q;
240 struct evtr_event ev;
242 for (i = 0; i < nhooks; ++i) {
243 struct pass_hook *h = &hooks[i];
247 filts = realloc(filts, (nfilts + h->nfilts) *
248 sizeof(struct evtr_filter));
250 err(1, "Out of memory");
251 memcpy(filts + nfilts, h->filts,
252 h->nfilts * sizeof(struct evtr_filter));
256 q = evtr_query_init(evtr, filts, nfilts);
258 err(1, "Can't initialize query\n");
259 while(!evtr_query_next(q, &ev)) {
260 for (i = 0; i < nhooks; ++i) {
262 hooks[i].event(hooks[i].data, &ev);
265 if (evtr_query_error(q)) {
266 err(1, evtr_query_errmsg(q));
268 evtr_query_destroy(q);
270 for (i = 0; i < nhooks; ++i) {
272 hooks[i].post(hooks[i].data);
274 if (evtr_rewind(evtr))
275 err(1, "Can't rewind event stream\n");
280 draw_thread_run(struct td_switch_ctx *ctx, struct cpu *c, evtr_event_t ev, int row)
282 double x, w, y, height;
283 w = (ev->ts - c->ts) * ctx->xscale;
284 x = (ev->ts - ctx->firstlast.start) * ctx->xscale;
285 rows_n(ctx->thread_rows, row, &y, &height);
286 svg_rect_draw(ctx->svg, ctx->thread_rect, x - w,
287 y + ctx->thread_rows_yoff, w, height);
292 draw_ctx_switch(struct td_switch_ctx *ctx, struct cpu *c, evtr_event_t ev)
294 struct svg_transform textrot;
296 double x, w, fs, y, height;
299 assert(ctx->xscale > 0.0);
302 /* distance to previous context switch */
303 w = (ev->ts - c->ts) * ctx->xscale;
304 x = (ev->ts - ctx->firstlast.start) * ctx->xscale;
306 fprintf(stderr, "(%ju - %ju) * %.20lf\n",
308 (uintmax_t)ctx->firstlast.start, ctx->xscale);
312 rows_n(ctx->cpu_rows, c->i, &y, &height);
314 assert(!isnan(height));
316 svg_rect_draw(ctx->svg, ctx->cpu_sw_rect, x - w, y, w, height);
319 * Draw the text label describing the thread we
327 textlen = snprintf(comm, sizeof(comm) - 1, "%s (%p)",
328 c->td ? c->td->comm : "unknown",
329 c->td ? c->td->id: NULL);
330 if (textlen > (int)sizeof(comm))
331 textlen = sizeof(comm) - 1;
332 comm[sizeof(comm) - 1] = '\0';
334 * Note the width and hight are exchanged because
335 * the bounding rectangle is rotated by 90 degrees.
337 fs = fontsize_for_rect(height, w, textlen);
338 svg_text_draw(ctx->svg, ctx->thread_label, &textrot, comm,
344 * The stats for ntd have changed, update ->top_threads
348 top_threads_update(struct td_switch_ctx *ctx, struct evtr_thread *ntd)
350 struct thread_info *tdi = ntd->userdata;
352 for (i = 0; i < ctx->nr_top_threads; ++i) {
353 struct evtr_thread *td = ctx->top_threads[i];
356 * ntd is already in top_threads and it is at
357 * the correct ranking
362 /* empty slot -- just insert our thread */
363 ctx->top_threads[i] = ntd;
366 if (((struct thread_info *)td->userdata)->runtime >=
368 /* this thread ranks higher than we do. Move on */
372 * OK, we've found the first thread that we outrank, so we
373 * need to replace it w/ our thread.
375 td = ntd; /* td holds the thread we will insert next */
376 for (j = i + 1; j < ctx->nr_top_threads; ++j, ++i) {
377 struct evtr_thread *tmp;
379 /* tmp holds the thread we replace */
380 tmp = ctx->top_threads[j];
381 ctx->top_threads[j] = td;
384 * Our thread was already in the top list,
385 * and we just removed the second instance.
386 * Nothing more to do.
398 ctxsw_prepare_event(void *_ctx, evtr_event_t ev)
400 struct td_switch_ctx *ctx = _ctx;
401 struct cpu *c, *cpus = ctx->cputab.cpus;
402 struct thread_info *tdi;
405 printd(INTV, "test1 (%ju:%ju) : %ju\n",
406 (uintmax_t)ctx->interval.start,
407 (uintmax_t)ctx->interval.end,
409 if ((ev->ts > ctx->interval.end) ||
410 (ev->ts < ctx->interval.start))
412 printd(INTV, "PREPEV on %d\n", ev->cpu);
414 /* update first/last timestamps */
416 if (!c->firstlast.start) {
417 c->firstlast.start = ev->ts;
419 c->firstlast.end = ev->ts;
421 * c->td can be null when this is the first ctxsw event we
425 /* update thread stats */
426 if (!c->td->userdata) {
427 if (!(tdi = malloc(sizeof(struct thread_info))))
428 err(1, "Out of memory");
429 c->td->userdata = tdi;
432 tdi = c->td->userdata;
433 tdi->runtime += ev->ts - c->ts;
434 top_threads_update(ctx, c->td);
437 /* Notice that ev->td is the new thread for ctxsw events */
444 find_first_last_ts(struct cpu_table *cputab, struct ts_interval *fl)
446 struct cpu *cpus = &cputab->cpus[0];
451 for (i = 0; i < cputab->ncpus; ++i) {
452 printd(INTV, "cpu%d: (%ju, %ju)\n", i,
453 (uintmax_t)cpus[i].firstlast.start,
454 (uintmax_t)cpus[i].firstlast.end);
455 if (cpus[i].firstlast.start &&
456 (cpus[i].firstlast.start < fl->start))
457 fl->start = cpus[i].firstlast.start;
458 if (cpus[i].firstlast.end &&
459 (cpus[i].firstlast.end > fl->end))
460 fl->end = cpus[i].firstlast.end;
464 printd(INTV, "global (%jd, %jd)\n", (uintmax_t)fl->start, (uintmax_t)fl->end);
469 ctxsw_prepare_post(void *_ctx)
471 struct td_switch_ctx *ctx = _ctx;
473 find_first_last_ts(&ctx->cputab, &ctx->firstlast);
478 ctxsw_draw_pre(void *_ctx)
480 struct td_switch_ctx *ctx = _ctx;
481 struct svg_transform textrot;
483 double y, height, fs;
485 struct evtr_thread *td;
487 textrot.tx = 0.0 - 0.2; /* XXX */
492 for (i = 0; i < ctx->nr_top_threads; ++i) {
493 td = ctx->top_threads[i];
496 rows_n(ctx->thread_rows, i, &y, &height);
497 svg_rect_draw(ctx->svg, ctx->inactive_rect, 0.0,
498 y + ctx->thread_rows_yoff, ctx->width, height);
499 textlen = snprintf(comm, sizeof(comm) - 1, "%s (%p)",
501 if (textlen > (int)sizeof(comm))
502 textlen = sizeof(comm) - 1;
503 comm[sizeof(comm) - 1] = '\0';
504 fs = fontsize_for_rect(height, 100.0, textlen);
506 textrot.ty = y + ctx->thread_rows_yoff + height;
507 svg_text_draw(ctx->svg, ctx->thread_label, &textrot,
514 ctxsw_draw_event(void *_ctx, evtr_event_t ev)
516 struct td_switch_ctx *ctx = _ctx;
517 struct cpu *c = &ctx->cputab.cpus[ev->cpu];
521 * ctx->firstlast.end can be 0 if there were no events
522 * in the specified interval, in which case
523 * ctx->firstlast.start is invalid too.
525 assert(!ctx->firstlast.end || (ev->ts >= ctx->firstlast.start));
526 printd(INTV, "test2 (%ju:%ju) : %ju\n", (uintmax_t)ctx->interval.start,
527 (uintmax_t)ctx->interval.end, (uintmax_t)ev->ts);
528 if ((ev->ts > ctx->interval.end) ||
529 (ev->ts < ctx->interval.start))
531 printd(INTV, "DRAWEV %d\n", ev->cpu);
532 if (c->td != ev->td) { /* thread switch (or preemption) */
533 draw_ctx_switch(ctx, c, ev);
534 /* XXX: this is silly */
535 for (i = 0; i < ctx->nr_top_threads; ++i) {
536 if (!ctx->top_threads[i])
538 if (ctx->top_threads[i] == c->td) {
539 draw_thread_run(ctx, c, ev, i);
550 cputab_init(struct cpu_table *ct)
556 if ((ct->ncpus = evtr_ncpus(evtr)) <= 0)
557 err(1, "No cpu information!\n");
558 printd(MISC, "evtranalyze: ncpus %d\n", ct->ncpus);
559 if (!(ct->cpus = malloc(sizeof(struct cpu) * ct->ncpus))) {
560 err(1, "Can't allocate memory\n");
563 if (!(freqs = malloc(sizeof(double) * ct->ncpus))) {
564 err(1, "Can't allocate memory\n");
566 if ((i = evtr_cpufreqs(evtr, freqs))) {
567 warnc(i, "Can't get cpu frequencies\n");
568 for (i = 0; i < ct->ncpus; ++i) {
573 /* initialize cpu array */
574 for (i = 0; i < ct->ncpus; ++i) {
578 cpus[i].firstlast.start = 0;
579 cpus[i].firstlast.end = 0;
581 cpus[i].freq = freqs[i];
588 parse_interval(const char *_str, struct ts_interval *ts,
589 struct cpu_table *cputab)
592 const char *str = _str + 1;
594 if ('c' == *_str) { /* cycles */
595 if (sscanf(str, "%" SCNu64 ":%" SCNu64,
599 } else if ('m' == *_str) { /* miliseconds */
600 if (sscanf(str, "%lf:%lf", &s, &e) == 2) {
601 freq = cputab->cpus[0].freq;
602 freq *= 1000.0; /* msecs */
604 fprintf(stderr, "No frequency information"
606 err(2, "Can't convert time to cycles\n");
608 ts->start = s * freq;
613 fprintf(stderr, "invalid interval format: %s\n", _str);
620 cmd_svg(int argc, char **argv)
624 double height, width;
625 struct rows cpu_rows, thread_rows;
626 struct td_switch_ctx td_ctx;
627 const char *outpath = "output.svg";
628 struct evtr_filter ctxsw_filts[2] = {
632 .ev_type = EVTR_TYPE_PROBE,
637 .ev_type = EVTR_TYPE_PROBE,
640 struct pass_hook ctxsw_prepare = {
642 .event = ctxsw_prepare_event,
643 .post = ctxsw_prepare_post,
645 .filts = ctxsw_filts,
646 .nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
648 .pre = ctxsw_draw_pre,
649 .event = ctxsw_draw_event,
652 .filts = ctxsw_filts,
653 .nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
657 * We are interested in thread switch and preemption
658 * events, but we don't use the data directly. Instead
661 ctxsw_filts[0].fmt = "sw %p > %p";
662 ctxsw_filts[1].fmt = "pre %p > %p";
663 td_ctx.interval.start = 0;
664 td_ctx.interval.end = -1; /* i.e. no interval given */
665 td_ctx.nr_top_threads = NR_TOP_THREADS;
666 cputab_init(&td_ctx.cputab); /* needed for parse_interval() */
670 while ((ch = getopt(argc, argv, "i:o:")) != -1) {
673 parse_interval(optarg, &td_ctx.interval,
689 td_ctx.width = width;
691 if (!(td_ctx.top_threads = calloc(td_ctx.nr_top_threads,
692 sizeof(struct evtr_thread *))))
693 err(1, "Can't allocate memory\n");
694 if (!(svg = svg_document_create(outpath)))
695 err(1, "Can't open svg document\n");
698 * Create rectangles to use for output.
700 if (!(td_ctx.cpu_sw_rect = svg_rect_new("generic")))
701 err(1, "Can't create rectangle\n");
702 if (!(td_ctx.thread_rect = svg_rect_new("thread")))
703 err(1, "Can't create rectangle\n");
704 if (!(td_ctx.inactive_rect = svg_rect_new("inactive")))
705 err(1, "Can't create rectangle\n");
706 /* text for thread names */
707 if (!(td_ctx.thread_label = svg_text_new("generic")))
708 err(1, "Can't create text\n");
709 rows_init(&cpu_rows, td_ctx.cputab.ncpus, height, 0.9);
711 td_ctx.xscale = -1.0;
712 td_ctx.cpu_rows = &cpu_rows;
714 do_pass(&ctxsw_prepare, 1);
715 td_ctx.thread_rows_yoff = height;
716 td_ctx.thread_rows = &thread_rows;
717 rows_init(td_ctx.thread_rows, td_ctx.nr_top_threads, 300, 0.9);
718 td_ctx.xscale = width / (td_ctx.firstlast.end - td_ctx.firstlast.start);
719 printd(SVG, "first %ju, last %ju, xscale %lf\n",
720 (uintmax_t)td_ctx.firstlast.start, (uintmax_t)td_ctx.firstlast.end,
723 do_pass(&ctxsw_draw, 1);
725 svg_document_close(svg);
731 cmd_show(int argc, char **argv)
733 struct evtr_event ev;
734 struct evtr_query *q;
735 struct evtr_filter filt;
736 struct cpu_table cputab;
739 uint64_t last_ts = 0;
741 cputab_init(&cputab);
743 * Assume all cores run on the same frequency
744 * for now. There's no reason to complicate
745 * things unless we can detect frequency change
748 * Note that the code is very simplistic and will
749 * produce garbage if the kernel doesn't fixup
750 * the timestamps for cores running with different
753 freq = cputab.cpus[0].freq;
754 freq /= 1000000; /* we want to print out usecs */
755 printd(MISC, "using freq = %lf\n", freq);
758 filt.ev_type = EVTR_TYPE_PROBE;
762 while ((ch = getopt(argc, argv, "f:")) != -1) {
769 q = evtr_query_init(evtr, &filt, 1);
771 err(1, "Can't initialize query\n");
772 while(!evtr_query_next(q, &ev)) {
778 printf("%s\t%ju cycles\t[%.3d]\t%s:%d",
779 ev.td ? ev.td->comm : "unknown",
780 (uintmax_t)(ev.ts - last_ts), ev.cpu,
781 basename(ev.file), ev.line);
783 printf("%s\t%.3lf usecs\t[%.3d]\t%s:%d",
784 ev.td ? ev.td->comm : "unknown",
785 (ev.ts - last_ts) / freq, ev.cpu,
786 basename(ev.file), ev.line);
789 evtr_event_data(&ev, buf, sizeof(buf));
790 printf(" !\t%s\n", buf);
796 if (evtr_query_error(q)) {
797 err(1, evtr_query_errmsg(q));
799 evtr_query_destroy(q);
805 cmd_stats(int argc, char **argv)
807 struct evtr_event ev;
808 struct evtr_query *q;
809 struct evtr_filter filt;
810 struct cpu_table cputab;
812 uint64_t last_ts = 0;
813 enum evtr_value_type type = EVTR_VAL_INT;
814 uintmax_t sum, occurences;
816 cputab_init(&cputab);
818 * Assume all cores run on the same frequency
819 * for now. There's no reason to complicate
820 * things unless we can detect frequency change
823 * Note that the code is very simplistic and will
824 * produce garbage if the kernel doesn't fixup
825 * the timestamps for cores running with different
828 freq = cputab.cpus[0].freq;
829 freq /= 1000000; /* we want to print out usecs */
830 printd(MISC, "using freq = %lf\n", freq);
833 filt.ev_type = EVTR_TYPE_STMT;
839 err(2, "Need exactly one variable");
841 q = evtr_query_init(evtr, &filt, 1);
843 err(1, "Can't initialize query\n");
844 sum = occurences = 0;
845 while(!evtr_query_next(q, &ev)) {
850 assert(ev.type == EVTR_TYPE_STMT);
851 if (type != ev.stmt.val->type) {
852 printf("ignoring assignment of wrong type %d (expected %d)\n",
853 ev.stmt.val->type, type);
855 printf("var \"%s\" = %jx\n",
856 filt.var, ev.stmt.val->num);
857 sum += ev.stmt.val->num;
862 if (evtr_query_error(q)) {
863 err(1, evtr_query_errmsg(q));
865 printf("median for variable %s is %lf\n", argv[1], (double)sum / occurences);
866 evtr_query_destroy(q);
873 cmd_summary(int argc, char **argv)
875 struct evtr_filter filt;
876 struct evtr_event ev;
877 struct evtr_query *q;
879 struct cpu_table cputab;
880 struct ts_interval global;
881 uintmax_t global_evcnt;
887 cputab_init(&cputab);
888 filt.ev_type = EVTR_TYPE_PROBE;
893 q = evtr_query_init(evtr, &filt, 1);
895 err(1, "Can't initialize query\n");
896 while(!evtr_query_next(q, &ev)) {
897 struct cpu *c = &cputab.cpus[ev.cpu];
898 if (!c->firstlast.start)
899 c->firstlast.start = ev.ts;
901 c->firstlast.end = ev.ts;
903 if (evtr_query_error(q)) {
904 err(1, evtr_query_errmsg(q));
906 evtr_query_destroy(q);
908 find_first_last_ts(&cputab, &global);
910 freq = cputab.cpus[0].freq;
912 for (i = 0; i < cputab.ncpus; ++i) {
913 struct cpu *c = &cputab.cpus[i];
914 printf("CPU %d: %jd events in %.3lf secs\n", i,
915 c->evcnt, (c->firstlast.end - c->firstlast.start)
917 global_evcnt += c->evcnt;
919 printf("Total: %jd events on %d cpus in %.3lf secs\n", global_evcnt,
920 cputab.ncpus, (global.end - global.start) / freq);
927 main(int argc, char **argv)
934 while ((ch = getopt(argc, argv, "f:D:")) != -1) {
940 if ((tmp = strchr(optarg, ':'))) {
944 printd_set_flags(optarg, &evtranalyze_debug);
954 err(2, "need to specify a command\n");
956 if (!opt_infile || !strcmp(opt_infile, "-")) {
959 inf = fopen(opt_infile, "r");
961 err(2, "Can't open input file\n");
965 if (!(evtr = evtr_open_read(inf))) {
966 err(1, "Can't open evtr stream\n");
970 for (cmd = commands; cmd->name != NULL; ++cmd) {
971 if (strcmp(argv[0], cmd->name))
973 cmd->func(argc, argv);
977 err(2, "no such command: %s\n", argv[0]);