790b9e93db335dccd787cf99ac25b7f97aa682af
[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         printd("PREPEV on %d\n", ev->cpu);
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                 top_threads_update(ctx, c->td);
397         }
398
399         /* Notice that ev->td is the new thread for ctxsw events */
400         c->td = ev->td;
401         c->ts = ev->ts;
402 }
403
404 static
405 void
406 ctxsw_prepare_post(void *_ctx)
407 {
408         struct td_switch_ctx *ctx = _ctx;
409         struct cpu *cpus = ctx->cpus;
410         int i;
411
412         (void)evtr;
413         ctx->first_ts = -1;
414         ctx->last_ts = 0;
415         printd("first_ts[0] = %llu\n",cpus[0].first_ts);
416         for (i = 0; i < ctx->ncpus; ++i) {
417                 printd("first_ts[%d] = %llu\n", i, cpus[i].first_ts);
418                 if (cpus[i].first_ts && (cpus[i].first_ts < ctx->first_ts))
419                         ctx->first_ts = cpus[i].first_ts;
420                 if (cpus[i].last_ts && (cpus[i].last_ts > ctx->last_ts))
421                         ctx->last_ts = cpus[i].last_ts;
422                 cpus[i].td = NULL;
423                 cpus[i].ts = 0;
424         }
425 }
426
427 static
428 void
429 ctxsw_draw_pre(void *_ctx)
430 {
431         struct td_switch_ctx *ctx = _ctx;
432         struct svg_transform textrot;
433         char comm[100];
434         double y, height, fs;
435         int i, textlen;
436         struct evtr_thread *td;
437
438         textrot.tx = 0.0 - 0.2; /* XXX */
439         textrot.sx = 1.0;
440         textrot.sy = 1.0;
441         textrot.rot = 270.0;
442
443         for (i = 0; i < ctx->nr_top_threads; ++i) {
444                 td = ctx->top_threads[i];
445                 rows_n(ctx->thread_rows, i, &y, &height);
446                 svg_rect_draw(ctx->svg, ctx->inactive_rect, 0.0,
447                               y + ctx->thread_rows_yoff, ctx->width, height);
448                 textlen = snprintf(comm, sizeof(comm) - 1, "%s (%p)",
449                                    td->comm, td->id);
450                 if (textlen > (int)sizeof(comm))
451                         textlen = sizeof(comm) - 1;
452                 comm[sizeof(comm) - 1] = '\0';
453                 fs = fontsize_for_rect(height, 100.0, textlen);
454
455                 textrot.ty = y + ctx->thread_rows_yoff + height;
456                 svg_text_draw(ctx->svg, ctx->thread_label, &textrot,
457                               comm, fs);
458         }
459 }
460
461 static
462 void
463 ctxsw_draw_event(void *_ctx, evtr_event_t ev)
464 {
465         struct td_switch_ctx *ctx = _ctx;
466         struct cpu *c = &ctx->cpus[ev->cpu];
467         int i;
468
469         /*
470          * ctx->last_ts can be 0 if there were no events
471          * in the specified interval, in which case
472          * ctx->first_ts is invalid too.
473          */
474         assert(!ctx->last_ts || (ev->ts >= ctx->first_ts));
475         printd("test2 (%llu:%llu) : %llu\n", ctx->interval_start,
476                ctx->interval_end, ev->ts);
477         if ((ev->ts > ctx->interval_end) ||
478             (ev->ts < ctx->interval_start))
479                 return;
480         printd("DRAWEV %d\n", ev->cpu);
481         if (c->td != ev->td) {  /* thread switch (or preemption) */
482                 draw_ctx_switch(ctx, c, ev);
483                 /* XXX: this is silly */
484                 for (i = 0; i < ctx->nr_top_threads; ++i) {
485                         if (ctx->top_threads[i] == c->td) {
486                                 draw_thread_run(ctx, c, ev, i);
487                                 break;
488                         }
489                 }
490                 c->td = ev->td;
491                 c->ts = ev->ts;
492         }
493 }
494
495 static
496 int
497 cmd_svg(int argc, char **argv)
498 {
499         svg_document_t svg;
500         int ncpus, i, ch;
501         double height, width;
502         struct rows cpu_rows, thread_rows;
503         struct cpu *cpus;
504         struct td_switch_ctx td_ctx;
505         struct evtr_filter ctxsw_filts[2] = {
506                 {
507                         .flags = 0,
508                         .cpu = -1,
509                 },
510                 {
511                         .flags = 0,
512                         .cpu = -1,
513                 },
514         };
515         struct pass_hook ctxsw_prepare = {
516                 .pre = NULL,
517                 .event = ctxsw_prepare_event,
518                 .post = ctxsw_prepare_post,
519                 .data = &td_ctx,
520                 .filts = ctxsw_filts,
521                 .nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
522         }, ctxsw_draw = {
523                 .pre = ctxsw_draw_pre,
524                 .event = ctxsw_draw_event,
525                 .post = NULL,
526                 .data = &td_ctx,
527                 .filts = ctxsw_filts,
528                 .nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
529         };
530
531         /*
532          * We are interested in thread switch and preemption
533          * events, but we don't use the data directly. Instead
534          * we rely on ev->td.
535          */
536         ctxsw_filts[0].fmt = "sw  %p > %p";
537         ctxsw_filts[1].fmt = "pre %p > %p";
538         td_ctx.interval_start = 0;
539         td_ctx.interval_end = -1;       /* i.e. no interval given */
540         td_ctx.nr_top_threads = NR_TOP_THREADS;
541
542         printd("argc: %d, argv[0] = %s\n", argc, argv[0] ? argv[0] : "NULL");
543         optind = 0;
544         optreset = 1;
545         while ((ch = getopt(argc, argv, "i:")) != -1) {
546                 switch (ch) {
547                 case 'i':
548                         if (sscanf(optarg, "%llu:%llu", &td_ctx.interval_start,
549                                    &td_ctx.interval_end) != 2) {
550                                 usage();
551                         }
552                         break;
553                 default:
554                         usage();
555                 }
556
557         }
558         argc -= optind;
559         argv += optind;
560
561         height = 200.0;
562         width = 700.0;
563         td_ctx.width = width;
564         if ((ncpus = evtr_ncpus(evtr)) <= 0)
565                 err(1, "No cpu information!\n");
566         printd("evtranalyze: ncpus %d\n", ncpus);
567
568         if (!(cpus = malloc(ncpus * sizeof(struct cpu))))
569                 err(1, "Can't allocate memory\n");
570         /* initialize cpu array */
571         for (i = 0; i < ncpus; ++i) {
572                 cpus[i].td = NULL;
573                 cpus[i].ts = 0;
574                 cpus[i].i = i;
575                 cpus[i].first_ts = 0;
576                 cpus[i].last_ts = 0;
577         }
578         td_ctx.cpus = cpus;
579         td_ctx.ncpus = ncpus;
580         if (!(td_ctx.top_threads = calloc(td_ctx.nr_top_threads, sizeof(struct evtr_thread *))))
581                 err(1, "Can't allocate memory\n");
582         if (!(svg = svg_document_create("output.svg")))
583                 err(1, "Can't open svg document\n");
584
585         /*
586          * Create rectangles to use for output.
587          */
588         if (!(td_ctx.cpu_sw_rect = svg_rect_new("generic")))
589                 err(1, "Can't create rectangle\n");
590         if (!(td_ctx.thread_rect = svg_rect_new("thread")))
591                 err(1, "Can't create rectangle\n");
592         if (!(td_ctx.inactive_rect = svg_rect_new("inactive")))
593                 err(1, "Can't create rectangle\n");
594         /* text for thread names */
595         if (!(td_ctx.thread_label = svg_text_new("generic")))
596                 err(1, "Can't create text\n");
597         rows_init(&cpu_rows, ncpus, height, 0.9);
598         td_ctx.svg = svg;
599         td_ctx.xscale = -1.0;
600         td_ctx.cpu_rows = &cpu_rows;
601
602         do_pass(&ctxsw_prepare, 1);
603         td_ctx.thread_rows_yoff = height;
604         td_ctx.thread_rows = &thread_rows;
605         rows_init(td_ctx.thread_rows, td_ctx.nr_top_threads, 300, 0.9);
606         td_ctx.xscale = width / (td_ctx.last_ts - td_ctx.first_ts);
607         printd("first %llu, last %llu, xscale %lf\n", td_ctx.first_ts,
608                td_ctx.last_ts, td_ctx.xscale);
609
610         do_pass(&ctxsw_draw, 1);
611
612         svg_document_close(svg);
613         return 0;
614 }
615
616 static
617 int
618 cmd_show(int argc, char **argv)
619 {
620         struct evtr_event ev;
621         struct evtr_query *q;
622         struct evtr_filter filt;
623         int ch;
624
625         filt.fmt = NULL;
626         optind = 0;
627         optreset = 1;
628         while ((ch = getopt(argc, argv, "f:")) != -1) {
629                 switch (ch) {
630                 case 'f':
631                         filt.fmt = optarg;
632                         break;
633                 }
634         }
635         filt.flags = 0;
636         filt.cpu = -1;
637         printd("fmt = %s\n", filt.fmt ? filt.fmt : "NULL");
638         q = evtr_query_init(evtr, &filt, 1);
639         if (!q)
640                 err(1, "Can't initialize query\n");
641         while(!evtr_query_next(q, &ev)) {
642                 char buf[1024];
643                 printf("%s\t%llu cycles\t[%.3d]\t%s:%d",
644                        ev.td ? ev.td->comm : "unknown",
645                        ev.ts, ev.cpu,
646                        basename(ev.file), ev.line);
647                 if (ev.fmt) {
648                         evtr_event_data(&ev, buf, sizeof(buf));
649                         printf(" !\t%s\n", buf);
650                 } else {
651                         printf("\n");
652                 }
653         }
654         if (evtr_error(evtr)) {
655                 err(1, evtr_errmsg(evtr));
656         }
657         evtr_query_destroy(q);
658         return 0;
659 }
660
661 int
662 main(int argc, char **argv)
663 {
664         int ch;
665         FILE *inf;
666         struct command *cmd;
667
668         while ((ch = getopt(argc, argv, "f:D:")) != -1) {
669                 switch (ch) {
670                 case 'f':
671                         opt_infile = optarg;
672                         break;
673                 case 'D':
674                         evtranalyze_debug = atoi(optarg);
675                         evtr_set_debug(evtranalyze_debug);
676                         break;
677                 default:
678                         usage();
679                 }
680         }
681         argc -= optind;
682         argv += optind;
683
684         if (argc == 0) {
685                 err(2, "need to specify a command\n");
686         }
687         if (!opt_infile || !strcmp(opt_infile, "-")) {
688                 inf = stdin;
689         } else {
690                 inf = fopen(opt_infile, "r");
691                 if (!inf) {
692                         err(2, "Can't open input file\n");
693                 }
694         }
695
696         if (!(evtr = evtr_open_read(inf))) {
697                 err(1, "Can't open evtr stream\n");
698         }
699
700
701         for (cmd = commands; cmd->name != NULL; ++cmd) {
702                 if (strcmp(argv[0], cmd->name))
703                         continue;
704                 cmd->func(argc, argv);
705                 break;
706         }
707         if (!cmd->name) {
708                 err(2, "no such command: %s\n", argv[0]);
709         }
710                 
711         evtr_close(evtr);
712         return 0;
713 }