evtranalyze: correct a couple of filtering bugs
[dragonfly.git] / usr.bin / evtranalyze / evtranalyze.c
CommitLineData
e7c0dbba
AE
1/*
2 * Copyright (c) 2009, 2010 Aggelos Economopoulos. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
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
13 * distribution.
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.
17 *
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
29 * SUCH DAMAGE.
30 */
31
32#include <assert.h>
33#include <err.h>
34#include <libgen.h>
35#include <math.h>
36#include <stdio.h>
37#include <stdlib.h>
38#include <string.h>
39#include <unistd.h>
40
41#include <evtr.h>
42#include "xml.h"
43#include "svg.h"
44
45enum {
46 NR_TOP_THREADS = 5,
47};
48
49struct rows {
50 double row_increment;
51 double row_off;
52};
53
54#define CMD_PROTO(name) \
55 static int cmd_ ## name(int, char **)
56
57CMD_PROTO(show);
58CMD_PROTO(svg);
59
60struct command {
61 const char *name;
62 int (*func)(int argc, char **argv);
63} commands[] = {
64 {
65 .name = "show",
66 .func = &cmd_show,
67 },
68 {
69 .name = "svg",
70 .func = &cmd_svg,
71 },
72 {
73 .name = NULL,
74 },
75};
76
77evtr_t evtr;
78char *opt_infile;
79static int evtranalyze_debug;
80
81#define printd(...) \
82 do { \
83 if (evtranalyze_debug) { \
84 fprintf(stderr, __VA_ARGS__); \
85 } \
86 } while (0)
87
88static
89void
90usage(void)
91{
92 fprintf(stderr, "bad usage :P\n");
93 exit(2);
94}
95
96static
97void
98rows_init(struct rows *rows, int n, double height, double perc)
99{
100 double row_h;
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));
107}
108
109static
110void
111rows_n(struct rows *rows, int n, double *y, double *height)
112{
113 *y = n * rows->row_increment + rows->row_off;
114 *height = rows->row_increment - 2 * rows->row_off;
115}
116
117/*
118 * Which fontsize to use so that the string fits in the
119 * given rect.
120 */
121static
122double
123fontsize_for_rect(double width, double height, int textlen)
124{
125 double wpc, maxh;
126 /*
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.
130 *
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.
134 */
135 /* available width per character */
136 wpc = width / textlen;
137 /*
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.
141 */
142#define GLYPH_HIGHT_TO_WIDTH 1.5
143 maxh = GLYPH_HIGHT_TO_WIDTH * wpc * 0.9;
144 if (height > maxh) {
145 height = maxh;
146 } else if (height < 0.01) {
147 height = 0.01;
148 } else {
149 /* rounding (XXX: make cheaper)*/
150 height = log(height);
151 height = round(height);
152 height = exp(height);
153 }
154 return height;
155}
156
157struct pass_hook {
158 void (*pre)(void *);
159 void (*event)(void *, evtr_event_t);
160 void (*post)(void *);
161 void *data;
162 struct evtr_filter *filts;
163 int nfilts;
164};
165
166struct thread_info {
167 uint64_t runtime;
168};
169
170struct td_switch_ctx {
171 svg_document_t svg;
172 struct rows *cpu_rows;
173 struct rows *thread_rows;
174 uint64_t interval_start, interval_end;
175 uint64_t first_ts, last_ts;
176 double width;
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;
182 struct cpu *cpus;
183 int ncpus;
184 struct evtr_thread **top_threads;
185 int nr_top_threads;
186 double thread_rows_yoff;
187};
188
189struct cpu {
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;
194};
195
196static
197void
198do_pass(struct pass_hook *hooks, int nhooks)
199{
200 struct evtr_filter *filts = NULL;
201 int nfilts = 0, i;
202 struct evtr_query *q;
203 struct evtr_event ev;
204
205 for (i = 0; i < nhooks; ++i) {
206 struct pass_hook *h = &hooks[i];
207 if (h->pre)
208 h->pre(h->data);
209 if (h->nfilts > 0) {
210 filts = realloc(filts, (nfilts + h->nfilts) *
211 sizeof(struct evtr_filter));
212 if (!filts)
213 err(1, "Out of memory");
0b33b2e3 214 memcpy(filts + nfilts, h->filts,
e7c0dbba
AE
215 h->nfilts * sizeof(struct evtr_filter));
216 nfilts += h->nfilts;
217 }
218 }
219 q = evtr_query_init(evtr, filts, nfilts);
220 if (!q)
221 err(1, "Can't initialize query\n");
222 while(!evtr_query_next(q, &ev)) {
223 for (i = 0; i < nhooks; ++i) {
224 if (hooks[i].event)
225 hooks[i].event(hooks[i].data, &ev);
226 }
227 }
228 if (evtr_error(evtr)) {
229 err(1, evtr_errmsg(evtr));
230 }
231 evtr_query_destroy(q);
232
233 for (i = 0; i < nhooks; ++i) {
234 if (hooks[i].post)
235 hooks[i].post(hooks[i].data);
236 }
237 if (evtr_rewind(evtr))
238 err(1, "Can't rewind event stream\n");
239}
240
241static
242void
243draw_thread_run(struct td_switch_ctx *ctx, struct cpu *c, evtr_event_t ev, int row)
244{
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);
251}
252
253static
254void
255draw_ctx_switch(struct td_switch_ctx *ctx, struct cpu *c, evtr_event_t ev)
256{
257 struct svg_transform textrot;
258 char comm[100];
259 double x, w, fs, y, height;
260 int textlen;
261
262 assert(ctx->xscale > 0.0);
263 if (!c->ts)
264 return;
265 /* distance to previous context switch */
266 w = (ev->ts - c->ts) * ctx->xscale;
267 x = (ev->ts - ctx->first_ts) * ctx->xscale;
268 if ((x - w) < 0) {
269 fprintf(stderr, "(%llu - %llu) * %.20lf\n", ev->ts,
270 ctx->first_ts, ctx->xscale);
271 abort();
272 }
273
274 rows_n(ctx->cpu_rows, c->i, &y, &height);
275 assert(!isnan(y));
276 assert(!isnan(height));
277
278 svg_rect_draw(ctx->svg, ctx->cpu_sw_rect, x - w, y, w, height);
279
280 /*
281 * Draw the text label describing the thread we
282 * switched out of.
283 */
284 textrot.tx = x - w;
285 textrot.ty = y;
286 textrot.sx = 1.0;
287 textrot.sy = 1.0;
288 textrot.rot = 90.0;
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';
295 /*
296 * Note the width and hight are exchanged because
297 * the bounding rectangle is rotated by 90 degrees.
298 */
299 fs = fontsize_for_rect(height, w, textlen);
300 svg_text_draw(ctx->svg, ctx->thread_label, &textrot, comm,
301 fs);
302}
303
304
305/*
306 * The stats for ntd have changed, update ->top_threads
307 */
308static
309void
310top_threads_update(struct td_switch_ctx *ctx, struct evtr_thread *ntd)
311{
312 struct thread_info *tdi = ntd->userdata;
313 int i, j;
314 for (i = 0; i < ctx->nr_top_threads; ++i) {
315 struct evtr_thread *td = ctx->top_threads[i];
316 if (td == ntd) {
317 /*
318 * ntd is already in top_threads and it is at
319 * the correct ranking
320 */
321 break;
322 }
323 if (!td) {
324 /* empty slot -- just insert our thread */
325 ctx->top_threads[i] = ntd;
326 break;
327 }
328 if (((struct thread_info *)td->userdata)->runtime >=
329 tdi->runtime) {
330 /* this thread ranks higher than we do. Move on */
331 continue;
332 }
333 /*
334 * OK, we've found the first thread that we outrank, so we
335 * need to replace it w/ our thread.
336 */
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;
340
341 /* tmp holds the thread we replace */
342 tmp = ctx->top_threads[j];
343 ctx->top_threads[j] = td;
344 if (tmp == ntd) {
345 /*
346 * Our thread was already in the top list,
347 * and we just removed the second instance.
348 * Nothing more to do.
349 */
350 break;
351 }
352 td = tmp;
353 }
354 break;
355 }
356}
357
358static
359void
360ctxsw_prepare_event(void *_ctx, evtr_event_t ev)
361{
362 struct td_switch_ctx *ctx = _ctx;
363 struct cpu *c, *cpus = ctx->cpus;
364 struct thread_info *tdi;
365
366 (void)evtr;
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))
371 return;
0b33b2e3 372 printd("PREPEV on %d\n", ev->cpu);
e7c0dbba
AE
373
374 /* update first/last timestamps */
375 c = &cpus[ev->cpu];
376 if (!c->first_ts) {
377 c->first_ts = ev->ts;
378 printd("setting first_ts (%d) = %llu\n", ev->cpu,
379 c->first_ts);
380 }
381 c->last_ts = ev->ts;
382 /*
383 * c->td can be null when this is the first ctxsw event we
384 * observe for a cpu
385 */
386 if (c->td) {
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;
392 tdi->runtime = 0;
393 }
394 tdi = c->td->userdata;
395 tdi->runtime += ev->ts - c->ts;
e7c0dbba
AE
396 top_threads_update(ctx, c->td);
397 }
398
399 /* Notice that ev->td is the new thread for ctxsw events */
400 c->td = ev->td;
401 c->ts = ev->ts;
402}
403
404static
405void
406ctxsw_prepare_post(void *_ctx)
407{
408 struct td_switch_ctx *ctx = _ctx;
409 struct cpu *cpus = ctx->cpus;
410 int i;
411
412 (void)evtr;
413 ctx->first_ts = -1;
414 ctx->last_ts = 0;
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;
422 cpus[i].td = NULL;
423 cpus[i].ts = 0;
424 }
425}
426
427static
428void
429ctxsw_draw_pre(void *_ctx)
430{
431 struct td_switch_ctx *ctx = _ctx;
432 struct svg_transform textrot;
433 char comm[100];
434 double y, height, fs;
435 int i, textlen;
436 struct evtr_thread *td;
437
438 textrot.tx = 0.0 - 0.2; /* XXX */
439 textrot.sx = 1.0;
440 textrot.sy = 1.0;
441 textrot.rot = 270.0;
442
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)",
449 td->comm, td->id);
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);
454
455 textrot.ty = y + ctx->thread_rows_yoff + height;
456 svg_text_draw(ctx->svg, ctx->thread_label, &textrot,
457 comm, fs);
458 }
459}
460
461static
462void
463ctxsw_draw_event(void *_ctx, evtr_event_t ev)
464{
465 struct td_switch_ctx *ctx = _ctx;
466 struct cpu *c = &ctx->cpus[ev->cpu];
467 int i;
468
469 /*
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.
473 */
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))
479 return;
0b33b2e3 480 printd("DRAWEV %d\n", ev->cpu);
e7c0dbba
AE
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);
487 break;
488 }
489 }
490 c->td = ev->td;
491 c->ts = ev->ts;
492 }
493}
494
495static
496int
497cmd_svg(int argc, char **argv)
498{
499 svg_document_t svg;
500 int ncpus, i, ch;
501 double height, width;
502 struct rows cpu_rows, thread_rows;
503 struct cpu *cpus;
504 struct td_switch_ctx td_ctx;
505 struct evtr_filter ctxsw_filts[2] = {
506 {
507 .flags = 0,
508 .cpu = -1,
509 },
510 {
511 .flags = 0,
512 .cpu = -1,
513 },
514 };
515 struct pass_hook ctxsw_prepare = {
516 .pre = NULL,
517 .event = ctxsw_prepare_event,
518 .post = ctxsw_prepare_post,
519 .data = &td_ctx,
520 .filts = ctxsw_filts,
521 .nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
522 }, ctxsw_draw = {
523 .pre = ctxsw_draw_pre,
524 .event = ctxsw_draw_event,
525 .post = NULL,
526 .data = &td_ctx,
527 .filts = ctxsw_filts,
528 .nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
529 };
530
531 /*
532 * We are interested in thread switch and preemption
533 * events, but we don't use the data directly. Instead
534 * we rely on ev->td.
535 */
0b33b2e3 536 ctxsw_filts[0].fmt = "sw %p > %p";
e7c0dbba
AE
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;
541
542 printd("argc: %d, argv[0] = %s\n", argc, argv[0] ? argv[0] : "NULL");
543 optind = 0;
544 optreset = 1;
545 while ((ch = getopt(argc, argv, "i:")) != -1) {
546 switch (ch) {
547 case 'i':
548 if (sscanf(optarg, "%llu:%llu", &td_ctx.interval_start,
549 &td_ctx.interval_end) != 2) {
550 usage();
551 }
552 break;
553 default:
554 usage();
555 }
556
557 }
558 argc -= optind;
559 argv += optind;
560
561 height = 200.0;
562 width = 700.0;
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);
567
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) {
572 cpus[i].td = NULL;
573 cpus[i].ts = 0;
574 cpus[i].i = i;
575 cpus[i].first_ts = 0;
576 cpus[i].last_ts = 0;
577 }
578 td_ctx.cpus = cpus;
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");
584
585 /*
586 * Create rectangles to use for output.
587 */
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);
598 td_ctx.svg = svg;
599 td_ctx.xscale = -1.0;
600 td_ctx.cpu_rows = &cpu_rows;
601
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);
609
610 do_pass(&ctxsw_draw, 1);
611
612 svg_document_close(svg);
613 return 0;
614}
615
616static
617int
618cmd_show(int argc, char **argv)
619{
620 struct evtr_event ev;
621 struct evtr_query *q;
622 struct evtr_filter filt;
623 int ch;
624
625 filt.fmt = NULL;
626 optind = 0;
627 optreset = 1;
628 while ((ch = getopt(argc, argv, "f:")) != -1) {
629 switch (ch) {
630 case 'f':
631 filt.fmt = optarg;
632 break;
633 }
634 }
635 filt.flags = 0;
636 filt.cpu = -1;
637 printd("fmt = %s\n", filt.fmt ? filt.fmt : "NULL");
638 q = evtr_query_init(evtr, &filt, 1);
639 if (!q)
640 err(1, "Can't initialize query\n");
641 while(!evtr_query_next(q, &ev)) {
642 char buf[1024];
643 printf("%s\t%llu cycles\t[%.3d]\t%s:%d",
644 ev.td ? ev.td->comm : "unknown",
645 ev.ts, ev.cpu,
646 basename(ev.file), ev.line);
647 if (ev.fmt) {
648 evtr_event_data(&ev, buf, sizeof(buf));
649 printf(" !\t%s\n", buf);
650 } else {
651 printf("\n");
652 }
653 }
654 if (evtr_error(evtr)) {
655 err(1, evtr_errmsg(evtr));
656 }
657 evtr_query_destroy(q);
658 return 0;
659}
660
661int
662main(int argc, char **argv)
663{
664 int ch;
665 FILE *inf;
666 struct command *cmd;
667
668 while ((ch = getopt(argc, argv, "f:D:")) != -1) {
669 switch (ch) {
670 case 'f':
671 opt_infile = optarg;
672 break;
673 case 'D':
674 evtranalyze_debug = atoi(optarg);
675 evtr_set_debug(evtranalyze_debug);
676 break;
677 default:
678 usage();
679 }
680 }
681 argc -= optind;
682 argv += optind;
683
684 if (argc == 0) {
685 err(2, "need to specify a command\n");
686 }
687 if (!opt_infile || !strcmp(opt_infile, "-")) {
688 inf = stdin;
689 } else {
690 inf = fopen(opt_infile, "r");
691 if (!inf) {
692 err(2, "Can't open input file\n");
693 }
694 }
695
696 if (!(evtr = evtr_open_read(inf))) {
697 err(1, "Can't open evtr stream\n");
698 }
699
700
701 for (cmd = commands; cmd->name != NULL; ++cmd) {
702 if (strcmp(argv[0], cmd->name))
703 continue;
704 cmd->func(argc, argv);
705 break;
706 }
707 if (!cmd->name) {
708 err(2, "no such command: %s\n", argv[0]);
709 }
710
711 evtr_close(evtr);
712 return 0;
713}