Merge branch 'vendor/EXPAT'
[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(summary);
63
64 struct command {
65         const char *name;
66         int (*func)(int argc, char **argv);
67 } commands[] = {
68         {
69                 .name = "show",
70                 .func = &cmd_show,
71         },
72         {
73                 .name = "svg",
74                 .func = &cmd_svg,
75         },
76         {
77                 .name = "summary",
78                 .func = &cmd_summary,
79         },
80         {
81                 .name = NULL,
82         },
83 };
84
85 evtr_t evtr;
86 char *opt_infile;
87 unsigned evtranalyze_debug;
88
89 static
90 void
91 printd_set_flags(const char *str, unsigned int *flags)
92 {
93         /*
94          * This is suboptimal as we don't detect
95          * invalid flags.
96          */
97         for (; *str; ++str) {
98                 if ('A' == *str) {
99                         *flags = -1;
100                         return;
101                 }
102                 if (!islower(*str))
103                         err(2, "invalid debug flag %c\n", *str);
104                 *flags |= *str - 'a';
105         }
106 }
107
108 static
109 void
110 usage(void)
111 {
112         fprintf(stderr, "bad usage :P\n");
113         exit(2);
114 }
115
116 static
117 void
118 rows_init(struct rows *rows, int n, double height, double perc)
119 {
120         double row_h;
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));
127 }
128
129 static
130 void
131 rows_n(struct rows *rows, int n, double *y, double *height)
132 {
133         *y = n * rows->row_increment + rows->row_off;
134         *height = rows->row_increment - 2 * rows->row_off;
135 }
136
137 /*
138  * Which fontsize to use so that the string fits in the
139  * given rect.
140  */
141 static
142 double
143 fontsize_for_rect(double width, double height, int textlen)
144 {
145         double wpc, maxh;
146         /*
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.
150          *
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.
154          */
155         /* available width per character */
156         wpc = width / textlen;
157         /*
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.
161          */
162 #define GLYPH_HIGHT_TO_WIDTH 1.5
163         maxh = GLYPH_HIGHT_TO_WIDTH * wpc * 0.9;
164         if (height > maxh) {
165                 height = maxh;
166         } else if (height < 0.01) {
167                 height = 0.01;
168         } else {
169                 /* rounding (XXX: make cheaper)*/
170                 height = log(height);
171                 height = round(height);
172                 height = exp(height);
173         }
174         return height;
175 }
176
177 struct pass_hook {
178         void (*pre)(void *);
179         void (*event)(void *, evtr_event_t);
180         void (*post)(void *);
181         void *data;
182         struct evtr_filter *filts;
183         int nfilts;
184 };
185
186 struct thread_info {
187         uint64_t runtime;
188 };
189
190 struct ts_interval {
191         uint64_t start;
192         uint64_t end;
193 };
194
195 struct td_switch_ctx {
196         svg_document_t svg;
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;
203         double width;
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;
209         struct cpu_table {
210                 struct cpu *cpus;
211                 int ncpus;
212         } cputab;
213         struct evtr_thread **top_threads;
214         int nr_top_threads;
215         double thread_rows_yoff;
216 };
217
218 struct cpu {
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;
224         double freq;
225         uintmax_t evcnt;
226 };
227
228 static
229 void
230 do_pass(struct pass_hook *hooks, int nhooks)
231 {
232         struct evtr_filter *filts = NULL;
233         int nfilts = 0, i;
234         struct evtr_query *q;
235         struct evtr_event ev;
236
237         for (i = 0; i < nhooks; ++i) {
238                 struct pass_hook *h = &hooks[i];
239                 if (h->pre)
240                         h->pre(h->data);
241                 if (h->nfilts > 0) {
242                         filts = realloc(filts, (nfilts + h->nfilts) *
243                                         sizeof(struct evtr_filter));
244                         if (!filts)
245                                 err(1, "Out of memory");
246                         memcpy(filts + nfilts, h->filts,
247                                h->nfilts * sizeof(struct evtr_filter));
248                         nfilts += h->nfilts;
249                 }
250         }
251         q = evtr_query_init(evtr, filts, nfilts);
252         if (!q)
253                 err(1, "Can't initialize query\n");
254         while(!evtr_query_next(q, &ev)) {
255                 for (i = 0; i < nhooks; ++i) {
256                         if (hooks[i].event)
257                                 hooks[i].event(hooks[i].data, &ev);
258                 }
259         }
260         if (evtr_error(evtr)) {
261                 err(1, evtr_errmsg(evtr));
262         }
263         evtr_query_destroy(q);
264
265         for (i = 0; i < nhooks; ++i) {
266                 if (hooks[i].post)
267                         hooks[i].post(hooks[i].data);
268         }
269         if (evtr_rewind(evtr))
270                 err(1, "Can't rewind event stream\n");
271 }
272
273 static
274 void
275 draw_thread_run(struct td_switch_ctx *ctx, struct cpu *c, evtr_event_t ev, int row)
276 {
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);
283 }
284
285 static
286 void
287 draw_ctx_switch(struct td_switch_ctx *ctx, struct cpu *c, evtr_event_t ev)
288 {
289         struct svg_transform textrot;
290         char comm[100];
291         double x, w, fs, y, height;
292         int textlen;
293
294         assert(ctx->xscale > 0.0);
295         if (!c->ts)
296                 return;
297         /* distance to previous context switch */
298         w = (ev->ts - c->ts) * ctx->xscale;
299         x = (ev->ts - ctx->firstlast.start) * ctx->xscale;
300         if ((x - w) < 0) {
301                 fprintf(stderr, "(%ju - %ju) * %.20lf\n",
302                         (uintmax_t)ev->ts,
303                         (uintmax_t)ctx->firstlast.start, ctx->xscale);
304                 abort();
305         }
306
307         rows_n(ctx->cpu_rows, c->i, &y, &height);
308         assert(!isnan(y));
309         assert(!isnan(height));
310
311         svg_rect_draw(ctx->svg, ctx->cpu_sw_rect, x - w, y, w, height);
312
313         /*
314          * Draw the text label describing the thread we
315          * switched out of.
316          */
317         textrot.tx = x - w;
318         textrot.ty = y;
319         textrot.sx = 1.0;
320         textrot.sy = 1.0;
321         textrot.rot = 90.0;
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';
328         /*
329          * Note the width and hight are exchanged because
330          * the bounding rectangle is rotated by 90 degrees.
331          */
332         fs = fontsize_for_rect(height, w, textlen);
333         svg_text_draw(ctx->svg, ctx->thread_label, &textrot, comm,
334                       fs);
335 }
336
337
338 /*
339  * The stats for ntd have changed, update ->top_threads
340  */
341 static
342 void
343 top_threads_update(struct td_switch_ctx *ctx, struct evtr_thread *ntd)
344 {
345         struct thread_info *tdi = ntd->userdata;
346         int i, j;
347         for (i = 0; i < ctx->nr_top_threads; ++i) {
348                 struct evtr_thread *td = ctx->top_threads[i];
349                 if (td == ntd) {
350                         /*
351                          * ntd is already in top_threads and it is at
352                          * the correct ranking
353                          */
354                         break;
355                 }
356                 if (!td) {
357                         /* empty slot -- just insert our thread */
358                         ctx->top_threads[i] = ntd;
359                         break;
360                 }
361                 if (((struct thread_info *)td->userdata)->runtime >=
362                     tdi->runtime) {
363                         /* this thread ranks higher than we do. Move on */
364                         continue;
365                 }
366                 /*
367                  * OK, we've found the first thread that we outrank, so we
368                  * need to replace it w/ our thread.
369                  */
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;
373
374                         /* tmp holds the thread we replace */
375                         tmp = ctx->top_threads[j];
376                         ctx->top_threads[j] = td;
377                         if (tmp == ntd) {
378                                 /*
379                                  * Our thread was already in the top list,
380                                  * and we just removed the second instance.
381                                  * Nothing more to do.
382                                  */
383                                 break;
384                         }
385                         td = tmp;
386                 }
387                 break;
388         }
389 }
390
391 static
392 void
393 ctxsw_prepare_event(void *_ctx, evtr_event_t ev)
394 {
395         struct td_switch_ctx *ctx = _ctx;
396         struct cpu *c, *cpus = ctx->cputab.cpus;
397         struct thread_info *tdi;
398
399         (void)evtr;
400         printd(INTV, "test1 (%ju:%ju) : %ju\n",
401                (uintmax_t)ctx->interval.start,
402                (uintmax_t)ctx->interval.end,
403                (uintmax_t)ev->ts);
404         if ((ev->ts > ctx->interval.end) ||
405             (ev->ts < ctx->interval.start))
406                 return;
407         printd(INTV, "PREPEV on %d\n", ev->cpu);
408
409         /* update first/last timestamps */
410         c = &cpus[ev->cpu];
411         if (!c->firstlast.start) {
412                 c->firstlast.start = ev->ts;
413         }
414         c->firstlast.end = ev->ts;
415         /*
416          * c->td can be null when this is the first ctxsw event we
417          * observe for a cpu
418          */
419         if (c->td) {
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;
425                         tdi->runtime = 0;
426                 }
427                 tdi = c->td->userdata;
428                 tdi->runtime += ev->ts - c->ts;
429                 top_threads_update(ctx, c->td);
430         }
431
432         /* Notice that ev->td is the new thread for ctxsw events */
433         c->td = ev->td;
434         c->ts = ev->ts;
435 }
436
437 static
438 void
439 find_first_last_ts(struct cpu_table *cputab, struct ts_interval *fl)
440 {
441         struct cpu *cpus = &cputab->cpus[0];
442         int i;
443
444         fl->start = -1;
445         fl->end = 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;
456                 cpus[i].td = NULL;
457                 cpus[i].ts = 0;
458         }
459         printd(INTV, "global (%jd, %jd)\n", (uintmax_t)fl->start, (uintmax_t)fl->end);
460 }
461
462 static
463 void
464 ctxsw_prepare_post(void *_ctx)
465 {
466         struct td_switch_ctx *ctx = _ctx;
467
468         find_first_last_ts(&ctx->cputab, &ctx->firstlast);
469 }
470
471 static
472 void
473 ctxsw_draw_pre(void *_ctx)
474 {
475         struct td_switch_ctx *ctx = _ctx;
476         struct svg_transform textrot;
477         char comm[100];
478         double y, height, fs;
479         int i, textlen;
480         struct evtr_thread *td;
481
482         textrot.tx = 0.0 - 0.2; /* XXX */
483         textrot.sx = 1.0;
484         textrot.sy = 1.0;
485         textrot.rot = 270.0;
486
487         for (i = 0; i < ctx->nr_top_threads; ++i) {
488                 td = ctx->top_threads[i];
489                 if (!td)
490                         break;
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)",
495                                    td->comm, td->id);
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);
500
501                 textrot.ty = y + ctx->thread_rows_yoff + height;
502                 svg_text_draw(ctx->svg, ctx->thread_label, &textrot,
503                               comm, fs);
504         }
505 }
506
507 static
508 void
509 ctxsw_draw_event(void *_ctx, evtr_event_t ev)
510 {
511         struct td_switch_ctx *ctx = _ctx;
512         struct cpu *c = &ctx->cputab.cpus[ev->cpu];
513         int i;
514
515         /*
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.
519          */
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))
525                 return;
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])
532                                 break;
533                         if (ctx->top_threads[i] == c->td) {
534                                 draw_thread_run(ctx, c, ev, i);
535                                 break;
536                         }
537                 }
538                 c->td = ev->td;
539                 c->ts = ev->ts;
540         }
541 }
542
543 static
544 void
545 cputab_init(struct cpu_table *ct)
546 {
547         struct cpu *cpus;
548         double *freqs;
549         int i;
550
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");
556         }
557         cpus = ct->cpus;
558         if (!(freqs = malloc(sizeof(double) * ct->ncpus))) {
559                 err(1, "Can't allocate memory\n");
560         }
561         if ((i = evtr_cpufreqs(evtr, freqs))) {
562                 warnc(i, "Can't get cpu frequencies\n");
563                 for (i = 0; i < ct->ncpus; ++i) {
564                         freqs[i] = -1.0;
565                 }
566         }
567
568         /* initialize cpu array */
569         for (i = 0; i < ct->ncpus; ++i) {
570                 cpus[i].td = NULL;
571                 cpus[i].ts = 0;
572                 cpus[i].i = i;
573                 cpus[i].firstlast.start = 0;
574                 cpus[i].firstlast.end = 0;
575                 cpus[i].evcnt = 0;
576                 cpus[i].freq = freqs[i];
577         }
578         free(freqs);
579 }
580
581 static
582 void
583 parse_interval(const char *_str, struct ts_interval *ts,
584                struct cpu_table *cputab)
585 {
586         double s, e, freq;
587         const char *str = _str + 1;
588
589         if ('c' == *_str) {     /* cycles */
590                 if (sscanf(str, "%" SCNu64 ":%" SCNu64,
591                            &ts->start,
592                            &ts->end) == 2)
593                         return;
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 */
598                         if (freq < 0.0) {
599                                 fprintf(stderr, "No frequency information"
600                                         " available\n");
601                                 err(2, "Can't convert time to cycles\n");
602                         }
603                         ts->start = s * freq;
604                         ts->end = e * freq;
605                         return;
606                 }
607         }
608         fprintf(stderr, "invalid interval format: %s\n", _str);
609         usage();
610 }
611
612
613 static
614 int
615 cmd_svg(int argc, char **argv)
616 {
617         svg_document_t svg;
618         int ch;
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] = {
624                 {
625                         .flags = 0,
626                         .cpu = -1,
627                 },
628                 {
629                         .flags = 0,
630                         .cpu = -1,
631                 },
632         };
633         struct pass_hook ctxsw_prepare = {
634                 .pre = NULL,
635                 .event = ctxsw_prepare_event,
636                 .post = ctxsw_prepare_post,
637                 .data = &td_ctx,
638                 .filts = ctxsw_filts,
639                 .nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
640         }, ctxsw_draw = {
641                 .pre = ctxsw_draw_pre,
642                 .event = ctxsw_draw_event,
643                 .post = NULL,
644                 .data = &td_ctx,
645                 .filts = ctxsw_filts,
646                 .nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
647         };
648
649         /*
650          * We are interested in thread switch and preemption
651          * events, but we don't use the data directly. Instead
652          * we rely on ev->td.
653          */
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() */
660
661         optind = 0;
662         optreset = 1;
663         while ((ch = getopt(argc, argv, "i:o:")) != -1) {
664                 switch (ch) {
665                 case 'i':
666                         parse_interval(optarg, &td_ctx.interval,
667                                        &td_ctx.cputab);
668                         break;
669                 case 'o':
670                         outpath = optarg;
671                         break;
672                 default:
673                         usage();
674                 }
675
676         }
677         argc -= optind;
678         argv += optind;
679
680         height = 200.0;
681         width = 700.0;
682         td_ctx.width = width;
683
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");
689
690         /*
691          * Create rectangles to use for output.
692          */
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);
703         td_ctx.svg = svg;
704         td_ctx.xscale = -1.0;
705         td_ctx.cpu_rows = &cpu_rows;
706
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,
714                td_ctx.xscale);
715
716         do_pass(&ctxsw_draw, 1);
717
718         svg_document_close(svg);
719         return 0;
720 }
721
722 static
723 int
724 cmd_show(int argc, char **argv)
725 {
726         struct evtr_event ev;
727         struct evtr_query *q;
728         struct evtr_filter filt;
729         struct cpu_table cputab;
730         double freq;
731         int ch;
732         uint64_t last_ts = 0;
733
734         cputab_init(&cputab);
735         /*
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
739          * events as well.
740          *
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
744          * frequencies.
745          */
746         freq = cputab.cpus[0].freq;
747         freq /= 1000000;        /* we want to print out usecs */
748         printd(MISC, "using freq = %lf\n", freq);
749         filt.fmt = NULL;
750         optind = 0;
751         optreset = 1;
752         while ((ch = getopt(argc, argv, "f:")) != -1) {
753                 switch (ch) {
754                 case 'f':
755                         filt.fmt = optarg;
756                         break;
757                 }
758         }
759         filt.flags = 0;
760         filt.cpu = -1;
761         q = evtr_query_init(evtr, &filt, 1);
762         if (!q)
763                 err(1, "Can't initialize query\n");
764         while(!evtr_query_next(q, &ev)) {
765                 char buf[1024];
766
767                 if (!last_ts)
768                         last_ts = ev.ts;
769                 if (freq < 0.0) {
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);
774                 } else {
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);
779                 }
780                 if (ev.fmt) {
781                         evtr_event_data(&ev, buf, sizeof(buf));
782                         printf(" !\t%s\n", buf);
783                 } else {
784                         printf("\n");
785                 }
786                 last_ts = ev.ts;
787         }
788         if (evtr_error(evtr)) {
789                 err(1, evtr_errmsg(evtr));
790         }
791         evtr_query_destroy(q);
792         return 0;
793 }
794
795 static
796 int
797 cmd_summary(int argc, char **argv)
798 {
799         struct evtr_filter filt;
800         struct evtr_event ev;
801         struct evtr_query *q;
802         double freq;
803         struct cpu_table cputab;
804         struct ts_interval global;
805         uintmax_t global_evcnt;
806         int i;
807
808         (void)argc;
809         (void)argv;
810
811         cputab_init(&cputab);
812         filt.fmt = NULL;
813         filt.flags = 0;
814         filt.cpu = -1;
815
816         q = evtr_query_init(evtr, &filt, 1);
817         if (!q)
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;
823                 ++c->evcnt;
824                 c->firstlast.end = ev.ts;
825         }
826         if (evtr_error(evtr)) {
827                 err(1, evtr_errmsg(evtr));
828         }
829         evtr_query_destroy(q);
830
831         find_first_last_ts(&cputab, &global);
832
833         freq = cputab.cpus[0].freq;
834         global_evcnt = 0;
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)
839                        / freq);
840                 global_evcnt += c->evcnt;
841         }
842         printf("Total: %jd events on %d cpus in %.3lf secs\n", global_evcnt,
843                cputab.ncpus, (global.end - global.start) / freq);
844         return 0;
845 }
846
847         
848
849 int
850 main(int argc, char **argv)
851 {
852         int ch;
853         FILE *inf;
854         struct command *cmd;
855         char *tmp;
856
857         while ((ch = getopt(argc, argv, "f:D:")) != -1) {
858                 switch (ch) {
859                 case 'f':
860                         opt_infile = optarg;
861                         break;
862                 case 'D':
863                         if ((tmp = strchr(optarg, ':'))) {
864                                 *tmp++ = '\0';
865                                 evtr_set_debug(tmp);
866                         }
867                         printd_set_flags(optarg, &evtranalyze_debug);
868                         break;
869                 default:
870                         usage();
871                 }
872         }
873         argc -= optind;
874         argv += optind;
875
876         if (argc == 0) {
877                 err(2, "need to specify a command\n");
878         }
879         if (!opt_infile || !strcmp(opt_infile, "-")) {
880                 inf = stdin;
881         } else {
882                 inf = fopen(opt_infile, "r");
883                 if (!inf) {
884                         err(2, "Can't open input file\n");
885                 }
886         }
887
888         if (!(evtr = evtr_open_read(inf))) {
889                 err(1, "Can't open evtr stream\n");
890         }
891
892
893         for (cmd = commands; cmd->name != NULL; ++cmd) {
894                 if (strcmp(argv[0], cmd->name))
895                         continue;
896                 cmd->func(argc, argv);
897                 break;
898         }
899         if (!cmd->name) {
900                 err(2, "no such command: %s\n", argv[0]);
901         }
902                 
903         evtr_close(evtr);
904         return 0;
905 }