evtranalyze: add summary command and refactor interval code
[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 <err.h>
34 #include <inttypes.h>
35 #include <libgen.h>
36 #include <math.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <unistd.h>
41
42 #include <evtr.h>
43 #include "xml.h"
44 #include "svg.h"
45
46 enum {
47         NR_TOP_THREADS = 5,
48 };
49
50 struct rows {
51         double row_increment;
52         double row_off;
53 };
54
55 #define CMD_PROTO(name) \
56         static int cmd_ ## name(int, char **)
57
58 CMD_PROTO(show);
59 CMD_PROTO(svg);
60 CMD_PROTO(summary);
61
62 struct command {
63         const char *name;
64         int (*func)(int argc, char **argv);
65 } commands[] = {
66         {
67                 .name = "show",
68                 .func = &cmd_show,
69         },
70         {
71                 .name = "svg",
72                 .func = &cmd_svg,
73         },
74         {
75                 .name = "summary",
76                 .func = &cmd_summary,
77         },
78         {
79                 .name = NULL,
80         },
81 };
82
83 evtr_t evtr;
84 char *opt_infile;
85 static int evtranalyze_debug;
86
87 #define printd(...)                                     \
88         do {                                            \
89                 if (evtranalyze_debug) {                \
90                         fprintf(stderr, __VA_ARGS__);   \
91                 }                                       \
92         } while (0)
93
94 static
95 void
96 usage(void)
97 {
98         fprintf(stderr, "bad usage :P\n");
99         exit(2);
100 }
101
102 static
103 void
104 rows_init(struct rows *rows, int n, double height, double perc)
105 {
106         double row_h;
107         rows->row_increment = height / n;
108         /* actual row height */
109         row_h = perc * rows->row_increment;
110         rows->row_off = (rows->row_increment - row_h) / 2.0;
111         assert(!isnan(rows->row_increment));
112         assert(!isnan(rows->row_off));
113 }
114
115 static
116 void
117 rows_n(struct rows *rows, int n, double *y, double *height)
118 {
119         *y = n * rows->row_increment + rows->row_off;
120         *height = rows->row_increment - 2 * rows->row_off;
121 }
122
123 /*
124  * Which fontsize to use so that the string fits in the
125  * given rect.
126  */
127 static
128 double
129 fontsize_for_rect(double width, double height, int textlen)
130 {
131         double wpc, maxh;
132         /*
133          * We start with a font size equal to the height
134          * of the rectangle and round down so that we only
135          * use a limited number of sizes.
136          *
137          * For a rectangle width comparable to the height,
138          * the text might extend outside of the rectangle.
139          * In that case we need to limit it.
140          */
141         /* available width per character */
142         wpc = width / textlen;
143         /*
144          * Assuming a rough hight/width ratio for characters,
145          * calculate the available height and round it down
146          * just to be on the safe side.
147          */
148 #define GLYPH_HIGHT_TO_WIDTH 1.5
149         maxh = GLYPH_HIGHT_TO_WIDTH * wpc * 0.9;
150         if (height > maxh) {
151                 height = maxh;
152         } else if (height < 0.01) {
153                 height = 0.01;
154         } else {
155                 /* rounding (XXX: make cheaper)*/
156                 height = log(height);
157                 height = round(height);
158                 height = exp(height);
159         }
160         return height;
161 }
162
163 struct pass_hook {
164         void (*pre)(void *);
165         void (*event)(void *, evtr_event_t);
166         void (*post)(void *);
167         void *data;
168         struct evtr_filter *filts;
169         int nfilts;
170 };
171
172 struct thread_info {
173         uint64_t runtime;
174 };
175
176 struct ts_interval {
177         uint64_t start;
178         uint64_t end;
179 };
180
181 struct td_switch_ctx {
182         svg_document_t svg;
183         struct rows *cpu_rows;
184         struct rows *thread_rows;
185         /* which events the user cares about */
186         struct ts_interval interval;
187         /* first/last event timestamps on any cpu */
188         struct ts_interval firstlast;
189         double width;
190         double xscale;  /* scale factor applied to x */
191         svg_rect_t cpu_sw_rect;
192         svg_rect_t thread_rect;
193         svg_rect_t inactive_rect;
194         svg_text_t thread_label;
195         struct cpu_table {
196                 struct cpu *cpus;
197                 int ncpus;
198         } cputab;
199         struct evtr_thread **top_threads;
200         int nr_top_threads;
201         double thread_rows_yoff;
202 };
203
204 struct cpu {
205         struct evtr_thread *td;
206         int i;          /* cpu index */
207         uint64_t ts;    /* time cpu switched to td */
208         /* timestamp for first/last event on this cpu */
209         struct ts_interval firstlast;
210         double freq;
211         uintmax_t evcnt;
212 };
213
214 static
215 void
216 do_pass(struct pass_hook *hooks, int nhooks)
217 {
218         struct evtr_filter *filts = NULL;
219         int nfilts = 0, i;
220         struct evtr_query *q;
221         struct evtr_event ev;
222
223         for (i = 0; i < nhooks; ++i) {
224                 struct pass_hook *h = &hooks[i];
225                 if (h->pre)
226                         h->pre(h->data);
227                 if (h->nfilts > 0) {
228                         filts = realloc(filts, (nfilts + h->nfilts) *
229                                         sizeof(struct evtr_filter));
230                         if (!filts)
231                                 err(1, "Out of memory");
232                         memcpy(filts + nfilts, h->filts,
233                                h->nfilts * sizeof(struct evtr_filter));
234                         nfilts += h->nfilts;
235                 }
236         }
237         q = evtr_query_init(evtr, filts, nfilts);
238         if (!q)
239                 err(1, "Can't initialize query\n");
240         while(!evtr_query_next(q, &ev)) {
241                 for (i = 0; i < nhooks; ++i) {
242                         if (hooks[i].event)
243                                 hooks[i].event(hooks[i].data, &ev);
244                 }
245         }
246         if (evtr_error(evtr)) {
247                 err(1, evtr_errmsg(evtr));
248         }
249         evtr_query_destroy(q);
250
251         for (i = 0; i < nhooks; ++i) {
252                 if (hooks[i].post)
253                         hooks[i].post(hooks[i].data);
254         }
255         if (evtr_rewind(evtr))
256                 err(1, "Can't rewind event stream\n");
257 }
258
259 static
260 void
261 draw_thread_run(struct td_switch_ctx *ctx, struct cpu *c, evtr_event_t ev, int row)
262 {
263         double x, w, y, height;
264         w = (ev->ts - c->ts) * ctx->xscale;
265         x = (ev->ts - ctx->firstlast.start) * ctx->xscale;
266         rows_n(ctx->thread_rows, row, &y, &height);
267         svg_rect_draw(ctx->svg, ctx->thread_rect, x - w,
268                       y + ctx->thread_rows_yoff, w, height);
269 }
270
271 static
272 void
273 draw_ctx_switch(struct td_switch_ctx *ctx, struct cpu *c, evtr_event_t ev)
274 {
275         struct svg_transform textrot;
276         char comm[100];
277         double x, w, fs, y, height;
278         int textlen;
279
280         assert(ctx->xscale > 0.0);
281         if (!c->ts)
282                 return;
283         /* distance to previous context switch */
284         w = (ev->ts - c->ts) * ctx->xscale;
285         x = (ev->ts - ctx->firstlast.start) * ctx->xscale;
286         if ((x - w) < 0) {
287                 fprintf(stderr, "(%ju - %ju) * %.20lf\n",
288                         (uintmax_t)ev->ts,
289                         (uintmax_t)ctx->firstlast.start, ctx->xscale);
290                 abort();
291         }
292
293         rows_n(ctx->cpu_rows, c->i, &y, &height);
294         assert(!isnan(y));
295         assert(!isnan(height));
296
297         svg_rect_draw(ctx->svg, ctx->cpu_sw_rect, x - w, y, w, height);
298
299         /*
300          * Draw the text label describing the thread we
301          * switched out of.
302          */
303         textrot.tx = x - w;
304         textrot.ty = y;
305         textrot.sx = 1.0;
306         textrot.sy = 1.0;
307         textrot.rot = 90.0;
308         textlen = snprintf(comm, sizeof(comm) - 1, "%s (%p)",
309                            c->td ? c->td->comm : "unknown",
310                                  c->td ? c->td->id: NULL);
311         if (textlen > (int)sizeof(comm))
312                 textlen = sizeof(comm) - 1;
313         comm[sizeof(comm) - 1] = '\0';
314         /*
315          * Note the width and hight are exchanged because
316          * the bounding rectangle is rotated by 90 degrees.
317          */
318         fs = fontsize_for_rect(height, w, textlen);
319         svg_text_draw(ctx->svg, ctx->thread_label, &textrot, comm,
320                       fs);
321 }
322
323
324 /*
325  * The stats for ntd have changed, update ->top_threads
326  */
327 static
328 void
329 top_threads_update(struct td_switch_ctx *ctx, struct evtr_thread *ntd)
330 {
331         struct thread_info *tdi = ntd->userdata;
332         int i, j;
333         for (i = 0; i < ctx->nr_top_threads; ++i) {
334                 struct evtr_thread *td = ctx->top_threads[i];
335                 if (td == ntd) {
336                         /*
337                          * ntd is already in top_threads and it is at
338                          * the correct ranking
339                          */
340                         break;
341                 }
342                 if (!td) {
343                         /* empty slot -- just insert our thread */
344                         ctx->top_threads[i] = ntd;
345                         break;
346                 }
347                 if (((struct thread_info *)td->userdata)->runtime >=
348                     tdi->runtime) {
349                         /* this thread ranks higher than we do. Move on */
350                         continue;
351                 }
352                 /*
353                  * OK, we've found the first thread that we outrank, so we
354                  * need to replace it w/ our thread.
355                  */
356                 td = ntd;       /* td holds the thread we will insert next */
357                 for (j = i + 1; j < ctx->nr_top_threads; ++j, ++i) {
358                         struct evtr_thread *tmp;
359
360                         /* tmp holds the thread we replace */
361                         tmp = ctx->top_threads[j];
362                         ctx->top_threads[j] = td;
363                         if (tmp == ntd) {
364                                 /*
365                                  * Our thread was already in the top list,
366                                  * and we just removed the second instance.
367                                  * Nothing more to do.
368                                  */
369                                 break;
370                         }
371                         td = tmp;
372                 }
373                 break;
374         }
375 }
376
377 static
378 void
379 ctxsw_prepare_event(void *_ctx, evtr_event_t ev)
380 {
381         struct td_switch_ctx *ctx = _ctx;
382         struct cpu *c, *cpus = ctx->cputab.cpus;
383         struct thread_info *tdi;
384
385         (void)evtr;
386         printd("test1 (%ju:%ju) : %ju\n",
387                (uintmax_t)ctx->interval.start,
388                (uintmax_t)ctx->interval.end,
389                (uintmax_t)ev->ts);
390         if ((ev->ts > ctx->interval.end) ||
391             (ev->ts < ctx->interval.start))
392                 return;
393         printd("PREPEV on %d\n", ev->cpu);
394
395         /* update first/last timestamps */
396         c = &cpus[ev->cpu];
397         if (!c->firstlast.start) {
398                 c->firstlast.start = ev->ts;
399                 printd("setting firstlast.start (%d) = %ju\n", ev->cpu,
400                        (uintmax_t)c->firstlast.start);
401         }
402         c->firstlast.end = ev->ts;
403         /*
404          * c->td can be null when this is the first ctxsw event we
405          * observe for a cpu
406          */
407         if (c->td) {
408                 /* update thread stats */
409                 if (!c->td->userdata) {
410                         if (!(tdi = malloc(sizeof(struct thread_info))))
411                                 err(1, "Out of memory");
412                         c->td->userdata = tdi;
413                         tdi->runtime = 0;
414                 }
415                 tdi = c->td->userdata;
416                 tdi->runtime += ev->ts - c->ts;
417                 top_threads_update(ctx, c->td);
418         }
419
420         /* Notice that ev->td is the new thread for ctxsw events */
421         c->td = ev->td;
422         c->ts = ev->ts;
423 }
424
425 static
426 void
427 find_first_last_ts(struct cpu_table *cputab, struct ts_interval *fl)
428 {
429         struct cpu *cpus = &cputab->cpus[0];
430         int i;
431
432         fl->start = -1;
433         fl->end = 0;
434         for (i = 0; i < cputab->ncpus; ++i) {
435                 printd("cpu%d: (%ju, %ju)\n", i,
436                        (uintmax_t)cpus[i].firstlast.start,
437                        (uintmax_t)cpus[i].firstlast.end);
438                 if (cpus[i].firstlast.start &&
439                     (cpus[i].firstlast.start < fl->start))
440                         fl->start = cpus[i].firstlast.start;
441                 if (cpus[i].firstlast.end &&
442                     (cpus[i].firstlast.end > fl->end))
443                         fl->end = cpus[i].firstlast.end;
444                 cpus[i].td = NULL;
445                 cpus[i].ts = 0;
446         }
447         printd("global (%jd, %jd)\n", (uintmax_t)fl->start, (uintmax_t)fl->end);
448 }
449
450 static
451 void
452 ctxsw_prepare_post(void *_ctx)
453 {
454         struct td_switch_ctx *ctx = _ctx;
455
456         find_first_last_ts(&ctx->cputab, &ctx->firstlast);
457 }
458
459 static
460 void
461 ctxsw_draw_pre(void *_ctx)
462 {
463         struct td_switch_ctx *ctx = _ctx;
464         struct svg_transform textrot;
465         char comm[100];
466         double y, height, fs;
467         int i, textlen;
468         struct evtr_thread *td;
469
470         textrot.tx = 0.0 - 0.2; /* XXX */
471         textrot.sx = 1.0;
472         textrot.sy = 1.0;
473         textrot.rot = 270.0;
474
475         for (i = 0; i < ctx->nr_top_threads; ++i) {
476                 td = ctx->top_threads[i];
477                 if (!td)
478                         break;
479                 rows_n(ctx->thread_rows, i, &y, &height);
480                 svg_rect_draw(ctx->svg, ctx->inactive_rect, 0.0,
481                               y + ctx->thread_rows_yoff, ctx->width, height);
482                 textlen = snprintf(comm, sizeof(comm) - 1, "%s (%p)",
483                                    td->comm, td->id);
484                 if (textlen > (int)sizeof(comm))
485                         textlen = sizeof(comm) - 1;
486                 comm[sizeof(comm) - 1] = '\0';
487                 fs = fontsize_for_rect(height, 100.0, textlen);
488
489                 textrot.ty = y + ctx->thread_rows_yoff + height;
490                 svg_text_draw(ctx->svg, ctx->thread_label, &textrot,
491                               comm, fs);
492         }
493 }
494
495 static
496 void
497 ctxsw_draw_event(void *_ctx, evtr_event_t ev)
498 {
499         struct td_switch_ctx *ctx = _ctx;
500         struct cpu *c = &ctx->cputab.cpus[ev->cpu];
501         int i;
502
503         /*
504          * ctx->firstlast.end can be 0 if there were no events
505          * in the specified interval, in which case
506          * ctx->firstlast.start is invalid too.
507          */
508         assert(!ctx->firstlast.end || (ev->ts >= ctx->firstlast.start));
509         printd("test2 (%ju:%ju) : %ju\n", (uintmax_t)ctx->interval.start,
510                (uintmax_t)ctx->interval.end, (uintmax_t)ev->ts);
511         if ((ev->ts > ctx->interval.end) ||
512             (ev->ts < ctx->interval.start))
513                 return;
514         printd("DRAWEV %d\n", ev->cpu);
515         if (c->td != ev->td) {  /* thread switch (or preemption) */
516                 draw_ctx_switch(ctx, c, ev);
517                 /* XXX: this is silly */
518                 for (i = 0; i < ctx->nr_top_threads; ++i) {
519                         if (!ctx->top_threads[i])
520                                 break;
521                         if (ctx->top_threads[i] == c->td) {
522                                 draw_thread_run(ctx, c, ev, i);
523                                 break;
524                         }
525                 }
526                 c->td = ev->td;
527                 c->ts = ev->ts;
528         }
529 }
530
531 static
532 void
533 cputab_init(struct cpu_table *ct)
534 {
535         struct cpu *cpus;
536         double *freqs;
537         int i;
538
539         if ((ct->ncpus = evtr_ncpus(evtr)) <= 0)
540                 err(1, "No cpu information!\n");
541         printd("evtranalyze: ncpus %d\n", ct->ncpus);
542         if (!(ct->cpus = malloc(sizeof(struct cpu) * ct->ncpus))) {
543                 err(1, "Can't allocate memory\n");
544         }
545         cpus = ct->cpus;
546         if (!(freqs = malloc(sizeof(double) * ct->ncpus))) {
547                 err(1, "Can't allocate memory\n");
548         }
549         if ((i = evtr_cpufreqs(evtr, freqs))) {
550                 warnc(i, "Can't get cpu frequencies\n");
551                 for (i = 0; i < ct->ncpus; ++i) {
552                         freqs[i] = -1.0;
553                 }
554         }
555
556         /* initialize cpu array */
557         for (i = 0; i < ct->ncpus; ++i) {
558                 cpus[i].td = NULL;
559                 cpus[i].ts = 0;
560                 cpus[i].i = i;
561                 cpus[i].firstlast.start = 0;
562                 cpus[i].firstlast.end = 0;
563                 cpus[i].evcnt = 0;
564                 cpus[i].freq = freqs[i];
565         }
566         free(freqs);
567 }
568
569 static
570 void
571 parse_interval(const char *_str, struct ts_interval *ts,
572                struct cpu_table *cputab)
573 {
574         double s, e, freq;
575         const char *str = _str + 1;
576
577         if ('c' == *_str) {     /* cycles */
578                 if (sscanf(str, "%" SCNu64 ":%" SCNu64,
579                            &ts->start,
580                            &ts->end) == 2)
581                         return;
582         } else if ('m' == *_str) {      /* miliseconds */
583                 if (sscanf(str, "%lf:%lf", &s, &e) == 2) {
584                         freq = cputab->cpus[0].freq;
585                         freq *= 1000.0; /* msecs */
586                         if (freq < 0.0) {
587                                 fprintf(stderr, "No frequency information"
588                                         " available\n");
589                                 err(2, "Can't convert time to cycles\n");
590                         }
591                         ts->start = s * freq;
592                         ts->end = e * freq;
593                         return;
594                 }
595         }
596         fprintf(stderr, "invalid interval format: %s\n", _str);
597         usage();
598 }
599
600
601 static
602 int
603 cmd_svg(int argc, char **argv)
604 {
605         svg_document_t svg;
606         int ch;
607         double height, width;
608         struct rows cpu_rows, thread_rows;
609         struct td_switch_ctx td_ctx;
610         const char *outpath = "output.svg";
611         struct evtr_filter ctxsw_filts[2] = {
612                 {
613                         .flags = 0,
614                         .cpu = -1,
615                 },
616                 {
617                         .flags = 0,
618                         .cpu = -1,
619                 },
620         };
621         struct pass_hook ctxsw_prepare = {
622                 .pre = NULL,
623                 .event = ctxsw_prepare_event,
624                 .post = ctxsw_prepare_post,
625                 .data = &td_ctx,
626                 .filts = ctxsw_filts,
627                 .nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
628         }, ctxsw_draw = {
629                 .pre = ctxsw_draw_pre,
630                 .event = ctxsw_draw_event,
631                 .post = NULL,
632                 .data = &td_ctx,
633                 .filts = ctxsw_filts,
634                 .nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
635         };
636
637         /*
638          * We are interested in thread switch and preemption
639          * events, but we don't use the data directly. Instead
640          * we rely on ev->td.
641          */
642         ctxsw_filts[0].fmt = "sw  %p > %p";
643         ctxsw_filts[1].fmt = "pre %p > %p";
644         td_ctx.interval.start = 0;
645         td_ctx.interval.end = -1;       /* i.e. no interval given */
646         td_ctx.nr_top_threads = NR_TOP_THREADS;
647         cputab_init(&td_ctx.cputab);    /* needed for parse_interval() */
648
649         printd("argc: %d, argv[0] = %s\n", argc, argv[0] ? argv[0] : "NULL");
650         optind = 0;
651         optreset = 1;
652         while ((ch = getopt(argc, argv, "i:o:")) != -1) {
653                 switch (ch) {
654                 case 'i':
655                         parse_interval(optarg, &td_ctx.interval,
656                                        &td_ctx.cputab);
657                         break;
658                 case 'o':
659                         outpath = optarg;
660                         break;
661                 default:
662                         usage();
663                 }
664
665         }
666         argc -= optind;
667         argv += optind;
668
669         height = 200.0;
670         width = 700.0;
671         td_ctx.width = width;
672
673         if (!(td_ctx.top_threads = calloc(td_ctx.nr_top_threads,
674                                           sizeof(struct evtr_thread *))))
675                 err(1, "Can't allocate memory\n");
676         if (!(svg = svg_document_create(outpath)))
677                 err(1, "Can't open svg document\n");
678
679         /*
680          * Create rectangles to use for output.
681          */
682         if (!(td_ctx.cpu_sw_rect = svg_rect_new("generic")))
683                 err(1, "Can't create rectangle\n");
684         if (!(td_ctx.thread_rect = svg_rect_new("thread")))
685                 err(1, "Can't create rectangle\n");
686         if (!(td_ctx.inactive_rect = svg_rect_new("inactive")))
687                 err(1, "Can't create rectangle\n");
688         /* text for thread names */
689         if (!(td_ctx.thread_label = svg_text_new("generic")))
690                 err(1, "Can't create text\n");
691         rows_init(&cpu_rows, td_ctx.cputab.ncpus, height, 0.9);
692         td_ctx.svg = svg;
693         td_ctx.xscale = -1.0;
694         td_ctx.cpu_rows = &cpu_rows;
695
696         do_pass(&ctxsw_prepare, 1);
697         td_ctx.thread_rows_yoff = height;
698         td_ctx.thread_rows = &thread_rows;
699         rows_init(td_ctx.thread_rows, td_ctx.nr_top_threads, 300, 0.9);
700         td_ctx.xscale = width / (td_ctx.firstlast.end - td_ctx.firstlast.start);
701         printd("first %ju, last %ju, xscale %lf\n",
702                (uintmax_t)td_ctx.firstlast.start, (uintmax_t)td_ctx.firstlast.end,
703                td_ctx.xscale);
704
705         do_pass(&ctxsw_draw, 1);
706
707         svg_document_close(svg);
708         return 0;
709 }
710
711 static
712 int
713 cmd_show(int argc, char **argv)
714 {
715         struct evtr_event ev;
716         struct evtr_query *q;
717         struct evtr_filter filt;
718         struct cpu_table cputab;
719         double freq;
720         int ch;
721         uint64_t last_ts = 0;
722
723         cputab_init(&cputab);
724         /*
725          * Assume all cores run on the same frequency
726          * for now. There's no reason to complicate
727          * things unless we can detect frequency change
728          * events as well.
729          *
730          * Note that the code is very simplistic and will
731          * produce garbage if the kernel doesn't fixup
732          * the timestamps for cores running with different
733          * frequencies.
734          */
735         freq = cputab.cpus[0].freq;
736         freq /= 1000000;        /* we want to print out usecs */
737         printd("using freq = %lf\n", freq);
738         filt.fmt = NULL;
739         optind = 0;
740         optreset = 1;
741         while ((ch = getopt(argc, argv, "f:")) != -1) {
742                 switch (ch) {
743                 case 'f':
744                         filt.fmt = optarg;
745                         break;
746                 }
747         }
748         filt.flags = 0;
749         filt.cpu = -1;
750         printd("fmt = %s\n", filt.fmt ? filt.fmt : "NULL");
751         q = evtr_query_init(evtr, &filt, 1);
752         if (!q)
753                 err(1, "Can't initialize query\n");
754         while(!evtr_query_next(q, &ev)) {
755                 char buf[1024];
756
757                 if (!last_ts)
758                         last_ts = ev.ts;
759                 if (freq < 0.0) {
760                         printf("%s\t%llu cycles\t[%.3d]\t%s:%d",
761                                ev.td ? ev.td->comm : "unknown",
762                                ev.ts - last_ts, ev.cpu,
763                                basename(ev.file), ev.line);
764                 } else {
765                         printf("%s\t%.3lf usecs\t[%.3d]\t%s:%d",
766                                ev.td ? ev.td->comm : "unknown",
767                                (ev.ts - last_ts) / freq, ev.cpu,
768                                basename(ev.file), ev.line);
769                 }
770                 if (ev.fmt) {
771                         evtr_event_data(&ev, buf, sizeof(buf));
772                         printf(" !\t%s\n", buf);
773                 } else {
774                         printf("\n");
775                 }
776                 last_ts = ev.ts;
777         }
778         if (evtr_error(evtr)) {
779                 err(1, evtr_errmsg(evtr));
780         }
781         evtr_query_destroy(q);
782         return 0;
783 }
784
785 static
786 int
787 cmd_summary(int argc, char **argv)
788 {
789         struct evtr_filter filt;
790         struct evtr_event ev;
791         struct evtr_query *q;
792         double freq;
793         struct cpu_table cputab;
794         struct ts_interval global;
795         uintmax_t global_evcnt;
796         int i;
797
798         (void)argc;
799         (void)argv;
800
801         cputab_init(&cputab);
802         filt.fmt = NULL;
803         filt.flags = 0;
804         filt.cpu = -1;
805
806         q = evtr_query_init(evtr, &filt, 1);
807         if (!q)
808                 err(1, "Can't initialize query\n");
809         while(!evtr_query_next(q, &ev)) {
810                 struct cpu *c = &cputab.cpus[ev.cpu];
811                 if (!c->firstlast.start)
812                         c->firstlast.start = ev.ts;
813                 ++c->evcnt;
814                 c->firstlast.end = ev.ts;
815         }
816         if (evtr_error(evtr)) {
817                 err(1, evtr_errmsg(evtr));
818         }
819         evtr_query_destroy(q);
820
821         find_first_last_ts(&cputab, &global);
822
823         freq = cputab.cpus[0].freq;
824         global_evcnt = 0;
825         for (i = 0; i < cputab.ncpus; ++i) {
826                 struct cpu *c = &cputab.cpus[i];
827                 printf("CPU %d: %jd events in %.3lf secs\n", i,
828                        c->evcnt, (c->firstlast.end - c->firstlast.start)
829                        / freq);
830                 global_evcnt += c->evcnt;
831         }
832         printf("Total: %jd events on %d cpus in %.3lf secs\n", global_evcnt,
833                cputab.ncpus, (global.end - global.start) / freq);
834         return 0;
835 }
836
837         
838
839 int
840 main(int argc, char **argv)
841 {
842         int ch;
843         FILE *inf;
844         struct command *cmd;
845
846         while ((ch = getopt(argc, argv, "f:D:")) != -1) {
847                 switch (ch) {
848                 case 'f':
849                         opt_infile = optarg;
850                         break;
851                 case 'D':
852                         evtranalyze_debug = atoi(optarg);
853                         evtr_set_debug(evtranalyze_debug);
854                         break;
855                 default:
856                         usage();
857                 }
858         }
859         argc -= optind;
860         argv += optind;
861
862         if (argc == 0) {
863                 err(2, "need to specify a command\n");
864         }
865         if (!opt_infile || !strcmp(opt_infile, "-")) {
866                 inf = stdin;
867         } else {
868                 inf = fopen(opt_infile, "r");
869                 if (!inf) {
870                         err(2, "Can't open input file\n");
871                 }
872         }
873
874         if (!(evtr = evtr_open_read(inf))) {
875                 err(1, "Can't open evtr stream\n");
876         }
877
878
879         for (cmd = commands; cmd->name != NULL; ++cmd) {
880                 if (strcmp(argv[0], cmd->name))
881                         continue;
882                 cmd->func(argc, argv);
883                 break;
884         }
885         if (!cmd->name) {
886                 err(2, "no such command: %s\n", argv[0]);
887         }
888                 
889         evtr_close(evtr);
890         return 0;
891 }