evtr: dump core frequencies and use them to print timestamps in usecs
[dragonfly.git] / usr.bin / evtranalyze / evtranalyze.c
CommitLineData
e7c0dbba
AE
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
45enum {
46 NR_TOP_THREADS = 5,
47};
48
49struct rows {
50 double row_increment;
51 double row_off;
52};
53
54#define CMD_PROTO(name) \
55 static int cmd_ ## name(int, char **)
56
57CMD_PROTO(show);
58CMD_PROTO(svg);
59
60struct 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
77evtr_t evtr;
78char *opt_infile;
79static int evtranalyze_debug;
80
81#define printd(...) \
82 do { \
83 if (evtranalyze_debug) { \
84 fprintf(stderr, __VA_ARGS__); \
85 } \
86 } while (0)
87
88static
89void
90usage(void)
91{
92 fprintf(stderr, "bad usage :P\n");
93 exit(2);
94}
95
96static
97void
98rows_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
109static
110void
111rows_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 */
121static
122double
123fontsize_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
157struct 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
166struct thread_info {
167 uint64_t runtime;
168};
169
170struct 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;
956f9b36
AE
182 struct cpu_table {
183 struct cpu *cpus;
184 int ncpus;
185 } cputab;
e7c0dbba
AE
186 struct evtr_thread **top_threads;
187 int nr_top_threads;
188 double thread_rows_yoff;
189};
190
191struct 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;
956f9b36 196 double freq;
e7c0dbba
AE
197};
198
199static
200void
201do_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");
0b33b2e3 217 memcpy(filts + nfilts, h->filts,
e7c0dbba
AE
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
244static
245void
246draw_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
256static
257void
258draw_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 */
311static
312void
313top_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
361static
362void
363ctxsw_prepare_event(void *_ctx, evtr_event_t ev)
364{
365 struct td_switch_ctx *ctx = _ctx;
956f9b36 366 struct cpu *c, *cpus = ctx->cputab.cpus;
e7c0dbba
AE
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;
0b33b2e3 375 printd("PREPEV on %d\n", ev->cpu);
e7c0dbba
AE
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;
e7c0dbba
AE
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
407static
408void
409ctxsw_prepare_post(void *_ctx)
410{
411 struct td_switch_ctx *ctx = _ctx;
956f9b36 412 struct cpu *cpus = ctx->cputab.cpus;
e7c0dbba
AE
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);
956f9b36 419 for (i = 0; i < ctx->cputab.ncpus; ++i) {
e7c0dbba
AE
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
430static
431void
432ctxsw_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
464static
465void
466ctxsw_draw_event(void *_ctx, evtr_event_t ev)
467{
468 struct td_switch_ctx *ctx = _ctx;
956f9b36 469 struct cpu *c = &ctx->cputab.cpus[ev->cpu];
e7c0dbba
AE
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;
0b33b2e3 483 printd("DRAWEV %d\n", ev->cpu);
e7c0dbba
AE
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
498static
956f9b36
AE
499void
500cputab_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
536static
e7c0dbba
AE
537int
538cmd_svg(int argc, char **argv)
539{
540 svg_document_t svg;
956f9b36 541 int ch;
e7c0dbba
AE
542 double height, width;
543 struct rows cpu_rows, thread_rows;
e7c0dbba
AE
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 */
0b33b2e3 576 ctxsw_filts[0].fmt = "sw %p > %p";
e7c0dbba
AE
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;
e7c0dbba 604
956f9b36 605 cputab_init(&td_ctx.cputab);
e7c0dbba
AE
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");
956f9b36 623 rows_init(&cpu_rows, td_ctx.cputab.ncpus, height, 0.9);
e7c0dbba
AE
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
642static
643int
644cmd_show(int argc, char **argv)
645{
646 struct evtr_event ev;
647 struct evtr_query *q;
648 struct evtr_filter filt;
956f9b36
AE
649 struct cpu_table cputab;
650 double freq;
e7c0dbba 651 int ch;
956f9b36 652 uint64_t last_ts = 0;
e7c0dbba 653
956f9b36
AE
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);
e7c0dbba
AE
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];
956f9b36
AE
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 }
e7c0dbba
AE
701 if (ev.fmt) {
702 evtr_event_data(&ev, buf, sizeof(buf));
703 printf(" !\t%s\n", buf);
704 } else {
705 printf("\n");
706 }
956f9b36 707 last_ts = ev.ts;
e7c0dbba
AE
708 }
709 if (evtr_error(evtr)) {
710 err(1, evtr_errmsg(evtr));
711 }
712 evtr_query_destroy(q);
713 return 0;
714}
715
716int
717main(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}