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