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