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