c0148eb4a1e6567e302f86cfd21c00366e5c18e4
[dragonfly.git] / usr.bin / evtranalyze / evtranalyze.c
1 /*
2  * Copyright (c) 2009, 2010 Aggelos Economopoulos.  All rights reserved.
3  * 
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in
12  *    the documentation and/or other materials provided with the
13  *    distribution.
14  * 3. Neither the name of The DragonFly Project nor the names of its
15  *    contributors may be used to endorse or promote products derived
16  *    from this software without specific, prior written permission.
17  * 
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
22  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
24  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
28  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31
32 #include <assert.h>
33 #include <ctype.h>
34 #include <err.h>
35 #include <inttypes.h>
36 #include <libgen.h>
37 #include <math.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42
43 #include <evtr.h>
44 #include "xml.h"
45 #include "svg.h"
46 #include "plotter.h"
47 #include "trivial.h"
48
49 enum {
50         NR_TOP_THREADS = 5,
51         NR_BUCKETS = 1021,
52 };
53
54 struct rows {
55         double row_increment;
56         double row_off;
57 };
58
59 #define CMD_PROTO(name) \
60         static int cmd_ ## name(int, char **)
61
62 CMD_PROTO(show);
63 CMD_PROTO(svg);
64 CMD_PROTO(stats);
65 CMD_PROTO(summary);
66
67 struct command {
68         const char *name;
69         int (*func)(int argc, char **argv);
70 } commands[] = {
71         {
72                 .name = "show",
73                 .func = &cmd_show,
74         },
75         {
76                 .name = "svg",
77                 .func = &cmd_svg,
78         },
79         {
80                 .name = "stats",
81                 .func = &cmd_stats,
82         },
83         {
84                 .name = "summary",
85                 .func = &cmd_summary,
86         },
87         {
88                 .name = NULL,
89         },
90 };
91
92 evtr_t evtr;
93 static char *opt_infile;
94 unsigned evtranalyze_debug;
95
96 static
97 void
98 printd_set_flags(const char *str, unsigned int *flags)
99 {
100         /*
101          * This is suboptimal as we don't detect
102          * invalid flags.
103          */
104         for (; *str; ++str) {
105                 if ('A' == *str) {
106                         *flags = -1;
107                         return;
108                 }
109                 if (!islower(*str))
110                         err(2, "invalid debug flag %c\n", *str);
111                 *flags |= 1 << (*str - 'a');
112         }
113 }
114
115 struct hashentry {
116         uintptr_t key;
117         uintptr_t val;
118         struct hashentry *next;
119 };
120
121 struct hashtab {
122         struct hashentry *buckets[NR_BUCKETS];
123         uintptr_t (*hashfunc)(uintptr_t);
124         uintptr_t (*cmpfunc)(uintptr_t, uintptr_t);
125 };
126
127 static int
128 ehash_find(const struct hashtab *tab, uintptr_t key, uintptr_t *val)
129 {
130         struct hashentry *ent;
131
132         for(ent = tab->buckets[tab->hashfunc(key)];
133             ent && tab->cmpfunc(ent->key, key);
134             ent = ent->next);
135
136         if (!ent)
137                 return !0;
138         *val = ent->val;
139         return 0;
140 }
141
142 static struct hashentry *
143 ehash_insert(struct hashtab *tab, uintptr_t key, uintptr_t val)
144 {
145         struct hashentry *ent;
146         int hsh;
147
148         if (!(ent = malloc(sizeof(*ent)))) {
149                 fprintf(stderr, "out of memory\n");
150                 return NULL;
151         }
152         hsh = tab->hashfunc(key);
153         ent->next = tab->buckets[hsh];
154         ent->key = key;
155         ent->val = val;
156         tab->buckets[hsh] = ent;
157         return ent;
158 }
159 static
160 int
161 ehash_delete(struct hashtab *tab, uintptr_t key)
162 {
163         struct hashentry *ent, *prev;
164
165         prev = NULL;
166         for(ent = tab->buckets[tab->hashfunc(key)];
167             ent && tab->cmpfunc(ent->key, key);
168             prev = ent, ent = ent->next);
169         if (!ent)
170                 return !0;
171         if (prev)
172                 prev->next = ent->next;
173         else
174                 tab->buckets[tab->hashfunc(key)] = ent->next;
175         free(ent);
176         return 0;
177 }
178
179 static
180 uintptr_t
181 cmpfunc_pointer(uintptr_t a, uintptr_t b)
182 {
183         return b - a;
184 }
185
186 static
187 uintptr_t
188 hashfunc_pointer(uintptr_t p)
189 {
190         return p % NR_BUCKETS;
191 }
192
193 static
194 uintptr_t
195 hashfunc_string(uintptr_t p)
196 {
197         const char *str = (char *)p;
198         unsigned long hash = 5381;
199         int c;
200
201         while ((c = *str++))
202             hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
203         return hash  % NR_BUCKETS;
204 }
205
206 static struct hashtab *
207 ehash_new(void)
208 {
209         struct hashtab *tab;
210         if (!(tab = calloc(sizeof(struct hashtab), 1)))
211                 return tab;
212         tab->hashfunc = &hashfunc_pointer;
213         tab->cmpfunc = &cmpfunc_pointer;
214         return tab;
215 }
216
217 /* returns 0 if equal */
218 static
219 int
220 cmp_vals(evtr_variable_value_t a, evtr_variable_value_t b)
221 {
222         if (a->type != b->type)
223                 return !0;
224         switch (a->type) {
225         case EVTR_VAL_NIL:
226                 return 0;
227         case EVTR_VAL_INT:
228                 return !(a->num == b->num);
229         case EVTR_VAL_STR:
230                 return strcmp(a->str, b->str);
231         case EVTR_VAL_HASH:
232                 return !0;      /* come on! */
233         case EVTR_VAL_CTOR:
234                 err(3, "not implemented");
235         }
236         err(3, "can't get here");
237 }
238
239 static
240 uintptr_t
241 cmpfunc_ctor(uintptr_t _a, uintptr_t _b)
242 {
243         evtr_variable_value_t vala, valb;
244         vala = (evtr_variable_value_t)_a;
245         valb = (evtr_variable_value_t)_b;
246         if (strcmp(vala->ctor.name, valb->ctor.name))
247                 return !0;
248         vala = TAILQ_FIRST(&vala->ctor.args);
249         valb = TAILQ_FIRST(&valb->ctor.args);
250         for (;;) {
251                 if (!vala && !valb)
252                         return 0;
253                 if ((vala && !valb) || (valb && !vala))
254                         return !0;
255                 if (cmp_vals(vala, valb))
256                         return !0;
257                 vala = TAILQ_NEXT(vala, link);
258                 valb = TAILQ_NEXT(valb, link);
259         }
260 }
261
262 static
263 uintptr_t
264 hashfunc_ctor(uintptr_t _p)
265 {
266         evtr_variable_value_t val, ctor_val = (evtr_variable_value_t)_p;
267         char buf[1024], *p = &buf[0];
268         size_t len;
269
270         p = buf;
271         assert(ctor_val->type == EVTR_VAL_CTOR);
272         len = strlcpy(buf, ctor_val->ctor.name, sizeof(buf));
273         if (len >= sizeof(buf))
274                 goto done;
275
276         TAILQ_FOREACH(val, &ctor_val->ctor.args, link) {
277                 switch (val->type) {
278                 case EVTR_VAL_NIL:
279                         assert(!"can't happen");
280                         break;
281                 case EVTR_VAL_INT:
282                         len += snprintf(p + len, sizeof(buf) - len - 1,
283                                         "%jd", val->num);
284                         break;
285                 case EVTR_VAL_STR:
286                         len = strlcat(p, val->str, sizeof(buf));
287                         break;
288                 case EVTR_VAL_HASH:
289                         break;  /* come on! */
290                 case EVTR_VAL_CTOR:
291                         err(3, "not implemented");
292                 }
293                 if (len >= (sizeof(buf) - 1))
294                         break;
295         }
296 done:
297         buf[sizeof(buf) - 1] = '\0';
298         return hashfunc_string((uintptr_t)buf);
299 }
300
301 typedef struct vector {
302         uintmax_t *vals;
303         int used;
304         int allocated;
305 } *vector_t;
306
307 static vector_t
308 vector_new(void)
309 {
310         vector_t v;
311         if (!(v = malloc(sizeof(*v))))
312                 return v;
313         v->allocated = 2;
314         if (!(v->vals = malloc(v->allocated * sizeof(v->vals[0])))) {
315                 free(v);
316                 return NULL;
317         }
318         v->allocated = 
319         v->used = 0;
320         return v;
321 }
322
323 static
324 void
325 vector_push(vector_t v, uintmax_t val)
326 {
327         uintmax_t *tmp;
328         if (v->used == v->allocated) {
329                 tmp = realloc(v->vals, 2 * v->allocated * sizeof(v->vals[0]));
330                 if (!tmp)
331                         err(1, "out of memory");
332                 v->vals = tmp;
333                 v->allocated *= 2;
334         }
335         v->vals[v->used++] = val;
336 }
337
338 static
339 void
340 vector_destroy(vector_t v)
341 {
342         free(v->vals);
343         free(v);
344 }
345
346 static
347 int
348 vector_nelems(vector_t v)
349 {
350         return v->used;
351 }
352
353 #define vector_foreach(v, val, i)                                       \
354         for (i = 0, val = v->vals[0]; i < v->used; val = v->vals[++i])
355
356 static
357 double
358 stddev(vector_t v, double avg)
359 {
360         uintmax_t val;
361         int i;
362         double diff, sqr_sum = 0.0;
363
364         if (vector_nelems(v) < 2)
365                 return 1 / 0.0;
366         vector_foreach(v, val, i) {
367                 diff = val - avg;
368                 sqr_sum += diff * diff;
369         }
370         return sqrt(sqr_sum / (vector_nelems(v) - 1));
371 }
372
373 static
374 void
375 usage(void)
376 {
377         fprintf(stderr, "bad usage :P\n");
378         exit(2);
379 }
380
381 static
382 void
383 rows_init(struct rows *rows, int n, double height, double perc)
384 {
385         double row_h;
386         rows->row_increment = height / n;
387         /* actual row height */
388         row_h = perc * rows->row_increment;
389         rows->row_off = (rows->row_increment - row_h) / 2.0;
390         assert(!isnan(rows->row_increment));
391         assert(!isnan(rows->row_off));
392 }
393
394 static
395 void
396 rows_n(struct rows *rows, int n, double *y, double *height)
397 {
398         *y = n * rows->row_increment + rows->row_off;
399         *height = rows->row_increment - 2 * rows->row_off;
400 }
401
402 /*
403  * Which fontsize to use so that the string fits in the
404  * given rect.
405  */
406 static
407 double
408 fontsize_for_rect(double width, double height, int textlen)
409 {
410         double wpc, maxh;
411         /*
412          * We start with a font size equal to the height
413          * of the rectangle and round down so that we only
414          * use a limited number of sizes.
415          *
416          * For a rectangle width comparable to the height,
417          * the text might extend outside of the rectangle.
418          * In that case we need to limit it.
419          */
420         /* available width per character */
421         wpc = width / textlen;
422         /*
423          * Assuming a rough hight/width ratio for characters,
424          * calculate the available height and round it down
425          * just to be on the safe side.
426          */
427 #define GLYPH_HIGHT_TO_WIDTH 1.5
428         maxh = GLYPH_HIGHT_TO_WIDTH * wpc * 0.9;
429         if (height > maxh) {
430                 height = maxh;
431         } else if (height < 0.01) {
432                 height = 0.01;
433         } else {
434                 /* rounding (XXX: make cheaper)*/
435                 height = log(height);
436                 height = round(height);
437                 height = exp(height);
438         }
439         return height;
440 }
441
442 struct pass_hook {
443         void (*pre)(void *);
444         void (*event)(void *, evtr_event_t);
445         void (*post)(void *);
446         void *data;
447         struct evtr_filter *filts;
448         int nfilts;
449 };
450
451 struct thread_info {
452         uint64_t runtime;
453 };
454
455 struct ts_interval {
456         uint64_t start;
457         uint64_t end;
458 };
459
460 struct td_switch_ctx {
461         svg_document_t svg;
462         struct rows *cpu_rows;
463         struct rows *thread_rows;
464         /* which events the user cares about */
465         struct ts_interval interval;
466         /* first/last event timestamps on any cpu */
467         struct ts_interval firstlast;
468         double width;
469         double xscale;  /* scale factor applied to x */
470         svg_rect_t cpu_sw_rect;
471         svg_rect_t thread_rect;
472         svg_rect_t inactive_rect;
473         svg_text_t thread_label;
474         struct cpu_table {
475                 struct cpu *cpus;
476                 int ncpus;
477         } cputab;
478         struct evtr_thread **top_threads;
479         int nr_top_threads;
480         double thread_rows_yoff;
481 };
482
483 struct cpu {
484         struct evtr_thread *td;
485         int i;          /* cpu index */
486         uint64_t ts;    /* time cpu switched to td */
487         /* timestamp for first/last event on this cpu */
488         struct ts_interval firstlast;
489         double freq;
490         uintmax_t evcnt;
491 };
492
493 static
494 void
495 do_pass(struct pass_hook *hooks, int nhooks)
496 {
497         struct evtr_filter *filts = NULL;
498         int nfilts = 0, i;
499         struct evtr_query *q;
500         struct evtr_event ev;
501
502         for (i = 0; i < nhooks; ++i) {
503                 struct pass_hook *h = &hooks[i];
504                 if (h->pre)
505                         h->pre(h->data);
506                 if (h->nfilts > 0) {
507                         filts = realloc(filts, (nfilts + h->nfilts) *
508                                         sizeof(struct evtr_filter));
509                         if (!filts)
510                                 err(1, "Out of memory");
511                         memcpy(filts + nfilts, h->filts,
512                                h->nfilts * sizeof(struct evtr_filter));
513                         nfilts += h->nfilts;
514                 }
515         }
516         q = evtr_query_init(evtr, filts, nfilts);
517         if (!q)
518                 err(1, "Can't initialize query\n");
519         while(!evtr_query_next(q, &ev)) {
520                 for (i = 0; i < nhooks; ++i) {
521                         if (hooks[i].event)
522                                 hooks[i].event(hooks[i].data, &ev);
523                 }
524         }
525         if (evtr_query_error(q)) {
526                 err(1, evtr_query_errmsg(q));
527         }
528         evtr_query_destroy(q);
529
530         for (i = 0; i < nhooks; ++i) {
531                 if (hooks[i].post)
532                         hooks[i].post(hooks[i].data);
533         }
534         if (evtr_rewind(evtr))
535                 err(1, "Can't rewind event stream\n");
536 }
537
538 static
539 void
540 draw_thread_run(struct td_switch_ctx *ctx, struct cpu *c, evtr_event_t ev, int row)
541 {
542         double x, w, y, height;
543         w = (ev->ts - c->ts) * ctx->xscale;
544         x = (ev->ts - ctx->firstlast.start) * ctx->xscale;
545         rows_n(ctx->thread_rows, row, &y, &height);
546         svg_rect_draw(ctx->svg, ctx->thread_rect, x - w,
547                       y + ctx->thread_rows_yoff, w, height);
548 }
549
550 static
551 void
552 draw_ctx_switch(struct td_switch_ctx *ctx, struct cpu *c, evtr_event_t ev)
553 {
554         struct svg_transform textrot;
555         char comm[100];
556         double x, w, fs, y, height;
557         int textlen;
558
559         assert(ctx->xscale > 0.0);
560         if (!c->ts)
561                 return;
562         /* distance to previous context switch */
563         w = (ev->ts - c->ts) * ctx->xscale;
564         x = (ev->ts - ctx->firstlast.start) * ctx->xscale;
565         if ((x - w) < 0) {
566                 fprintf(stderr, "(%ju - %ju) * %.20lf\n",
567                         (uintmax_t)ev->ts,
568                         (uintmax_t)ctx->firstlast.start, ctx->xscale);
569                 abort();
570         }
571
572         rows_n(ctx->cpu_rows, c->i, &y, &height);
573         assert(!isnan(y));
574         assert(!isnan(height));
575
576         svg_rect_draw(ctx->svg, ctx->cpu_sw_rect, x - w, y, w, height);
577
578         /*
579          * Draw the text label describing the thread we
580          * switched out of.
581          */
582         textrot.tx = x - w;
583         textrot.ty = y;
584         textrot.sx = 1.0;
585         textrot.sy = 1.0;
586         textrot.rot = 90.0;
587         textlen = snprintf(comm, sizeof(comm) - 1, "%s (%p)",
588                            c->td ? c->td->comm : "unknown",
589                                  c->td ? c->td->id: NULL);
590         if (textlen > (int)sizeof(comm))
591                 textlen = sizeof(comm) - 1;
592         comm[sizeof(comm) - 1] = '\0';
593         /*
594          * Note the width and hight are exchanged because
595          * the bounding rectangle is rotated by 90 degrees.
596          */
597         fs = fontsize_for_rect(height, w, textlen);
598         svg_text_draw(ctx->svg, ctx->thread_label, &textrot, comm,
599                       fs);
600 }
601
602
603 /*
604  * The stats for ntd have changed, update ->top_threads
605  */
606 static
607 void
608 top_threads_update(struct td_switch_ctx *ctx, struct evtr_thread *ntd)
609 {
610         struct thread_info *tdi = ntd->userdata;
611         int i, j;
612         for (i = 0; i < ctx->nr_top_threads; ++i) {
613                 struct evtr_thread *td = ctx->top_threads[i];
614                 if (td == ntd) {
615                         /*
616                          * ntd is already in top_threads and it is at
617                          * the correct ranking
618                          */
619                         break;
620                 }
621                 if (!td) {
622                         /* empty slot -- just insert our thread */
623                         ctx->top_threads[i] = ntd;
624                         break;
625                 }
626                 if (((struct thread_info *)td->userdata)->runtime >=
627                     tdi->runtime) {
628                         /* this thread ranks higher than we do. Move on */
629                         continue;
630                 }
631                 /*
632                  * OK, we've found the first thread that we outrank, so we
633                  * need to replace it w/ our thread.
634                  */
635                 td = ntd;       /* td holds the thread we will insert next */
636                 for (j = i + 1; j < ctx->nr_top_threads; ++j, ++i) {
637                         struct evtr_thread *tmp;
638
639                         /* tmp holds the thread we replace */
640                         tmp = ctx->top_threads[j];
641                         ctx->top_threads[j] = td;
642                         if (tmp == ntd) {
643                                 /*
644                                  * Our thread was already in the top list,
645                                  * and we just removed the second instance.
646                                  * Nothing more to do.
647                                  */
648                                 break;
649                         }
650                         td = tmp;
651                 }
652                 break;
653         }
654 }
655
656 static
657 void
658 ctxsw_prepare_event(void *_ctx, evtr_event_t ev)
659 {
660         struct td_switch_ctx *ctx = _ctx;
661         struct cpu *c, *cpus = ctx->cputab.cpus;
662         struct thread_info *tdi;
663
664         (void)evtr;
665         printd(INTV, "test1 (%ju:%ju) : %ju\n",
666                (uintmax_t)ctx->interval.start,
667                (uintmax_t)ctx->interval.end,
668                (uintmax_t)ev->ts);
669         if ((ev->ts > ctx->interval.end) ||
670             (ev->ts < ctx->interval.start))
671                 return;
672         printd(INTV, "PREPEV on %d\n", ev->cpu);
673
674         /* update first/last timestamps */
675         c = &cpus[ev->cpu];
676         if (!c->firstlast.start) {
677                 c->firstlast.start = ev->ts;
678         }
679         c->firstlast.end = ev->ts;
680         /*
681          * c->td can be null when this is the first ctxsw event we
682          * observe for a cpu
683          */
684         if (c->td) {
685                 /* update thread stats */
686                 if (!c->td->userdata) {
687                         if (!(tdi = malloc(sizeof(struct thread_info))))
688                                 err(1, "Out of memory");
689                         c->td->userdata = tdi;
690                         tdi->runtime = 0;
691                 }
692                 tdi = c->td->userdata;
693                 tdi->runtime += ev->ts - c->ts;
694                 top_threads_update(ctx, c->td);
695         }
696
697         /* Notice that ev->td is the new thread for ctxsw events */
698         c->td = ev->td;
699         c->ts = ev->ts;
700 }
701
702 static
703 void
704 find_first_last_ts(struct cpu_table *cputab, struct ts_interval *fl)
705 {
706         struct cpu *cpus = &cputab->cpus[0];
707         int i;
708
709         fl->start = -1;
710         fl->end = 0;
711         for (i = 0; i < cputab->ncpus; ++i) {
712                 printd(INTV, "cpu%d: (%ju, %ju)\n", i,
713                        (uintmax_t)cpus[i].firstlast.start,
714                        (uintmax_t)cpus[i].firstlast.end);
715                 if (cpus[i].firstlast.start &&
716                     (cpus[i].firstlast.start < fl->start))
717                         fl->start = cpus[i].firstlast.start;
718                 if (cpus[i].firstlast.end &&
719                     (cpus[i].firstlast.end > fl->end))
720                         fl->end = cpus[i].firstlast.end;
721                 cpus[i].td = NULL;
722                 cpus[i].ts = 0;
723         }
724         printd(INTV, "global (%jd, %jd)\n", (uintmax_t)fl->start, (uintmax_t)fl->end);
725 }
726
727 static
728 void
729 ctxsw_prepare_post(void *_ctx)
730 {
731         struct td_switch_ctx *ctx = _ctx;
732
733         find_first_last_ts(&ctx->cputab, &ctx->firstlast);
734 }
735
736 static
737 void
738 ctxsw_draw_pre(void *_ctx)
739 {
740         struct td_switch_ctx *ctx = _ctx;
741         struct svg_transform textrot;
742         char comm[100];
743         double y, height, fs;
744         int i, textlen;
745         struct evtr_thread *td;
746
747         textrot.tx = 0.0 - 0.2; /* XXX */
748         textrot.sx = 1.0;
749         textrot.sy = 1.0;
750         textrot.rot = 270.0;
751
752         for (i = 0; i < ctx->nr_top_threads; ++i) {
753                 td = ctx->top_threads[i];
754                 if (!td)
755                         break;
756                 rows_n(ctx->thread_rows, i, &y, &height);
757                 svg_rect_draw(ctx->svg, ctx->inactive_rect, 0.0,
758                               y + ctx->thread_rows_yoff, ctx->width, height);
759                 textlen = snprintf(comm, sizeof(comm) - 1, "%s (%p)",
760                                    td->comm, td->id);
761                 if (textlen > (int)sizeof(comm))
762                         textlen = sizeof(comm) - 1;
763                 comm[sizeof(comm) - 1] = '\0';
764                 fs = fontsize_for_rect(height, 100.0, textlen);
765
766                 textrot.ty = y + ctx->thread_rows_yoff + height;
767                 svg_text_draw(ctx->svg, ctx->thread_label, &textrot,
768                               comm, fs);
769         }
770 }
771
772 static
773 void
774 ctxsw_draw_event(void *_ctx, evtr_event_t ev)
775 {
776         struct td_switch_ctx *ctx = _ctx;
777         struct cpu *c = &ctx->cputab.cpus[ev->cpu];
778         int i;
779
780         /*
781          * ctx->firstlast.end can be 0 if there were no events
782          * in the specified interval, in which case
783          * ctx->firstlast.start is invalid too.
784          */
785         assert(!ctx->firstlast.end || (ev->ts >= ctx->firstlast.start));
786         printd(INTV, "test2 (%ju:%ju) : %ju\n", (uintmax_t)ctx->interval.start,
787                (uintmax_t)ctx->interval.end, (uintmax_t)ev->ts);
788         if ((ev->ts > ctx->interval.end) ||
789             (ev->ts < ctx->interval.start))
790                 return;
791         printd(INTV, "DRAWEV %d\n", ev->cpu);
792         if (c->td != ev->td) {  /* thread switch (or preemption) */
793                 draw_ctx_switch(ctx, c, ev);
794                 /* XXX: this is silly */
795                 for (i = 0; i < ctx->nr_top_threads; ++i) {
796                         if (!ctx->top_threads[i])
797                                 break;
798                         if (ctx->top_threads[i] == c->td) {
799                                 draw_thread_run(ctx, c, ev, i);
800                                 break;
801                         }
802                 }
803                 c->td = ev->td;
804                 c->ts = ev->ts;
805         }
806 }
807
808 static
809 void
810 cputab_init(struct cpu_table *ct)
811 {
812         struct cpu *cpus;
813         double *freqs;
814         int i;
815
816         if ((ct->ncpus = evtr_ncpus(evtr)) <= 0)
817                 err(1, "No cpu information!\n");
818         printd(MISC, "evtranalyze: ncpus %d\n", ct->ncpus);
819         if (!(ct->cpus = malloc(sizeof(struct cpu) * ct->ncpus))) {
820                 err(1, "Can't allocate memory\n");
821         }
822         cpus = ct->cpus;
823         if (!(freqs = malloc(sizeof(double) * ct->ncpus))) {
824                 err(1, "Can't allocate memory\n");
825         }
826         if ((i = evtr_cpufreqs(evtr, freqs))) {
827                 warnc(i, "Can't get cpu frequencies\n");
828                 for (i = 0; i < ct->ncpus; ++i) {
829                         freqs[i] = -1.0;
830                 }
831         }
832
833         /* initialize cpu array */
834         for (i = 0; i < ct->ncpus; ++i) {
835                 cpus[i].td = NULL;
836                 cpus[i].ts = 0;
837                 cpus[i].i = i;
838                 cpus[i].firstlast.start = 0;
839                 cpus[i].firstlast.end = 0;
840                 cpus[i].evcnt = 0;
841                 cpus[i].freq = freqs[i];
842         }
843         free(freqs);
844 }
845
846 static
847 void
848 parse_interval(const char *_str, struct ts_interval *ts,
849                struct cpu_table *cputab)
850 {
851         double s, e, freq;
852         const char *str = _str + 1;
853
854         if ('c' == *_str) {     /* cycles */
855                 if (sscanf(str, "%" SCNu64 ":%" SCNu64,
856                            &ts->start,
857                            &ts->end) == 2)
858                         return;
859         } else if ('m' == *_str) {      /* miliseconds */
860                 if (sscanf(str, "%lf:%lf", &s, &e) == 2) {
861                         freq = cputab->cpus[0].freq;
862                         freq *= 1000.0; /* msecs */
863                         if (freq < 0.0) {
864                                 fprintf(stderr, "No frequency information"
865                                         " available\n");
866                                 err(2, "Can't convert time to cycles\n");
867                         }
868                         ts->start = s * freq;
869                         ts->end = e * freq;
870                         return;
871                 }
872         }
873         fprintf(stderr, "invalid interval format: %s\n", _str);
874         usage();
875 }
876
877
878 static
879 int
880 cmd_svg(int argc, char **argv)
881 {
882         svg_document_t svg;
883         int ch;
884         double height, width;
885         struct rows cpu_rows, thread_rows;
886         struct td_switch_ctx td_ctx;
887         const char *outpath = "output.svg";
888         struct evtr_filter ctxsw_filts[2] = {
889                 {
890                         .flags = 0,
891                         .cpu = -1,
892                         .ev_type = EVTR_TYPE_PROBE,
893                 },
894                 {
895                         .flags = 0,
896                         .cpu = -1,
897                         .ev_type = EVTR_TYPE_PROBE,
898                 },
899         };
900         struct pass_hook ctxsw_prepare = {
901                 .pre = NULL,
902                 .event = ctxsw_prepare_event,
903                 .post = ctxsw_prepare_post,
904                 .data = &td_ctx,
905                 .filts = ctxsw_filts,
906                 .nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
907         }, ctxsw_draw = {
908                 .pre = ctxsw_draw_pre,
909                 .event = ctxsw_draw_event,
910                 .post = NULL,
911                 .data = &td_ctx,
912                 .filts = ctxsw_filts,
913                 .nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
914         };
915
916         /*
917          * We are interested in thread switch and preemption
918          * events, but we don't use the data directly. Instead
919          * we rely on ev->td.
920          */
921         ctxsw_filts[0].fmt = "sw  %p > %p";
922         ctxsw_filts[1].fmt = "pre %p > %p";
923         td_ctx.interval.start = 0;
924         td_ctx.interval.end = -1;       /* i.e. no interval given */
925         td_ctx.nr_top_threads = NR_TOP_THREADS;
926         cputab_init(&td_ctx.cputab);    /* needed for parse_interval() */
927
928         optind = 0;
929         optreset = 1;
930         while ((ch = getopt(argc, argv, "i:o:")) != -1) {
931                 switch (ch) {
932                 case 'i':
933                         parse_interval(optarg, &td_ctx.interval,
934                                        &td_ctx.cputab);
935                         break;
936                 case 'o':
937                         outpath = optarg;
938                         break;
939                 default:
940                         usage();
941                 }
942
943         }
944         argc -= optind;
945         argv += optind;
946
947         height = 200.0;
948         width = 700.0;
949         td_ctx.width = width;
950
951         if (!(td_ctx.top_threads = calloc(td_ctx.nr_top_threads,
952                                           sizeof(struct evtr_thread *))))
953                 err(1, "Can't allocate memory\n");
954         if (!(svg = svg_document_create(outpath)))
955                 err(1, "Can't open svg document\n");
956
957         /*
958          * Create rectangles to use for output.
959          */
960         if (!(td_ctx.cpu_sw_rect = svg_rect_new("generic")))
961                 err(1, "Can't create rectangle\n");
962         if (!(td_ctx.thread_rect = svg_rect_new("thread")))
963                 err(1, "Can't create rectangle\n");
964         if (!(td_ctx.inactive_rect = svg_rect_new("inactive")))
965                 err(1, "Can't create rectangle\n");
966         /* text for thread names */
967         if (!(td_ctx.thread_label = svg_text_new("generic")))
968                 err(1, "Can't create text\n");
969         rows_init(&cpu_rows, td_ctx.cputab.ncpus, height, 0.9);
970         td_ctx.svg = svg;
971         td_ctx.xscale = -1.0;
972         td_ctx.cpu_rows = &cpu_rows;
973
974         do_pass(&ctxsw_prepare, 1);
975         td_ctx.thread_rows_yoff = height;
976         td_ctx.thread_rows = &thread_rows;
977         rows_init(td_ctx.thread_rows, td_ctx.nr_top_threads, 300, 0.9);
978         td_ctx.xscale = width / (td_ctx.firstlast.end - td_ctx.firstlast.start);
979         printd(SVG, "first %ju, last %ju, xscale %lf\n",
980                (uintmax_t)td_ctx.firstlast.start, (uintmax_t)td_ctx.firstlast.end,
981                td_ctx.xscale);
982
983         do_pass(&ctxsw_draw, 1);
984
985         svg_document_close(svg);
986         return 0;
987 }
988
989 static
990 int
991 cmd_show(int argc, char **argv)
992 {
993         struct evtr_event ev;
994         struct evtr_query *q;
995         struct evtr_filter filt;
996         struct cpu_table cputab;
997         double freq;
998         int ch;
999         uint64_t last_ts = 0;
1000
1001         cputab_init(&cputab);
1002         /*
1003          * Assume all cores run on the same frequency
1004          * for now. There's no reason to complicate
1005          * things unless we can detect frequency change
1006          * events as well.
1007          *
1008          * Note that the code is very simplistic and will
1009          * produce garbage if the kernel doesn't fixup
1010          * the timestamps for cores running with different
1011          * frequencies.
1012          */
1013         freq = cputab.cpus[0].freq;
1014         freq /= 1000000;        /* we want to print out usecs */
1015         printd(MISC, "using freq = %lf\n", freq);
1016         filt.flags = 0;
1017         filt.cpu = -1;
1018         filt.ev_type = EVTR_TYPE_PROBE;
1019         filt.fmt = NULL;
1020         optind = 0;
1021         optreset = 1;
1022         while ((ch = getopt(argc, argv, "f:")) != -1) {
1023                 switch (ch) {
1024                 case 'f':
1025                         filt.fmt = optarg;
1026                         break;
1027                 }
1028         }
1029         q = evtr_query_init(evtr, &filt, 1);
1030         if (!q)
1031                 err(1, "Can't initialize query\n");
1032         while(!evtr_query_next(q, &ev)) {
1033                 char buf[1024];
1034
1035                 if (!last_ts)
1036                         last_ts = ev.ts;
1037                 if (freq < 0.0) {
1038                         printf("%s\t%ju cycles\t[%.3d]\t%s:%d",
1039                                ev.td ? ev.td->comm : "unknown",
1040                                (uintmax_t)(ev.ts - last_ts), ev.cpu,
1041                                basename(ev.file), ev.line);
1042                 } else {
1043                         printf("%s\t%.3lf usecs\t[%.3d]\t%s:%d",
1044                                ev.td ? ev.td->comm : "unknown",
1045                                (ev.ts - last_ts) / freq, ev.cpu,
1046                                basename(ev.file), ev.line);
1047                 }
1048                 if (ev.fmt) {
1049                         evtr_event_data(&ev, buf, sizeof(buf));
1050                         printf(" !\t%s\n", buf);
1051                 } else {
1052                         printf("\n");
1053                 }
1054                 last_ts = ev.ts;
1055         }
1056         if (evtr_query_error(q)) {
1057                 err(1, evtr_query_errmsg(q));
1058         }
1059         evtr_query_destroy(q);
1060         return 0;
1061 }
1062
1063 struct stats_ops {
1064         const char *statscmd;
1065         void *(*prepare)(int, char **, struct evtr_filter *);
1066         void (*each_event)(void *, evtr_event_t);
1067         void (*report)(void *);
1068 };
1069
1070 struct stats_integer_ctx {
1071         const char *varname;
1072         struct {
1073                 int plot;
1074                 const char *path;
1075         } opts;
1076         void *plotter_ctx;
1077         struct plotter *plotter;
1078         plotid_t time_plot;
1079         uintmax_t sum;
1080         uintmax_t occurences;
1081 };
1082
1083 static
1084 void *
1085 stats_integer_prepare(int argc, char **argv, struct evtr_filter *filt)
1086 {
1087         struct stats_integer_ctx *ctx;
1088         int ch;
1089
1090         if (!(ctx = calloc(1, sizeof(*ctx))))
1091                 return ctx;
1092  
1093         optind = 0;
1094         optreset = 1;
1095         while ((ch = getopt(argc, argv, "p:")) != -1) {
1096                 switch (ch) {
1097                 case 'p':
1098                         ctx->opts.plot = !0;
1099                         ctx->opts.path = optarg;
1100                         break;
1101                 default:
1102                         usage();
1103                 }
1104         }
1105         argc -= optind;
1106         argv += optind;
1107
1108         if (argc != 1)
1109                 err(2, "Need exactly one variable");
1110         ctx->varname = argv[0];
1111         ctx->sum = ctx->occurences = 0;
1112         filt->flags = 0;
1113         filt->cpu = -1;
1114         filt->ev_type = EVTR_TYPE_STMT;
1115         filt->var = ctx->varname;
1116         if (!ctx->opts.plot)
1117                 return ctx;
1118
1119         if (!(ctx->plotter = plotter_factory()))
1120                 err(1, "can't allocate plotter");
1121         if (!(ctx->plotter_ctx = ctx->plotter->plot_init(ctx->opts.path)))
1122                 err(1, "can't allocate plotter context");
1123
1124         if ((ctx->time_plot = ctx->plotter->plot_new(ctx->plotter_ctx,
1125                                                           PLOT_TYPE_LINE,
1126                                                           ctx->varname)) < 0)
1127                 err(1, "can't create histogram");
1128         return ctx;
1129 }
1130
1131 static
1132 void
1133 stats_integer_each(void *_ctx, evtr_event_t ev)
1134 {
1135         struct stats_integer_ctx *ctx = _ctx;
1136         if (EVTR_VAL_INT != ev->stmt.val->type) {
1137                 fprintf(stderr, "event at %jd (cpu %d) does not treat %s as an"
1138                         "integer variable; ignored\n", ev->ts, ev->cpu,
1139                         ctx->varname);
1140                 return;
1141         }
1142         if (ctx->plotter)
1143                 ctx->plotter->plot_line(ctx->plotter_ctx, ctx->time_plot,
1144                                         (double)ev->ts, (double)ev->stmt.val->num);
1145         ctx->sum += ev->stmt.val->num;
1146         ++ctx->occurences;
1147 }
1148
1149 static
1150 void
1151 stats_integer_report(void *_ctx)
1152 {
1153         struct stats_integer_ctx *ctx = _ctx;
1154         printf("median for variable %s is %lf\n", ctx->varname,
1155                (double)ctx->sum / ctx->occurences);
1156         if (ctx->plotter)
1157                 ctx->plotter->plot_finish(ctx->plotter_ctx);
1158
1159         free(ctx);
1160 }
1161
1162 struct stats_completion_ctx {
1163         struct stats_completion_options {
1164                 int plot;
1165                 const char *path;
1166         } opts;
1167         struct plotter *plotter;
1168         void *plotter_ctx;
1169         plotid_t durations_plot;
1170         const char *varname;
1171         const char *ctor;
1172         const char *dtor;
1173         struct hashtab *ctors;
1174         uintmax_t historical_dtors;
1175         uintmax_t uncompleted_events;
1176         uintmax_t begun_events;
1177         uintmax_t completed_events;
1178         uintmax_t completed_duration_sum;
1179         vector_t durations;
1180 };
1181
1182 struct ctor_data {
1183         evtr_variable_value_t val;
1184         uintmax_t ts;
1185 };
1186
1187 static
1188 struct ctor_data *
1189 ctor_data_new(evtr_event_t ev)
1190 {
1191         struct ctor_data *cd;
1192
1193         if (!(cd = malloc(sizeof(*cd))))
1194                 return cd;
1195         cd->val = ev->stmt.val;
1196         cd->ts = ev->ts;
1197         return cd;
1198 }
1199
1200 static
1201 void *
1202 stats_completion_prepare(int argc, char **argv, struct evtr_filter *filt)
1203 {
1204         struct stats_completion_ctx *ctx;
1205         int ch;
1206
1207         if (!(ctx = calloc(1, sizeof(*ctx))))
1208                 return ctx;
1209
1210         optind = 0;
1211         optreset = 1;
1212         while ((ch = getopt(argc, argv, "p:")) != -1) {
1213                 switch (ch) {
1214                 case 'p':
1215                         ctx->opts.plot = !0;
1216                         ctx->opts.path = optarg;
1217                         break;
1218                 default:
1219                         usage();
1220                 }
1221         }
1222         argc -= optind;
1223         argv += optind;
1224         if (argc != 3)
1225                 err(2, "need a variable, a constructor and a destructor");
1226         if (!(ctx->ctors = ehash_new()))
1227                 goto free_ctx;
1228         ctx->ctors->hashfunc = &hashfunc_ctor;
1229         ctx->ctors->cmpfunc = &cmpfunc_ctor;
1230         if (!(ctx->durations = vector_new()))
1231                 goto free_ctors;
1232         ctx->varname = argv[0];
1233         ctx->ctor = argv[1];
1234         ctx->dtor = argv[2];
1235
1236         filt->flags = 0;
1237         filt->cpu = -1;
1238         filt->ev_type = EVTR_TYPE_STMT;
1239         filt->var = ctx->varname;
1240
1241         if (!ctx->opts.plot)
1242                 return ctx;
1243
1244         if (!(ctx->plotter = plotter_factory()))
1245                 err(1, "can't allocate plotter");
1246         if (!(ctx->plotter_ctx = ctx->plotter->plot_init(ctx->opts.path)))
1247                 err(1, "can't allocate plotter context");
1248
1249         if ((ctx->durations_plot = ctx->plotter->plot_new(ctx->plotter_ctx,
1250                                                           PLOT_TYPE_HIST,
1251                                                           ctx->varname)) < 0)
1252                 err(1, "can't create histogram");
1253         return ctx;
1254 free_ctors:
1255         ;       /* XXX */
1256 free_ctx:
1257         free(ctx);
1258         return NULL;
1259 }
1260
1261 static
1262 void
1263 stats_completion_each(void *_ctx, evtr_event_t ev)
1264 {
1265         struct stats_completion_ctx *ctx = _ctx;
1266         struct ctor_data *cd;
1267
1268         if (ev->stmt.val->type != EVTR_VAL_CTOR) {
1269                 fprintf(stderr, "event at %jd (cpu %d) does not assign to %s "
1270                         "with a data constructor; ignored\n", ev->ts, ev->cpu,
1271                         ctx->varname);
1272                 return;
1273         }
1274         if (!strcmp(ev->stmt.val->ctor.name, ctx->ctor)) {
1275                 uintptr_t v;
1276                 if (!ehash_find(ctx->ctors, (uintptr_t)ev->stmt.val, &v)) {
1277                         /* XXX:better diagnostic */
1278                         fprintf(stderr, "duplicate ctor\n");
1279                         err(3, "giving up");
1280                 }
1281                 if (!(cd = ctor_data_new(ev)))
1282                         err(1, "out of memory");
1283                 v = (uintptr_t)cd;
1284                 if (!ehash_insert(ctx->ctors, (uintptr_t)ev->stmt.val, v))
1285                         err(1, "out of memory");
1286                 ++ctx->begun_events;
1287         } else if (!strcmp(ev->stmt.val->ctor.name, ctx->dtor)) {
1288                 uintptr_t v;
1289                 const char *tmp = ev->stmt.val->ctor.name;
1290                 ev->stmt.val->ctor.name = ctx->ctor;
1291                 if (ehash_find(ctx->ctors, (uintptr_t)ev->stmt.val, &v)) {
1292                         ++ctx->historical_dtors;
1293                         ev->stmt.val->ctor.name = tmp;
1294                         return;
1295                 }
1296                 cd = (struct ctor_data *)v;
1297                 if (cd->ts >= ev->ts) {
1298                         /* XXX:better diagnostic */
1299                         fprintf(stderr, "destructor preceds constructor;"
1300                                 " ignored\n");
1301                         ev->stmt.val->ctor.name = tmp;
1302                         return;
1303                 }
1304                 if (ctx->plotter)
1305                         ctx->plotter->plot_histogram(ctx->plotter_ctx,
1306                                                      ctx->durations_plot,
1307                                                      (double)(ev->ts - cd->ts));
1308                 vector_push(ctx->durations, ev->ts - cd->ts);
1309                 ++ctx->completed_events;
1310                 ctx->completed_duration_sum += ev->ts - cd->ts;
1311                 if (ehash_delete(ctx->ctors, (uintptr_t)ev->stmt.val))
1312                         err(3, "ctor disappeared from hash!");
1313                 ev->stmt.val->ctor.name = tmp;
1314         } else {
1315                 fprintf(stderr, "event at %jd (cpu %d) assigns to %s "
1316                         "with an unexpected data constructor; ignored\n",
1317                         ev->ts, ev->cpu, ctx->varname);
1318                 return;
1319         }
1320 }
1321
1322 static
1323 void
1324 stats_completion_report(void *_ctx)
1325 {
1326         struct stats_completion_ctx *ctx = _ctx;
1327         double avg;
1328
1329         printf("Events completed without having started:\t%jd\n",
1330                ctx->historical_dtors);
1331         printf("Events started but didn't complete:\t%jd\n",
1332                ctx->begun_events - ctx->completed_events);
1333         avg = (double)ctx->completed_duration_sum / ctx->completed_events;
1334         printf("Average event duration:\t%lf (stddev %lf)\n", avg,
1335                stddev(ctx->durations, avg));
1336
1337         if (ctx->plotter)
1338                 ctx->plotter->plot_finish(ctx->plotter_ctx);
1339         vector_destroy(ctx->durations);
1340         /* XXX: hash */
1341         free(ctx);
1342 }
1343
1344 static struct stats_ops cmd_stat_ops[] = {
1345         {
1346                 .statscmd = "integer",
1347                 .prepare = &stats_integer_prepare,
1348                 .each_event = &stats_integer_each,
1349                 .report = &stats_integer_report,
1350         },
1351         {
1352                 .statscmd = "completion",
1353                 .prepare = &stats_completion_prepare,
1354                 .each_event = &stats_completion_each,
1355                 .report = &stats_completion_report,
1356         },
1357         {
1358                 .statscmd = NULL,
1359         },
1360 };
1361
1362 static
1363 int
1364 cmd_stats(int argc, char **argv)
1365 {
1366         struct evtr_event ev;
1367         struct evtr_query *q;
1368         struct evtr_filter filt;
1369         struct cpu_table cputab;
1370         double freq;
1371         uint64_t last_ts = 0;
1372         struct stats_ops *statsops = &cmd_stat_ops[0];
1373         void *statctx;
1374
1375         for (; statsops->statscmd; ++statsops) {
1376                 if (!strcmp(statsops->statscmd, argv[1]))
1377                         break;
1378         }
1379         if (!statsops->statscmd)
1380                 err(2, "No such stats type: %s", argv[1]);
1381
1382         --argc;
1383         ++argv;
1384         cputab_init(&cputab);
1385         /*
1386          * Assume all cores run on the same frequency
1387          * for now. There's no reason to complicate
1388          * things unless we can detect frequency change
1389          * events as well.
1390          *
1391          * Note that the code is very simplistic and will
1392          * produce garbage if the kernel doesn't fixup
1393          * the timestamps for cores running with different
1394          * frequencies.
1395          */
1396         freq = cputab.cpus[0].freq;
1397         freq /= 1000000;        /* we want to print out usecs */
1398         printd(MISC, "using freq = %lf\n", freq);
1399
1400         if (!(statctx = statsops->prepare(argc, argv, &filt)))
1401                 err(1, "Can't allocate stats context");
1402         q = evtr_query_init(evtr, &filt, 1);
1403         if (!q)
1404                 err(1, "Can't initialize query");
1405         while(!evtr_query_next(q, &ev)) {
1406
1407                 if (!last_ts)
1408                         last_ts = ev.ts;
1409
1410                 assert(ev.type == EVTR_TYPE_STMT);
1411                 statsops->each_event(statctx, &ev);
1412                 last_ts = ev.ts;
1413         }
1414         if (evtr_query_error(q)) {
1415                 err(1, evtr_query_errmsg(q));
1416         }
1417         evtr_query_destroy(q);
1418         statsops->report(statctx);
1419         return 0;
1420 }
1421
1422
1423 static
1424 int
1425 cmd_summary(int argc, char **argv)
1426 {
1427         struct evtr_filter filt;
1428         struct evtr_event ev;
1429         struct evtr_query *q;
1430         double freq;
1431         struct cpu_table cputab;
1432         struct ts_interval global;
1433         uintmax_t global_evcnt;
1434         int i;
1435
1436         (void)argc;
1437         (void)argv;
1438
1439         cputab_init(&cputab);
1440         filt.ev_type = EVTR_TYPE_PROBE;
1441         filt.fmt = NULL;
1442         filt.flags = 0;
1443         filt.cpu = -1;
1444
1445         q = evtr_query_init(evtr, &filt, 1);
1446         if (!q)
1447                 err(1, "Can't initialize query\n");
1448         while(!evtr_query_next(q, &ev)) {
1449                 struct cpu *c = &cputab.cpus[ev.cpu];
1450                 if (!c->firstlast.start)
1451                         c->firstlast.start = ev.ts;
1452                 ++c->evcnt;
1453                 c->firstlast.end = ev.ts;
1454         }
1455         if (evtr_query_error(q)) {
1456                 err(1, evtr_query_errmsg(q));
1457         }
1458         evtr_query_destroy(q);
1459
1460         find_first_last_ts(&cputab, &global);
1461
1462         freq = cputab.cpus[0].freq;
1463         global_evcnt = 0;
1464         for (i = 0; i < cputab.ncpus; ++i) {
1465                 struct cpu *c = &cputab.cpus[i];
1466                 printf("CPU %d: %jd events in %.3lf secs\n", i,
1467                        c->evcnt, (c->firstlast.end - c->firstlast.start)
1468                        / freq);
1469                 global_evcnt += c->evcnt;
1470         }
1471         printf("Total: %jd events on %d cpus in %.3lf secs\n", global_evcnt,
1472                cputab.ncpus, (global.end - global.start) / freq);
1473         return 0;
1474 }
1475
1476
1477 int
1478 main(int argc, char **argv)
1479 {
1480         int ch;
1481         FILE *inf;
1482         struct command *cmd;
1483         char *tmp;
1484
1485         while ((ch = getopt(argc, argv, "f:D:")) != -1) {
1486                 switch (ch) {
1487                 case 'f':
1488                         opt_infile = optarg;
1489                         break;
1490                 case 'D':
1491                         if ((tmp = strchr(optarg, ':'))) {
1492                                 *tmp++ = '\0';
1493                                 evtr_set_debug(tmp);
1494                         }
1495                         printd_set_flags(optarg, &evtranalyze_debug);
1496                         break;
1497                 default:
1498                         usage();
1499                 }
1500         }
1501         argc -= optind;
1502         argv += optind;
1503
1504         if (argc == 0) {
1505                 err(2, "need to specify a command\n");
1506         }
1507         if (!opt_infile) {
1508                 err(2, "you need to specify an input file\n");
1509         } else if (!strcmp(opt_infile, "-")) {
1510                 inf = stdin;
1511         } else {
1512                 inf = fopen(opt_infile, "r");
1513                 if (!inf) {
1514                         err(2, "Can't open input file\n");
1515                 }
1516         }
1517
1518         if (!(evtr = evtr_open_read(inf))) {
1519                 err(1, "Can't open evtr stream\n");
1520         }
1521
1522
1523         for (cmd = commands; cmd->name != NULL; ++cmd) {
1524                 if (strcmp(argv[0], cmd->name))
1525                         continue;
1526                 cmd->func(argc, argv);
1527                 break;
1528         }
1529         if (!cmd->name) {
1530                 err(2, "no such command: %s\n", argv[0]);
1531         }
1532                 
1533         evtr_close(evtr);
1534         return 0;
1535 }