2 * Copyright (c) 2009, 2010 Aggelos Economopoulos. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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
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.
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
58 #define CMD_PROTO(name) \
59 static int cmd_ ## name(int, char **)
68 int (*func)(int argc, char **argv);
92 static char *opt_infile;
93 unsigned evtranalyze_debug;
97 printd_set_flags(const char *str, unsigned int *flags)
100 * This is suboptimal as we don't detect
103 for (; *str; ++str) {
109 err(2, "invalid debug flag %c\n", *str);
110 *flags |= 1 << (*str - 'a');
117 struct hashentry *next;
121 struct hashentry *buckets[NR_BUCKETS];
122 uintptr_t (*hashfunc)(uintptr_t);
123 uintptr_t (*cmpfunc)(uintptr_t, uintptr_t);
127 ehash_find(const struct hashtab *tab, uintptr_t key, uintptr_t *val)
129 struct hashentry *ent;
131 for(ent = tab->buckets[tab->hashfunc(key)];
132 ent && tab->cmpfunc(ent->key, key);
142 ehash_insert(struct hashtab *tab, uintptr_t key, uintptr_t val)
144 struct hashentry *ent;
147 if (!(ent = malloc(sizeof(*ent)))) {
148 fprintf(stderr, "out of memory\n");
151 hsh = tab->hashfunc(key);
152 ent->next = tab->buckets[hsh];
155 tab->buckets[hsh] = ent;
160 ehash_delete(struct hashtab *tab, uintptr_t key)
162 struct hashentry *ent, *prev;
165 for(ent = tab->buckets[tab->hashfunc(key)];
166 ent && tab->cmpfunc(ent->key, key);
167 prev = ent, ent = ent->next);
171 prev->next = ent->next;
173 tab->buckets[tab->hashfunc(key)] = ent->next;
180 cmpfunc_pointer(uintptr_t a, uintptr_t b)
187 hashfunc_pointer(uintptr_t p)
189 return p % NR_BUCKETS;
194 hashfunc_string(uintptr_t p)
196 const char *str = (char *)p;
197 unsigned long hash = 5381;
201 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
202 return hash % NR_BUCKETS;
209 if (!(tab = calloc(sizeof(struct hashtab), 1)))
211 tab->hashfunc = &hashfunc_pointer;
212 tab->cmpfunc = &cmpfunc_pointer;
216 /* returns 0 if equal */
219 cmp_vals(evtr_variable_value_t a, evtr_variable_value_t b)
221 if (a->type != b->type)
227 return !(a->num == b->num);
229 return strcmp(a->str, b->str);
231 return !0; /* come on! */
233 err(3, "not implemented");
235 err(3, "can't get here");
240 cmpfunc_ctor(uintptr_t _a, uintptr_t _b)
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))
247 vala = TAILQ_FIRST(&vala->ctor.args);
248 valb = TAILQ_FIRST(&valb->ctor.args);
252 if ((vala && !valb) || (valb && !vala))
254 if (cmp_vals(vala, valb))
256 vala = TAILQ_NEXT(vala, link);
257 valb = TAILQ_NEXT(valb, link);
263 hashfunc_ctor(uintptr_t _p)
265 evtr_variable_value_t val, ctor_val = (evtr_variable_value_t)_p;
266 char buf[1024], *p = &buf[0];
270 assert(ctor_val->type == EVTR_VAL_CTOR);
271 len = strlcpy(buf, ctor_val->ctor.name, sizeof(buf));
272 if (len >= sizeof(buf))
275 TAILQ_FOREACH(val, &ctor_val->ctor.args, link) {
278 assert(!"can't happen");
281 len += snprintf(p + len, sizeof(buf) - len - 1,
285 len = strlcat(p, val->str, sizeof(buf));
288 break; /* come on! */
290 err(3, "not implemented");
292 if (len >= (sizeof(buf) - 1))
296 buf[sizeof(buf) - 1] = '\0';
297 return hashfunc_string((uintptr_t)buf);
300 typedef struct vector {
310 if (!(v = malloc(sizeof(*v))))
313 if (!(v->vals = malloc(v->allocated * sizeof(v->vals[0])))) {
324 vector_push(vector_t v, uintmax_t val)
327 if (v->used == v->allocated) {
328 tmp = realloc(v->vals, 2 * v->allocated * sizeof(v->vals[0]));
330 err(1, "out of memory");
334 v->vals[v->used++] = val;
339 vector_destroy(vector_t v)
347 vector_nelems(vector_t v)
352 #define vector_foreach(v, val, i) \
353 for (i = 0, val = v->vals[0]; i < v->used; val = v->vals[++i])
357 stddev(vector_t v, double avg)
361 double diff, sqr_sum = 0.0;
363 if (vector_nelems(v) < 2)
365 vector_foreach(v, val, i) {
367 sqr_sum += diff * diff;
369 return sqrt(sqr_sum / (vector_nelems(v) - 1));
376 fprintf(stderr, "bad usage :P\n");
382 rows_init(struct rows *rows, int n, double height, double perc)
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));
395 rows_n(struct rows *rows, int n, double *y, double *height)
397 *y = n * rows->row_increment + rows->row_off;
398 *height = rows->row_increment - 2 * rows->row_off;
402 * Which fontsize to use so that the string fits in the
407 fontsize_for_rect(double width, double height, int textlen)
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.
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.
419 /* available width per character */
420 wpc = width / textlen;
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.
426 #define GLYPH_HIGHT_TO_WIDTH 1.5
427 maxh = GLYPH_HIGHT_TO_WIDTH * wpc * 0.9;
430 } else if (height < 0.01) {
433 /* rounding (XXX: make cheaper)*/
434 height = log(height);
435 height = round(height);
436 height = exp(height);
443 void (*event)(void *, evtr_event_t);
444 void (*post)(void *);
446 struct evtr_filter *filts;
459 struct td_switch_ctx {
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;
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;
477 struct evtr_thread **top_threads;
479 double thread_rows_yoff;
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;
494 do_pass(struct pass_hook *hooks, int nhooks)
496 struct evtr_filter *filts = NULL;
498 struct evtr_query *q;
499 struct evtr_event ev;
501 for (i = 0; i < nhooks; ++i) {
502 struct pass_hook *h = &hooks[i];
506 filts = realloc(filts, (nfilts + h->nfilts) *
507 sizeof(struct evtr_filter));
509 err(1, "Out of memory");
510 memcpy(filts + nfilts, h->filts,
511 h->nfilts * sizeof(struct evtr_filter));
515 q = evtr_query_init(evtr, filts, nfilts);
517 err(1, "Can't initialize query\n");
518 while(!evtr_query_next(q, &ev)) {
519 for (i = 0; i < nhooks; ++i) {
521 hooks[i].event(hooks[i].data, &ev);
524 if (evtr_query_error(q)) {
525 err(1, evtr_query_errmsg(q));
527 evtr_query_destroy(q);
529 for (i = 0; i < nhooks; ++i) {
531 hooks[i].post(hooks[i].data);
533 if (evtr_rewind(evtr))
534 err(1, "Can't rewind event stream\n");
539 draw_thread_run(struct td_switch_ctx *ctx, struct cpu *c, evtr_event_t ev, int row)
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);
551 draw_ctx_switch(struct td_switch_ctx *ctx, struct cpu *c, evtr_event_t ev)
553 struct svg_transform textrot;
555 double x, w, fs, y, height;
558 assert(ctx->xscale > 0.0);
561 /* distance to previous context switch */
562 w = (ev->ts - c->ts) * ctx->xscale;
563 x = (ev->ts - ctx->firstlast.start) * ctx->xscale;
565 fprintf(stderr, "(%ju - %ju) * %.20lf\n",
567 (uintmax_t)ctx->firstlast.start, ctx->xscale);
571 rows_n(ctx->cpu_rows, c->i, &y, &height);
573 assert(!isnan(height));
575 svg_rect_draw(ctx->svg, ctx->cpu_sw_rect, x - w, y, w, height);
578 * Draw the text label describing the thread we
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';
593 * Note the width and hight are exchanged because
594 * the bounding rectangle is rotated by 90 degrees.
596 fs = fontsize_for_rect(height, w, textlen);
597 svg_text_draw(ctx->svg, ctx->thread_label, &textrot, comm,
603 * The stats for ntd have changed, update ->top_threads
607 top_threads_update(struct td_switch_ctx *ctx, struct evtr_thread *ntd)
609 struct thread_info *tdi = ntd->userdata;
611 for (i = 0; i < ctx->nr_top_threads; ++i) {
612 struct evtr_thread *td = ctx->top_threads[i];
615 * ntd is already in top_threads and it is at
616 * the correct ranking
621 /* empty slot -- just insert our thread */
622 ctx->top_threads[i] = ntd;
625 if (((struct thread_info *)td->userdata)->runtime >=
627 /* this thread ranks higher than we do. Move on */
631 * OK, we've found the first thread that we outrank, so we
632 * need to replace it w/ our thread.
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;
638 /* tmp holds the thread we replace */
639 tmp = ctx->top_threads[j];
640 ctx->top_threads[j] = td;
643 * Our thread was already in the top list,
644 * and we just removed the second instance.
645 * Nothing more to do.
657 ctxsw_prepare_event(void *_ctx, evtr_event_t ev)
659 struct td_switch_ctx *ctx = _ctx;
660 struct cpu *c, *cpus = ctx->cputab.cpus;
661 struct thread_info *tdi;
664 printd(INTV, "test1 (%ju:%ju) : %ju\n",
665 (uintmax_t)ctx->interval.start,
666 (uintmax_t)ctx->interval.end,
668 if ((ev->ts > ctx->interval.end) ||
669 (ev->ts < ctx->interval.start))
671 printd(INTV, "PREPEV on %d\n", ev->cpu);
673 /* update first/last timestamps */
675 if (!c->firstlast.start) {
676 c->firstlast.start = ev->ts;
678 c->firstlast.end = ev->ts;
680 * c->td can be null when this is the first ctxsw event we
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;
691 tdi = c->td->userdata;
692 tdi->runtime += ev->ts - c->ts;
693 top_threads_update(ctx, c->td);
696 /* Notice that ev->td is the new thread for ctxsw events */
703 find_first_last_ts(struct cpu_table *cputab, struct ts_interval *fl)
705 struct cpu *cpus = &cputab->cpus[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;
723 printd(INTV, "global (%jd, %jd)\n", (uintmax_t)fl->start, (uintmax_t)fl->end);
728 ctxsw_prepare_post(void *_ctx)
730 struct td_switch_ctx *ctx = _ctx;
732 find_first_last_ts(&ctx->cputab, &ctx->firstlast);
737 ctxsw_draw_pre(void *_ctx)
739 struct td_switch_ctx *ctx = _ctx;
740 struct svg_transform textrot;
742 double y, height, fs;
744 struct evtr_thread *td;
746 textrot.tx = 0.0 - 0.2; /* XXX */
751 for (i = 0; i < ctx->nr_top_threads; ++i) {
752 td = ctx->top_threads[i];
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)",
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);
765 textrot.ty = y + ctx->thread_rows_yoff + height;
766 svg_text_draw(ctx->svg, ctx->thread_label, &textrot,
773 ctxsw_draw_event(void *_ctx, evtr_event_t ev)
775 struct td_switch_ctx *ctx = _ctx;
776 struct cpu *c = &ctx->cputab.cpus[ev->cpu];
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.
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))
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])
797 if (ctx->top_threads[i] == c->td) {
798 draw_thread_run(ctx, c, ev, i);
809 cputab_init(struct cpu_table *ct)
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");
822 if (!(freqs = malloc(sizeof(double) * ct->ncpus))) {
823 err(1, "Can't allocate memory\n");
825 if ((i = evtr_cpufreqs(evtr, freqs))) {
826 warnc(i, "Can't get cpu frequencies\n");
827 for (i = 0; i < ct->ncpus; ++i) {
832 /* initialize cpu array */
833 for (i = 0; i < ct->ncpus; ++i) {
837 cpus[i].firstlast.start = 0;
838 cpus[i].firstlast.end = 0;
840 cpus[i].freq = freqs[i];
847 parse_interval(const char *_str, struct ts_interval *ts,
848 struct cpu_table *cputab)
851 const char *str = _str + 1;
853 if ('c' == *_str) { /* cycles */
854 if (sscanf(str, "%" SCNu64 ":%" SCNu64,
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 */
863 fprintf(stderr, "No frequency information"
865 err(2, "Can't convert time to cycles\n");
867 ts->start = s * freq;
872 fprintf(stderr, "invalid interval format: %s\n", _str);
879 cmd_svg(int argc, char **argv)
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] = {
891 .ev_type = EVTR_TYPE_PROBE,
896 .ev_type = EVTR_TYPE_PROBE,
899 struct pass_hook ctxsw_prepare = {
901 .event = ctxsw_prepare_event,
902 .post = ctxsw_prepare_post,
904 .filts = ctxsw_filts,
905 .nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
907 .pre = ctxsw_draw_pre,
908 .event = ctxsw_draw_event,
911 .filts = ctxsw_filts,
912 .nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
916 * We are interested in thread switch and preemption
917 * events, but we don't use the data directly. Instead
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() */
929 while ((ch = getopt(argc, argv, "i:o:")) != -1) {
932 parse_interval(optarg, &td_ctx.interval,
948 td_ctx.width = width;
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");
957 * Create rectangles to use for output.
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);
970 td_ctx.xscale = -1.0;
971 td_ctx.cpu_rows = &cpu_rows;
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,
982 do_pass(&ctxsw_draw, 1);
984 svg_document_close(svg);
990 cmd_show(int argc, char **argv)
992 struct evtr_event ev;
993 struct evtr_query *q;
994 struct evtr_filter filt;
995 struct cpu_table cputab;
998 uint64_t last_ts = 0;
1000 cputab_init(&cputab);
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
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
1012 freq = cputab.cpus[0].freq;
1013 freq /= 1000000; /* we want to print out usecs */
1014 printd(MISC, "using freq = %lf\n", freq);
1017 filt.ev_type = EVTR_TYPE_PROBE;
1021 while ((ch = getopt(argc, argv, "f:")) != -1) {
1028 q = evtr_query_init(evtr, &filt, 1);
1030 err(1, "Can't initialize query\n");
1031 while(!evtr_query_next(q, &ev)) {
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);
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);
1048 evtr_event_data(&ev, buf, sizeof(buf));
1049 printf(" !\t%s\n", buf);
1055 if (evtr_query_error(q)) {
1056 err(1, evtr_query_errmsg(q));
1058 evtr_query_destroy(q);
1063 const char *statscmd;
1064 void *(*prepare)(int, char **);
1065 void (*each_event)(void *, evtr_event_t);
1066 void (*report)(void *);
1069 struct stats_integer_ctx {
1070 const char *varname;
1072 uintmax_t occurences;
1077 stats_integer_prepare(int argc, char **argv)
1079 struct stats_integer_ctx *ctx;
1082 err(2, "Need exactly one variable");
1083 if (!(ctx = malloc(sizeof(*ctx))))
1085 ctx->varname = argv[1];
1086 ctx->sum = ctx->occurences = 0;
1092 stats_integer_each(void *_ctx, evtr_event_t ev)
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,
1101 ctx->sum += ev->stmt.val->num;
1107 stats_integer_report(void *_ctx)
1109 struct stats_integer_ctx *ctx = _ctx;
1110 printf("median for variable %s is %lf\n", ctx->varname,
1111 (double)ctx->sum / ctx->occurences);
1115 struct stats_completion_ctx {
1116 const char *varname;
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;
1129 evtr_variable_value_t val;
1135 ctor_data_new(evtr_event_t ev)
1137 struct ctor_data *cd;
1139 if (!(cd = malloc(sizeof(*cd))))
1141 cd->val = ev->stmt.val;
1148 stats_completion_prepare(int argc, char **argv)
1150 struct stats_completion_ctx *ctx;
1153 err(2, "need a variable, a constructor and a destructor");
1154 if (!(ctx = calloc(1, sizeof(*ctx))))
1156 if (!(ctx->ctors = ehash_new()))
1158 ctx->ctors->hashfunc = &hashfunc_ctor;
1159 ctx->ctors->cmpfunc = &cmpfunc_ctor;
1160 if (!(ctx->durations = vector_new()))
1162 ctx->varname = argv[1];
1163 ctx->ctor = argv[2];
1164 ctx->dtor = argv[3];
1175 stats_completion_each(void *_ctx, evtr_event_t ev)
1177 struct stats_completion_ctx *ctx = _ctx;
1178 struct ctor_data *cd;
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,
1186 if (!strcmp(ev->stmt.val->ctor.name, ctx->ctor)) {
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");
1193 if (!(cd = ctor_data_new(ev)))
1194 err(1, "out of memory");
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)) {
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;
1208 cd = (struct ctor_data *)v;
1209 if (cd->ts >= ev->ts) {
1210 /* XXX:better diagnostic */
1211 fprintf(stderr, "destructor preceds constructor;"
1213 ev->stmt.val->ctor.name = tmp;
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;
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);
1232 stats_completion_report(void *_ctx)
1234 struct stats_completion_ctx *ctx = _ctx;
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));
1245 vector_destroy(ctx->durations);
1250 static struct stats_ops cmd_stat_ops[] = {
1252 .statscmd = "integer",
1253 .prepare = &stats_integer_prepare,
1254 .each_event = &stats_integer_each,
1255 .report = &stats_integer_report,
1258 .statscmd = "completion",
1259 .prepare = &stats_completion_prepare,
1260 .each_event = &stats_completion_each,
1261 .report = &stats_completion_report,
1270 cmd_stats(int argc, char **argv)
1272 struct evtr_event ev;
1273 struct evtr_query *q;
1274 struct evtr_filter filt;
1275 struct cpu_table cputab;
1277 uint64_t last_ts = 0;
1278 struct stats_ops *statsops = &cmd_stat_ops[0];
1281 for (; statsops->statscmd; ++statsops) {
1282 if (!strcmp(statsops->statscmd, argv[1]))
1285 if (!statsops->statscmd)
1286 err(2, "No such stats type: %s", argv[1]);
1290 cputab_init(&cputab);
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
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
1302 freq = cputab.cpus[0].freq;
1303 freq /= 1000000; /* we want to print out usecs */
1304 printd(MISC, "using freq = %lf\n", freq);
1307 filt.ev_type = EVTR_TYPE_STMT;
1313 err(2, "Need a variable");
1315 q = evtr_query_init(evtr, &filt, 1);
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)) {
1325 assert(ev.type == EVTR_TYPE_STMT);
1326 statsops->each_event(statctx, &ev);
1329 if (evtr_query_error(q)) {
1330 err(1, evtr_query_errmsg(q));
1332 evtr_query_destroy(q);
1333 statsops->report(statctx);
1340 cmd_summary(int argc, char **argv)
1342 struct evtr_filter filt;
1343 struct evtr_event ev;
1344 struct evtr_query *q;
1346 struct cpu_table cputab;
1347 struct ts_interval global;
1348 uintmax_t global_evcnt;
1354 cputab_init(&cputab);
1355 filt.ev_type = EVTR_TYPE_PROBE;
1360 q = evtr_query_init(evtr, &filt, 1);
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;
1368 c->firstlast.end = ev.ts;
1370 if (evtr_query_error(q)) {
1371 err(1, evtr_query_errmsg(q));
1373 evtr_query_destroy(q);
1375 find_first_last_ts(&cputab, &global);
1377 freq = cputab.cpus[0].freq;
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)
1384 global_evcnt += c->evcnt;
1386 printf("Total: %jd events on %d cpus in %.3lf secs\n", global_evcnt,
1387 cputab.ncpus, (global.end - global.start) / freq);
1394 main(int argc, char **argv)
1398 struct command *cmd;
1401 while ((ch = getopt(argc, argv, "f:D:")) != -1) {
1404 opt_infile = optarg;
1407 if ((tmp = strchr(optarg, ':'))) {
1409 evtr_set_debug(tmp);
1411 printd_set_flags(optarg, &evtranalyze_debug);
1421 err(2, "need to specify a command\n");
1424 err(2, "you need to specify an input file\n");
1425 } else if (!strcmp(opt_infile, "-")) {
1428 inf = fopen(opt_infile, "r");
1430 err(2, "Can't open input file\n");
1434 if (!(evtr = evtr_open_read(inf))) {
1435 err(1, "Can't open evtr stream\n");
1439 for (cmd = commands; cmd->name != NULL; ++cmd) {
1440 if (strcmp(argv[0], cmd->name))
1442 cmd->func(argc, argv);
1446 err(2, "no such command: %s\n", argv[0]);