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