59d7b2cc99a53a8d557f4c21e4104f8d67ddd5a5
[dragonfly.git] / usr.bin / evtranalyze / evtranalyze.c
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 <ctype.h>
34 #include <err.h>
35 #include <inttypes.h>
36 #include <libgen.h>
37 #include <math.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42
43 #include <evtr.h>
44 #include "xml.h"
45 #include "svg.h"
46 #include "trivial.h"
47
48 enum {
49         NR_TOP_THREADS = 5,
50 };
51
52 struct rows {
53         double row_increment;
54         double row_off;
55 };
56
57 #define CMD_PROTO(name) \
58         static int cmd_ ## name(int, char **)
59
60 CMD_PROTO(show);
61 CMD_PROTO(svg);
62 CMD_PROTO(stats);
63 CMD_PROTO(summary);
64
65 struct command {
66         const char *name;
67         int (*func)(int argc, char **argv);
68 } commands[] = {
69         {
70                 .name = "show",
71                 .func = &cmd_show,
72         },
73         {
74                 .name = "svg",
75                 .func = &cmd_svg,
76         },
77         {
78                 .name = "stats",
79                 .func = &cmd_stats,
80         },
81         {
82                 .name = "summary",
83                 .func = &cmd_summary,
84         },
85         {
86                 .name = NULL,
87         },
88 };
89
90 evtr_t evtr;
91 char *opt_infile;
92 unsigned evtranalyze_debug;
93
94 static
95 void
96 printd_set_flags(const char *str, unsigned int *flags)
97 {
98         /*
99          * This is suboptimal as we don't detect
100          * invalid flags.
101          */
102         for (; *str; ++str) {
103                 if ('A' == *str) {
104                         *flags = -1;
105                         return;
106                 }
107                 if (!islower(*str))
108                         err(2, "invalid debug flag %c\n", *str);
109                 *flags |= 1 << (*str - 'a');
110         }
111 }
112
113 static
114 void
115 usage(void)
116 {
117         fprintf(stderr, "bad usage :P\n");
118         exit(2);
119 }
120
121 static
122 void
123 rows_init(struct rows *rows, int n, double height, double perc)
124 {
125         double row_h;
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));
132 }
133
134 static
135 void
136 rows_n(struct rows *rows, int n, double *y, double *height)
137 {
138         *y = n * rows->row_increment + rows->row_off;
139         *height = rows->row_increment - 2 * rows->row_off;
140 }
141
142 /*
143  * Which fontsize to use so that the string fits in the
144  * given rect.
145  */
146 static
147 double
148 fontsize_for_rect(double width, double height, int textlen)
149 {
150         double wpc, maxh;
151         /*
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.
155          *
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.
159          */
160         /* available width per character */
161         wpc = width / textlen;
162         /*
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.
166          */
167 #define GLYPH_HIGHT_TO_WIDTH 1.5
168         maxh = GLYPH_HIGHT_TO_WIDTH * wpc * 0.9;
169         if (height > maxh) {
170                 height = maxh;
171         } else if (height < 0.01) {
172                 height = 0.01;
173         } else {
174                 /* rounding (XXX: make cheaper)*/
175                 height = log(height);
176                 height = round(height);
177                 height = exp(height);
178         }
179         return height;
180 }
181
182 struct pass_hook {
183         void (*pre)(void *);
184         void (*event)(void *, evtr_event_t);
185         void (*post)(void *);
186         void *data;
187         struct evtr_filter *filts;
188         int nfilts;
189 };
190
191 struct thread_info {
192         uint64_t runtime;
193 };
194
195 struct ts_interval {
196         uint64_t start;
197         uint64_t end;
198 };
199
200 struct td_switch_ctx {
201         svg_document_t svg;
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;
208         double width;
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;
214         struct cpu_table {
215                 struct cpu *cpus;
216                 int ncpus;
217         } cputab;
218         struct evtr_thread **top_threads;
219         int nr_top_threads;
220         double thread_rows_yoff;
221 };
222
223 struct cpu {
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;
229         double freq;
230         uintmax_t evcnt;
231 };
232
233 static
234 void
235 do_pass(struct pass_hook *hooks, int nhooks)
236 {
237         struct evtr_filter *filts = NULL;
238         int nfilts = 0, i;
239         struct evtr_query *q;
240         struct evtr_event ev;
241
242         for (i = 0; i < nhooks; ++i) {
243                 struct pass_hook *h = &hooks[i];
244                 if (h->pre)
245                         h->pre(h->data);
246                 if (h->nfilts > 0) {
247                         filts = realloc(filts, (nfilts + h->nfilts) *
248                                         sizeof(struct evtr_filter));
249                         if (!filts)
250                                 err(1, "Out of memory");
251                         memcpy(filts + nfilts, h->filts,
252                                h->nfilts * sizeof(struct evtr_filter));
253                         nfilts += h->nfilts;
254                 }
255         }
256         q = evtr_query_init(evtr, filts, nfilts);
257         if (!q)
258                 err(1, "Can't initialize query\n");
259         while(!evtr_query_next(q, &ev)) {
260                 for (i = 0; i < nhooks; ++i) {
261                         if (hooks[i].event)
262                                 hooks[i].event(hooks[i].data, &ev);
263                 }
264         }
265         if (evtr_query_error(q)) {
266                 err(1, evtr_query_errmsg(q));
267         }
268         evtr_query_destroy(q);
269
270         for (i = 0; i < nhooks; ++i) {
271                 if (hooks[i].post)
272                         hooks[i].post(hooks[i].data);
273         }
274         if (evtr_rewind(evtr))
275                 err(1, "Can't rewind event stream\n");
276 }
277
278 static
279 void
280 draw_thread_run(struct td_switch_ctx *ctx, struct cpu *c, evtr_event_t ev, int row)
281 {
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);
288 }
289
290 static
291 void
292 draw_ctx_switch(struct td_switch_ctx *ctx, struct cpu *c, evtr_event_t ev)
293 {
294         struct svg_transform textrot;
295         char comm[100];
296         double x, w, fs, y, height;
297         int textlen;
298
299         assert(ctx->xscale > 0.0);
300         if (!c->ts)
301                 return;
302         /* distance to previous context switch */
303         w = (ev->ts - c->ts) * ctx->xscale;
304         x = (ev->ts - ctx->firstlast.start) * ctx->xscale;
305         if ((x - w) < 0) {
306                 fprintf(stderr, "(%ju - %ju) * %.20lf\n",
307                         (uintmax_t)ev->ts,
308                         (uintmax_t)ctx->firstlast.start, ctx->xscale);
309                 abort();
310         }
311
312         rows_n(ctx->cpu_rows, c->i, &y, &height);
313         assert(!isnan(y));
314         assert(!isnan(height));
315
316         svg_rect_draw(ctx->svg, ctx->cpu_sw_rect, x - w, y, w, height);
317
318         /*
319          * Draw the text label describing the thread we
320          * switched out of.
321          */
322         textrot.tx = x - w;
323         textrot.ty = y;
324         textrot.sx = 1.0;
325         textrot.sy = 1.0;
326         textrot.rot = 90.0;
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';
333         /*
334          * Note the width and hight are exchanged because
335          * the bounding rectangle is rotated by 90 degrees.
336          */
337         fs = fontsize_for_rect(height, w, textlen);
338         svg_text_draw(ctx->svg, ctx->thread_label, &textrot, comm,
339                       fs);
340 }
341
342
343 /*
344  * The stats for ntd have changed, update ->top_threads
345  */
346 static
347 void
348 top_threads_update(struct td_switch_ctx *ctx, struct evtr_thread *ntd)
349 {
350         struct thread_info *tdi = ntd->userdata;
351         int i, j;
352         for (i = 0; i < ctx->nr_top_threads; ++i) {
353                 struct evtr_thread *td = ctx->top_threads[i];
354                 if (td == ntd) {
355                         /*
356                          * ntd is already in top_threads and it is at
357                          * the correct ranking
358                          */
359                         break;
360                 }
361                 if (!td) {
362                         /* empty slot -- just insert our thread */
363                         ctx->top_threads[i] = ntd;
364                         break;
365                 }
366                 if (((struct thread_info *)td->userdata)->runtime >=
367                     tdi->runtime) {
368                         /* this thread ranks higher than we do. Move on */
369                         continue;
370                 }
371                 /*
372                  * OK, we've found the first thread that we outrank, so we
373                  * need to replace it w/ our thread.
374                  */
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;
378
379                         /* tmp holds the thread we replace */
380                         tmp = ctx->top_threads[j];
381                         ctx->top_threads[j] = td;
382                         if (tmp == ntd) {
383                                 /*
384                                  * Our thread was already in the top list,
385                                  * and we just removed the second instance.
386                                  * Nothing more to do.
387                                  */
388                                 break;
389                         }
390                         td = tmp;
391                 }
392                 break;
393         }
394 }
395
396 static
397 void
398 ctxsw_prepare_event(void *_ctx, evtr_event_t ev)
399 {
400         struct td_switch_ctx *ctx = _ctx;
401         struct cpu *c, *cpus = ctx->cputab.cpus;
402         struct thread_info *tdi;
403
404         (void)evtr;
405         printd(INTV, "test1 (%ju:%ju) : %ju\n",
406                (uintmax_t)ctx->interval.start,
407                (uintmax_t)ctx->interval.end,
408                (uintmax_t)ev->ts);
409         if ((ev->ts > ctx->interval.end) ||
410             (ev->ts < ctx->interval.start))
411                 return;
412         printd(INTV, "PREPEV on %d\n", ev->cpu);
413
414         /* update first/last timestamps */
415         c = &cpus[ev->cpu];
416         if (!c->firstlast.start) {
417                 c->firstlast.start = ev->ts;
418         }
419         c->firstlast.end = ev->ts;
420         /*
421          * c->td can be null when this is the first ctxsw event we
422          * observe for a cpu
423          */
424         if (c->td) {
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;
430                         tdi->runtime = 0;
431                 }
432                 tdi = c->td->userdata;
433                 tdi->runtime += ev->ts - c->ts;
434                 top_threads_update(ctx, c->td);
435         }
436
437         /* Notice that ev->td is the new thread for ctxsw events */
438         c->td = ev->td;
439         c->ts = ev->ts;
440 }
441
442 static
443 void
444 find_first_last_ts(struct cpu_table *cputab, struct ts_interval *fl)
445 {
446         struct cpu *cpus = &cputab->cpus[0];
447         int i;
448
449         fl->start = -1;
450         fl->end = 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;
461                 cpus[i].td = NULL;
462                 cpus[i].ts = 0;
463         }
464         printd(INTV, "global (%jd, %jd)\n", (uintmax_t)fl->start, (uintmax_t)fl->end);
465 }
466
467 static
468 void
469 ctxsw_prepare_post(void *_ctx)
470 {
471         struct td_switch_ctx *ctx = _ctx;
472
473         find_first_last_ts(&ctx->cputab, &ctx->firstlast);
474 }
475
476 static
477 void
478 ctxsw_draw_pre(void *_ctx)
479 {
480         struct td_switch_ctx *ctx = _ctx;
481         struct svg_transform textrot;
482         char comm[100];
483         double y, height, fs;
484         int i, textlen;
485         struct evtr_thread *td;
486
487         textrot.tx = 0.0 - 0.2; /* XXX */
488         textrot.sx = 1.0;
489         textrot.sy = 1.0;
490         textrot.rot = 270.0;
491
492         for (i = 0; i < ctx->nr_top_threads; ++i) {
493                 td = ctx->top_threads[i];
494                 if (!td)
495                         break;
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)",
500                                    td->comm, td->id);
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);
505
506                 textrot.ty = y + ctx->thread_rows_yoff + height;
507                 svg_text_draw(ctx->svg, ctx->thread_label, &textrot,
508                               comm, fs);
509         }
510 }
511
512 static
513 void
514 ctxsw_draw_event(void *_ctx, evtr_event_t ev)
515 {
516         struct td_switch_ctx *ctx = _ctx;
517         struct cpu *c = &ctx->cputab.cpus[ev->cpu];
518         int i;
519
520         /*
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.
524          */
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))
530                 return;
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])
537                                 break;
538                         if (ctx->top_threads[i] == c->td) {
539                                 draw_thread_run(ctx, c, ev, i);
540                                 break;
541                         }
542                 }
543                 c->td = ev->td;
544                 c->ts = ev->ts;
545         }
546 }
547
548 static
549 void
550 cputab_init(struct cpu_table *ct)
551 {
552         struct cpu *cpus;
553         double *freqs;
554         int i;
555
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");
561         }
562         cpus = ct->cpus;
563         if (!(freqs = malloc(sizeof(double) * ct->ncpus))) {
564                 err(1, "Can't allocate memory\n");
565         }
566         if ((i = evtr_cpufreqs(evtr, freqs))) {
567                 warnc(i, "Can't get cpu frequencies\n");
568                 for (i = 0; i < ct->ncpus; ++i) {
569                         freqs[i] = -1.0;
570                 }
571         }
572
573         /* initialize cpu array */
574         for (i = 0; i < ct->ncpus; ++i) {
575                 cpus[i].td = NULL;
576                 cpus[i].ts = 0;
577                 cpus[i].i = i;
578                 cpus[i].firstlast.start = 0;
579                 cpus[i].firstlast.end = 0;
580                 cpus[i].evcnt = 0;
581                 cpus[i].freq = freqs[i];
582         }
583         free(freqs);
584 }
585
586 static
587 void
588 parse_interval(const char *_str, struct ts_interval *ts,
589                struct cpu_table *cputab)
590 {
591         double s, e, freq;
592         const char *str = _str + 1;
593
594         if ('c' == *_str) {     /* cycles */
595                 if (sscanf(str, "%" SCNu64 ":%" SCNu64,
596                            &ts->start,
597                            &ts->end) == 2)
598                         return;
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 */
603                         if (freq < 0.0) {
604                                 fprintf(stderr, "No frequency information"
605                                         " available\n");
606                                 err(2, "Can't convert time to cycles\n");
607                         }
608                         ts->start = s * freq;
609                         ts->end = e * freq;
610                         return;
611                 }
612         }
613         fprintf(stderr, "invalid interval format: %s\n", _str);
614         usage();
615 }
616
617
618 static
619 int
620 cmd_svg(int argc, char **argv)
621 {
622         svg_document_t svg;
623         int ch;
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] = {
629                 {
630                         .flags = 0,
631                         .cpu = -1,
632                         .ev_type = EVTR_TYPE_PROBE,
633                 },
634                 {
635                         .flags = 0,
636                         .cpu = -1,
637                         .ev_type = EVTR_TYPE_PROBE,
638                 },
639         };
640         struct pass_hook ctxsw_prepare = {
641                 .pre = NULL,
642                 .event = ctxsw_prepare_event,
643                 .post = ctxsw_prepare_post,
644                 .data = &td_ctx,
645                 .filts = ctxsw_filts,
646                 .nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
647         }, ctxsw_draw = {
648                 .pre = ctxsw_draw_pre,
649                 .event = ctxsw_draw_event,
650                 .post = NULL,
651                 .data = &td_ctx,
652                 .filts = ctxsw_filts,
653                 .nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
654         };
655
656         /*
657          * We are interested in thread switch and preemption
658          * events, but we don't use the data directly. Instead
659          * we rely on ev->td.
660          */
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() */
667
668         optind = 0;
669         optreset = 1;
670         while ((ch = getopt(argc, argv, "i:o:")) != -1) {
671                 switch (ch) {
672                 case 'i':
673                         parse_interval(optarg, &td_ctx.interval,
674                                        &td_ctx.cputab);
675                         break;
676                 case 'o':
677                         outpath = optarg;
678                         break;
679                 default:
680                         usage();
681                 }
682
683         }
684         argc -= optind;
685         argv += optind;
686
687         height = 200.0;
688         width = 700.0;
689         td_ctx.width = width;
690
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");
696
697         /*
698          * Create rectangles to use for output.
699          */
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);
710         td_ctx.svg = svg;
711         td_ctx.xscale = -1.0;
712         td_ctx.cpu_rows = &cpu_rows;
713
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,
721                td_ctx.xscale);
722
723         do_pass(&ctxsw_draw, 1);
724
725         svg_document_close(svg);
726         return 0;
727 }
728
729 static
730 int
731 cmd_show(int argc, char **argv)
732 {
733         struct evtr_event ev;
734         struct evtr_query *q;
735         struct evtr_filter filt;
736         struct cpu_table cputab;
737         double freq;
738         int ch;
739         uint64_t last_ts = 0;
740
741         cputab_init(&cputab);
742         /*
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
746          * events as well.
747          *
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
751          * frequencies.
752          */
753         freq = cputab.cpus[0].freq;
754         freq /= 1000000;        /* we want to print out usecs */
755         printd(MISC, "using freq = %lf\n", freq);
756         filt.flags = 0;
757         filt.cpu = -1;
758         filt.ev_type = EVTR_TYPE_PROBE;
759         filt.fmt = NULL;
760         optind = 0;
761         optreset = 1;
762         while ((ch = getopt(argc, argv, "f:")) != -1) {
763                 switch (ch) {
764                 case 'f':
765                         filt.fmt = optarg;
766                         break;
767                 }
768         }
769         q = evtr_query_init(evtr, &filt, 1);
770         if (!q)
771                 err(1, "Can't initialize query\n");
772         while(!evtr_query_next(q, &ev)) {
773                 char buf[1024];
774
775                 if (!last_ts)
776                         last_ts = ev.ts;
777                 if (freq < 0.0) {
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);
782                 } else {
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);
787                 }
788                 if (ev.fmt) {
789                         evtr_event_data(&ev, buf, sizeof(buf));
790                         printf(" !\t%s\n", buf);
791                 } else {
792                         printf("\n");
793                 }
794                 last_ts = ev.ts;
795         }
796         if (evtr_query_error(q)) {
797                 err(1, evtr_query_errmsg(q));
798         }
799         evtr_query_destroy(q);
800         return 0;
801 }
802
803 static
804 int
805 cmd_stats(int argc, char **argv)
806 {
807         struct evtr_event ev;
808         struct evtr_query *q;
809         struct evtr_filter filt;
810         struct cpu_table cputab;
811         double freq;
812         uint64_t last_ts = 0;
813         enum evtr_value_type type = EVTR_VAL_INT;
814         uintmax_t sum, occurences;
815
816         cputab_init(&cputab);
817         /*
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
821          * events as well.
822          *
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
826          * frequencies.
827          */
828         freq = cputab.cpus[0].freq;
829         freq /= 1000000;        /* we want to print out usecs */
830         printd(MISC, "using freq = %lf\n", freq);
831         filt.flags = 0;
832         filt.cpu = -1;
833         filt.ev_type = EVTR_TYPE_STMT;
834         filt.var = NULL;
835         optind = 0;
836         optreset = 1;
837
838         if (argc != 2)
839                 err(2, "Need exactly one variable");
840         filt.var = argv[1];
841         q = evtr_query_init(evtr, &filt, 1);
842         if (!q)
843                 err(1, "Can't initialize query\n");
844         sum = occurences = 0;
845         while(!evtr_query_next(q, &ev)) {
846
847                 if (!last_ts)
848                         last_ts = ev.ts;
849
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);
854                 } else {
855                         printf("var \"%s\" = %jx\n",
856                                filt.var, ev.stmt.val->num);
857                         sum += ev.stmt.val->num;
858                         ++occurences;
859                 }
860                 last_ts = ev.ts;
861         }
862         if (evtr_query_error(q)) {
863                 err(1, evtr_query_errmsg(q));
864         }
865         printf("median for variable %s is %lf\n", argv[1], (double)sum / occurences);
866         evtr_query_destroy(q);
867         return 0;
868 }
869
870
871 static
872 int
873 cmd_summary(int argc, char **argv)
874 {
875         struct evtr_filter filt;
876         struct evtr_event ev;
877         struct evtr_query *q;
878         double freq;
879         struct cpu_table cputab;
880         struct ts_interval global;
881         uintmax_t global_evcnt;
882         int i;
883
884         (void)argc;
885         (void)argv;
886
887         cputab_init(&cputab);
888         filt.ev_type = EVTR_TYPE_PROBE;
889         filt.fmt = NULL;
890         filt.flags = 0;
891         filt.cpu = -1;
892
893         q = evtr_query_init(evtr, &filt, 1);
894         if (!q)
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;
900                 ++c->evcnt;
901                 c->firstlast.end = ev.ts;
902         }
903         if (evtr_query_error(q)) {
904                 err(1, evtr_query_errmsg(q));
905         }
906         evtr_query_destroy(q);
907
908         find_first_last_ts(&cputab, &global);
909
910         freq = cputab.cpus[0].freq;
911         global_evcnt = 0;
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)
916                        / freq);
917                 global_evcnt += c->evcnt;
918         }
919         printf("Total: %jd events on %d cpus in %.3lf secs\n", global_evcnt,
920                cputab.ncpus, (global.end - global.start) / freq);
921         return 0;
922 }
923
924         
925
926 int
927 main(int argc, char **argv)
928 {
929         int ch;
930         FILE *inf;
931         struct command *cmd;
932         char *tmp;
933
934         while ((ch = getopt(argc, argv, "f:D:")) != -1) {
935                 switch (ch) {
936                 case 'f':
937                         opt_infile = optarg;
938                         break;
939                 case 'D':
940                         if ((tmp = strchr(optarg, ':'))) {
941                                 *tmp++ = '\0';
942                                 evtr_set_debug(tmp);
943                         }
944                         printd_set_flags(optarg, &evtranalyze_debug);
945                         break;
946                 default:
947                         usage();
948                 }
949         }
950         argc -= optind;
951         argv += optind;
952
953         if (argc == 0) {
954                 err(2, "need to specify a command\n");
955         }
956         if (!opt_infile || !strcmp(opt_infile, "-")) {
957                 inf = stdin;
958         } else {
959                 inf = fopen(opt_infile, "r");
960                 if (!inf) {
961                         err(2, "Can't open input file\n");
962                 }
963         }
964
965         if (!(evtr = evtr_open_read(inf))) {
966                 err(1, "Can't open evtr stream\n");
967         }
968
969
970         for (cmd = commands; cmd->name != NULL; ++cmd) {
971                 if (strcmp(argv[0], cmd->name))
972                         continue;
973                 cmd->func(argc, argv);
974                 break;
975         }
976         if (!cmd->name) {
977                 err(2, "no such command: %s\n", argv[0]);
978         }
979                 
980         evtr_close(evtr);
981         return 0;
982 }