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