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
59 #define CMD_PROTO(name) \
60 static int cmd_ ## name(int, char **)
69 int (*func
)(int argc
, char **argv
);
93 static char *opt_infile
;
94 unsigned evtranalyze_debug
;
98 printd_set_flags(const char *str
, unsigned int *flags
)
101 * This is suboptimal as we don't detect
104 for (; *str
; ++str
) {
110 err(2, "invalid debug flag %c\n", *str
);
111 *flags
|= 1 << (*str
- 'a');
118 struct hashentry
*next
;
122 struct hashentry
*buckets
[NR_BUCKETS
];
123 uintptr_t (*hashfunc
)(uintptr_t);
124 uintptr_t (*cmpfunc
)(uintptr_t, uintptr_t);
128 ehash_find(const struct hashtab
*tab
, uintptr_t key
, uintptr_t *val
)
130 struct hashentry
*ent
;
132 for(ent
= tab
->buckets
[tab
->hashfunc(key
)];
133 ent
&& tab
->cmpfunc(ent
->key
, key
);
142 static struct hashentry
*
143 ehash_insert(struct hashtab
*tab
, uintptr_t key
, uintptr_t val
)
145 struct hashentry
*ent
;
148 if (!(ent
= malloc(sizeof(*ent
)))) {
149 fprintf(stderr
, "out of memory\n");
152 hsh
= tab
->hashfunc(key
);
153 ent
->next
= tab
->buckets
[hsh
];
156 tab
->buckets
[hsh
] = ent
;
161 ehash_delete(struct hashtab
*tab
, uintptr_t key
)
163 struct hashentry
*ent
, *prev
;
166 for(ent
= tab
->buckets
[tab
->hashfunc(key
)];
167 ent
&& tab
->cmpfunc(ent
->key
, key
);
168 prev
= ent
, ent
= ent
->next
);
172 prev
->next
= ent
->next
;
174 tab
->buckets
[tab
->hashfunc(key
)] = ent
->next
;
181 cmpfunc_pointer(uintptr_t a
, uintptr_t b
)
188 hashfunc_pointer(uintptr_t p
)
190 return p
% NR_BUCKETS
;
195 hashfunc_string(uintptr_t p
)
197 const char *str
= (char *)p
;
198 unsigned long hash
= 5381;
202 hash
= ((hash
<< 5) + hash
) + c
; /* hash * 33 + c */
203 return hash
% NR_BUCKETS
;
206 static struct hashtab
*
210 if (!(tab
= calloc(sizeof(struct hashtab
), 1)))
212 tab
->hashfunc
= &hashfunc_pointer
;
213 tab
->cmpfunc
= &cmpfunc_pointer
;
217 /* returns 0 if equal */
220 cmp_vals(evtr_variable_value_t a
, evtr_variable_value_t b
)
222 if (a
->type
!= b
->type
)
228 return !(a
->num
== b
->num
);
230 return strcmp(a
->str
, b
->str
);
232 return !0; /* come on! */
234 err(3, "not implemented");
236 err(3, "can't get here");
241 cmpfunc_ctor(uintptr_t _a
, uintptr_t _b
)
243 evtr_variable_value_t vala
, valb
;
244 vala
= (evtr_variable_value_t
)_a
;
245 valb
= (evtr_variable_value_t
)_b
;
246 if (strcmp(vala
->ctor
.name
, valb
->ctor
.name
))
248 vala
= TAILQ_FIRST(&vala
->ctor
.args
);
249 valb
= TAILQ_FIRST(&valb
->ctor
.args
);
253 if ((vala
&& !valb
) || (valb
&& !vala
))
255 if (cmp_vals(vala
, valb
))
257 vala
= TAILQ_NEXT(vala
, link
);
258 valb
= TAILQ_NEXT(valb
, link
);
264 hashfunc_ctor(uintptr_t _p
)
266 evtr_variable_value_t val
, ctor_val
= (evtr_variable_value_t
)_p
;
267 char buf
[1024], *p
= &buf
[0];
271 assert(ctor_val
->type
== EVTR_VAL_CTOR
);
272 len
= strlcpy(buf
, ctor_val
->ctor
.name
, sizeof(buf
));
273 if (len
>= sizeof(buf
))
276 TAILQ_FOREACH(val
, &ctor_val
->ctor
.args
, link
) {
279 assert(!"can't happen");
282 len
+= snprintf(p
+ len
, sizeof(buf
) - len
- 1,
286 len
= strlcat(p
, val
->str
, sizeof(buf
));
289 break; /* come on! */
291 err(3, "not implemented");
293 if (len
>= (sizeof(buf
) - 1))
297 buf
[sizeof(buf
) - 1] = '\0';
298 return hashfunc_string((uintptr_t)buf
);
301 typedef struct vector
{
311 if (!(v
= malloc(sizeof(*v
))))
314 if (!(v
->vals
= malloc(v
->allocated
* sizeof(v
->vals
[0])))) {
325 vector_push(vector_t v
, uintmax_t val
)
328 if (v
->used
== v
->allocated
) {
329 tmp
= realloc(v
->vals
, 2 * v
->allocated
* sizeof(v
->vals
[0]));
331 err(1, "out of memory");
335 v
->vals
[v
->used
++] = val
;
340 vector_destroy(vector_t v
)
348 vector_nelems(vector_t v
)
353 #define vector_foreach(v, val, i) \
354 for (i = 0, val = v->vals[0]; i < v->used; val = v->vals[++i])
358 stddev(vector_t v
, double avg
)
362 double diff
, sqr_sum
= 0.0;
364 if (vector_nelems(v
) < 2)
366 vector_foreach(v
, val
, i
) {
368 sqr_sum
+= diff
* diff
;
370 return sqrt(sqr_sum
/ (vector_nelems(v
) - 1));
377 fprintf(stderr
, "bad usage :P\n");
383 rows_init(struct rows
*rows
, int n
, double height
, double perc
)
386 rows
->row_increment
= height
/ n
;
387 /* actual row height */
388 row_h
= perc
* rows
->row_increment
;
389 rows
->row_off
= (rows
->row_increment
- row_h
) / 2.0;
390 assert(!isnan(rows
->row_increment
));
391 assert(!isnan(rows
->row_off
));
396 rows_n(struct rows
*rows
, int n
, double *y
, double *height
)
398 *y
= n
* rows
->row_increment
+ rows
->row_off
;
399 *height
= rows
->row_increment
- 2 * rows
->row_off
;
403 * Which fontsize to use so that the string fits in the
408 fontsize_for_rect(double width
, double height
, int textlen
)
412 * We start with a font size equal to the height
413 * of the rectangle and round down so that we only
414 * use a limited number of sizes.
416 * For a rectangle width comparable to the height,
417 * the text might extend outside of the rectangle.
418 * In that case we need to limit it.
420 /* available width per character */
421 wpc
= width
/ textlen
;
423 * Assuming a rough hight/width ratio for characters,
424 * calculate the available height and round it down
425 * just to be on the safe side.
427 #define GLYPH_HIGHT_TO_WIDTH 1.5
428 maxh
= GLYPH_HIGHT_TO_WIDTH
* wpc
* 0.9;
431 } else if (height
< 0.01) {
434 /* rounding (XXX: make cheaper)*/
435 height
= log(height
);
436 height
= round(height
);
437 height
= exp(height
);
444 void (*event
)(void *, evtr_event_t
);
445 void (*post
)(void *);
447 struct evtr_filter
*filts
;
460 struct td_switch_ctx
{
462 struct rows
*cpu_rows
;
463 struct rows
*thread_rows
;
464 /* which events the user cares about */
465 struct ts_interval interval
;
466 /* first/last event timestamps on any cpu */
467 struct ts_interval firstlast
;
469 double xscale
; /* scale factor applied to x */
470 svg_rect_t cpu_sw_rect
;
471 svg_rect_t thread_rect
;
472 svg_rect_t inactive_rect
;
473 svg_text_t thread_label
;
478 struct evtr_thread
**top_threads
;
480 double thread_rows_yoff
;
484 struct evtr_thread
*td
;
485 int i
; /* cpu index */
486 uint64_t ts
; /* time cpu switched to td */
487 /* timestamp for first/last event on this cpu */
488 struct ts_interval firstlast
;
495 do_pass(struct pass_hook
*hooks
, int nhooks
)
497 struct evtr_filter
*filts
= NULL
;
499 struct evtr_query
*q
;
500 struct evtr_event ev
;
502 for (i
= 0; i
< nhooks
; ++i
) {
503 struct pass_hook
*h
= &hooks
[i
];
507 filts
= realloc(filts
, (nfilts
+ h
->nfilts
) *
508 sizeof(struct evtr_filter
));
510 err(1, "Out of memory");
511 memcpy(filts
+ nfilts
, h
->filts
,
512 h
->nfilts
* sizeof(struct evtr_filter
));
516 q
= evtr_query_init(evtr
, filts
, nfilts
);
518 err(1, "Can't initialize query\n");
519 while(!evtr_query_next(q
, &ev
)) {
520 for (i
= 0; i
< nhooks
; ++i
) {
522 hooks
[i
].event(hooks
[i
].data
, &ev
);
525 if (evtr_query_error(q
)) {
526 err(1, "%s", evtr_query_errmsg(q
));
528 evtr_query_destroy(q
);
530 for (i
= 0; i
< nhooks
; ++i
) {
532 hooks
[i
].post(hooks
[i
].data
);
534 if (evtr_rewind(evtr
))
535 err(1, "Can't rewind event stream\n");
540 draw_thread_run(struct td_switch_ctx
*ctx
, struct cpu
*c
, evtr_event_t ev
, int row
)
542 double x
, w
, y
, height
;
543 w
= (ev
->ts
- c
->ts
) * ctx
->xscale
;
544 x
= (ev
->ts
- ctx
->firstlast
.start
) * ctx
->xscale
;
545 rows_n(ctx
->thread_rows
, row
, &y
, &height
);
546 svg_rect_draw(ctx
->svg
, ctx
->thread_rect
, x
- w
,
547 y
+ ctx
->thread_rows_yoff
, w
, height
);
552 draw_ctx_switch(struct td_switch_ctx
*ctx
, struct cpu
*c
, evtr_event_t ev
)
554 struct svg_transform textrot
;
556 double x
, w
, fs
, y
, height
;
559 assert(ctx
->xscale
> 0.0);
562 /* distance to previous context switch */
563 w
= (ev
->ts
- c
->ts
) * ctx
->xscale
;
564 x
= (ev
->ts
- ctx
->firstlast
.start
) * ctx
->xscale
;
566 fprintf(stderr
, "(%ju - %ju) * %.20lf\n",
568 (uintmax_t)ctx
->firstlast
.start
, ctx
->xscale
);
572 rows_n(ctx
->cpu_rows
, c
->i
, &y
, &height
);
574 assert(!isnan(height
));
576 svg_rect_draw(ctx
->svg
, ctx
->cpu_sw_rect
, x
- w
, y
, w
, height
);
579 * Draw the text label describing the thread we
587 textlen
= snprintf(comm
, sizeof(comm
) - 1, "%s (%p)",
588 c
->td
? c
->td
->comm
: "unknown",
589 c
->td
? c
->td
->id
: NULL
);
590 if (textlen
> (int)sizeof(comm
))
591 textlen
= sizeof(comm
) - 1;
592 comm
[sizeof(comm
) - 1] = '\0';
594 * Note the width and hight are exchanged because
595 * the bounding rectangle is rotated by 90 degrees.
597 fs
= fontsize_for_rect(height
, w
, textlen
);
598 svg_text_draw(ctx
->svg
, ctx
->thread_label
, &textrot
, comm
,
604 * The stats for ntd have changed, update ->top_threads
608 top_threads_update(struct td_switch_ctx
*ctx
, struct evtr_thread
*ntd
)
610 struct thread_info
*tdi
= ntd
->userdata
;
612 for (i
= 0; i
< ctx
->nr_top_threads
; ++i
) {
613 struct evtr_thread
*td
= ctx
->top_threads
[i
];
616 * ntd is already in top_threads and it is at
617 * the correct ranking
622 /* empty slot -- just insert our thread */
623 ctx
->top_threads
[i
] = ntd
;
626 if (((struct thread_info
*)td
->userdata
)->runtime
>=
628 /* this thread ranks higher than we do. Move on */
632 * OK, we've found the first thread that we outrank, so we
633 * need to replace it w/ our thread.
635 td
= ntd
; /* td holds the thread we will insert next */
636 for (j
= i
+ 1; j
< ctx
->nr_top_threads
; ++j
, ++i
) {
637 struct evtr_thread
*tmp
;
639 /* tmp holds the thread we replace */
640 tmp
= ctx
->top_threads
[j
];
641 ctx
->top_threads
[j
] = td
;
644 * Our thread was already in the top list,
645 * and we just removed the second instance.
646 * Nothing more to do.
658 ctxsw_prepare_event(void *_ctx
, evtr_event_t ev
)
660 struct td_switch_ctx
*ctx
= _ctx
;
661 struct cpu
*c
, *cpus
= ctx
->cputab
.cpus
;
662 struct thread_info
*tdi
;
665 printd(INTV
, "test1 (%ju:%ju) : %ju\n",
666 (uintmax_t)ctx
->interval
.start
,
667 (uintmax_t)ctx
->interval
.end
,
669 if ((ev
->ts
> ctx
->interval
.end
) ||
670 (ev
->ts
< ctx
->interval
.start
))
672 printd(INTV
, "PREPEV on %d\n", ev
->cpu
);
674 /* update first/last timestamps */
676 if (!c
->firstlast
.start
) {
677 c
->firstlast
.start
= ev
->ts
;
679 c
->firstlast
.end
= ev
->ts
;
681 * c->td can be null when this is the first ctxsw event we
685 /* update thread stats */
686 if (!c
->td
->userdata
) {
687 if (!(tdi
= malloc(sizeof(struct thread_info
))))
688 err(1, "Out of memory");
689 c
->td
->userdata
= tdi
;
692 tdi
= c
->td
->userdata
;
693 tdi
->runtime
+= ev
->ts
- c
->ts
;
694 top_threads_update(ctx
, c
->td
);
697 /* Notice that ev->td is the new thread for ctxsw events */
704 find_first_last_ts(struct cpu_table
*cputab
, struct ts_interval
*fl
)
706 struct cpu
*cpus
= &cputab
->cpus
[0];
711 for (i
= 0; i
< cputab
->ncpus
; ++i
) {
712 printd(INTV
, "cpu%d: (%ju, %ju)\n", i
,
713 (uintmax_t)cpus
[i
].firstlast
.start
,
714 (uintmax_t)cpus
[i
].firstlast
.end
);
715 if (cpus
[i
].firstlast
.start
&&
716 (cpus
[i
].firstlast
.start
< fl
->start
))
717 fl
->start
= cpus
[i
].firstlast
.start
;
718 if (cpus
[i
].firstlast
.end
&&
719 (cpus
[i
].firstlast
.end
> fl
->end
))
720 fl
->end
= cpus
[i
].firstlast
.end
;
724 printd(INTV
, "global (%jd, %jd)\n", (uintmax_t)fl
->start
, (uintmax_t)fl
->end
);
729 ctxsw_prepare_post(void *_ctx
)
731 struct td_switch_ctx
*ctx
= _ctx
;
733 find_first_last_ts(&ctx
->cputab
, &ctx
->firstlast
);
738 ctxsw_draw_pre(void *_ctx
)
740 struct td_switch_ctx
*ctx
= _ctx
;
741 struct svg_transform textrot
;
743 double y
, height
, fs
;
745 struct evtr_thread
*td
;
747 textrot
.tx
= 0.0 - 0.2; /* XXX */
752 for (i
= 0; i
< ctx
->nr_top_threads
; ++i
) {
753 td
= ctx
->top_threads
[i
];
756 rows_n(ctx
->thread_rows
, i
, &y
, &height
);
757 svg_rect_draw(ctx
->svg
, ctx
->inactive_rect
, 0.0,
758 y
+ ctx
->thread_rows_yoff
, ctx
->width
, height
);
759 textlen
= snprintf(comm
, sizeof(comm
) - 1, "%s (%p)",
761 if (textlen
> (int)sizeof(comm
))
762 textlen
= sizeof(comm
) - 1;
763 comm
[sizeof(comm
) - 1] = '\0';
764 fs
= fontsize_for_rect(height
, 100.0, textlen
);
766 textrot
.ty
= y
+ ctx
->thread_rows_yoff
+ height
;
767 svg_text_draw(ctx
->svg
, ctx
->thread_label
, &textrot
,
774 ctxsw_draw_event(void *_ctx
, evtr_event_t ev
)
776 struct td_switch_ctx
*ctx
= _ctx
;
777 struct cpu
*c
= &ctx
->cputab
.cpus
[ev
->cpu
];
781 * ctx->firstlast.end can be 0 if there were no events
782 * in the specified interval, in which case
783 * ctx->firstlast.start is invalid too.
785 assert(!ctx
->firstlast
.end
|| (ev
->ts
>= ctx
->firstlast
.start
));
786 printd(INTV
, "test2 (%ju:%ju) : %ju\n", (uintmax_t)ctx
->interval
.start
,
787 (uintmax_t)ctx
->interval
.end
, (uintmax_t)ev
->ts
);
788 if ((ev
->ts
> ctx
->interval
.end
) ||
789 (ev
->ts
< ctx
->interval
.start
))
791 printd(INTV
, "DRAWEV %d\n", ev
->cpu
);
792 if (c
->td
!= ev
->td
) { /* thread switch (or preemption) */
793 draw_ctx_switch(ctx
, c
, ev
);
794 /* XXX: this is silly */
795 for (i
= 0; i
< ctx
->nr_top_threads
; ++i
) {
796 if (!ctx
->top_threads
[i
])
798 if (ctx
->top_threads
[i
] == c
->td
) {
799 draw_thread_run(ctx
, c
, ev
, i
);
810 cputab_init(struct cpu_table
*ct
)
816 if ((ct
->ncpus
= evtr_ncpus(evtr
)) <= 0)
817 err(1, "No cpu information!\n");
818 printd(MISC
, "evtranalyze: ncpus %d\n", ct
->ncpus
);
819 if (!(ct
->cpus
= malloc(sizeof(struct cpu
) * ct
->ncpus
))) {
820 err(1, "Can't allocate memory\n");
823 if (!(freqs
= malloc(sizeof(double) * ct
->ncpus
))) {
824 err(1, "Can't allocate memory\n");
826 if ((i
= evtr_cpufreqs(evtr
, freqs
))) {
827 warnc(i
, "Can't get cpu frequencies\n");
828 for (i
= 0; i
< ct
->ncpus
; ++i
) {
833 /* initialize cpu array */
834 for (i
= 0; i
< ct
->ncpus
; ++i
) {
838 cpus
[i
].firstlast
.start
= 0;
839 cpus
[i
].firstlast
.end
= 0;
841 cpus
[i
].freq
= freqs
[i
];
848 parse_interval(const char *_str
, struct ts_interval
*ts
,
849 struct cpu_table
*cputab
)
852 const char *str
= _str
+ 1;
854 if ('c' == *_str
) { /* cycles */
855 if (sscanf(str
, "%" SCNu64
":%" SCNu64
,
859 } else if ('m' == *_str
) { /* miliseconds */
860 if (sscanf(str
, "%lf:%lf", &s
, &e
) == 2) {
861 freq
= cputab
->cpus
[0].freq
;
862 freq
*= 1000.0; /* msecs */
864 fprintf(stderr
, "No frequency information"
866 err(2, "Can't convert time to cycles\n");
868 ts
->start
= s
* freq
;
873 fprintf(stderr
, "invalid interval format: %s\n", _str
);
880 cmd_svg(int argc
, char **argv
)
884 double height
, width
;
885 struct rows cpu_rows
, thread_rows
;
886 struct td_switch_ctx td_ctx
;
887 const char *outpath
= "output.svg";
888 struct evtr_filter ctxsw_filts
[2] = {
892 .ev_type
= EVTR_TYPE_PROBE
,
897 .ev_type
= EVTR_TYPE_PROBE
,
900 struct pass_hook ctxsw_prepare
= {
902 .event
= ctxsw_prepare_event
,
903 .post
= ctxsw_prepare_post
,
905 .filts
= ctxsw_filts
,
906 .nfilts
= sizeof(ctxsw_filts
)/sizeof(ctxsw_filts
[0]),
908 .pre
= ctxsw_draw_pre
,
909 .event
= ctxsw_draw_event
,
912 .filts
= ctxsw_filts
,
913 .nfilts
= sizeof(ctxsw_filts
)/sizeof(ctxsw_filts
[0]),
917 * We are interested in thread switch and preemption
918 * events, but we don't use the data directly. Instead
921 ctxsw_filts
[0].fmt
= "sw %p > %p";
922 ctxsw_filts
[1].fmt
= "pre %p > %p";
923 td_ctx
.interval
.start
= 0;
924 td_ctx
.interval
.end
= -1; /* i.e. no interval given */
925 td_ctx
.nr_top_threads
= NR_TOP_THREADS
;
926 cputab_init(&td_ctx
.cputab
); /* needed for parse_interval() */
930 while ((ch
= getopt(argc
, argv
, "i:o:")) != -1) {
933 parse_interval(optarg
, &td_ctx
.interval
,
949 td_ctx
.width
= width
;
951 if (!(td_ctx
.top_threads
= calloc(td_ctx
.nr_top_threads
,
952 sizeof(struct evtr_thread
*))))
953 err(1, "Can't allocate memory\n");
954 if (!(svg
= svg_document_create(outpath
)))
955 err(1, "Can't open svg document\n");
958 * Create rectangles to use for output.
960 if (!(td_ctx
.cpu_sw_rect
= svg_rect_new("generic")))
961 err(1, "Can't create rectangle\n");
962 if (!(td_ctx
.thread_rect
= svg_rect_new("thread")))
963 err(1, "Can't create rectangle\n");
964 if (!(td_ctx
.inactive_rect
= svg_rect_new("inactive")))
965 err(1, "Can't create rectangle\n");
966 /* text for thread names */
967 if (!(td_ctx
.thread_label
= svg_text_new("generic")))
968 err(1, "Can't create text\n");
969 rows_init(&cpu_rows
, td_ctx
.cputab
.ncpus
, height
, 0.9);
971 td_ctx
.xscale
= -1.0;
972 td_ctx
.cpu_rows
= &cpu_rows
;
974 do_pass(&ctxsw_prepare
, 1);
975 td_ctx
.thread_rows_yoff
= height
;
976 td_ctx
.thread_rows
= &thread_rows
;
977 rows_init(td_ctx
.thread_rows
, td_ctx
.nr_top_threads
, 300, 0.9);
978 td_ctx
.xscale
= width
/ (td_ctx
.firstlast
.end
- td_ctx
.firstlast
.start
);
979 printd(SVG
, "first %ju, last %ju, xscale %lf\n",
980 (uintmax_t)td_ctx
.firstlast
.start
, (uintmax_t)td_ctx
.firstlast
.end
,
983 do_pass(&ctxsw_draw
, 1);
985 svg_document_close(svg
);
991 cmd_show(int argc
, char **argv
)
993 struct evtr_event ev
;
994 struct evtr_query
*q
;
995 struct evtr_filter filt
;
996 struct cpu_table cputab
;
999 uint64_t last_ts
= 0;
1001 cputab_init(&cputab
);
1003 * Assume all cores run on the same frequency
1004 * for now. There's no reason to complicate
1005 * things unless we can detect frequency change
1008 * Note that the code is very simplistic and will
1009 * produce garbage if the kernel doesn't fixup
1010 * the timestamps for cores running with different
1013 freq
= cputab
.cpus
[0].freq
;
1014 freq
/= 1000000; /* we want to print out usecs */
1015 printd(MISC
, "using freq = %lf\n", freq
);
1018 filt
.ev_type
= EVTR_TYPE_PROBE
;
1022 while ((ch
= getopt(argc
, argv
, "f:")) != -1) {
1029 q
= evtr_query_init(evtr
, &filt
, 1);
1031 err(1, "Can't initialize query\n");
1032 while(!evtr_query_next(q
, &ev
)) {
1038 printf("%s\t%ju cycles\t[%.3d]\t%s:%d",
1039 ev
.td
? ev
.td
->comm
: "unknown",
1040 (uintmax_t)(ev
.ts
- last_ts
), ev
.cpu
,
1041 basename(ev
.file
), ev
.line
);
1043 printf("%s\t%.3lf usecs\t[%.3d]\t%s:%d",
1044 ev
.td
? ev
.td
->comm
: "unknown",
1045 (ev
.ts
- last_ts
) / freq
, ev
.cpu
,
1046 basename(ev
.file
), ev
.line
);
1049 evtr_event_data(&ev
, buf
, sizeof(buf
));
1050 printf(" !\t%s\n", buf
);
1056 if (evtr_query_error(q
)) {
1057 err(1, "%s", evtr_query_errmsg(q
));
1059 evtr_query_destroy(q
);
1064 const char *statscmd
;
1065 void *(*prepare
)(int, char **, struct evtr_filter
*);
1066 void (*each_event
)(void *, evtr_event_t
);
1067 void (*report
)(void *);
1070 struct stats_integer_ctx
{
1071 const char *varname
;
1077 struct plotter
*plotter
;
1080 uintmax_t occurrences
;
1085 stats_integer_prepare(int argc
, char **argv
, struct evtr_filter
*filt
)
1087 struct stats_integer_ctx
*ctx
;
1090 if (!(ctx
= calloc(1, sizeof(*ctx
))))
1095 while ((ch
= getopt(argc
, argv
, "p:")) != -1) {
1098 ctx
->opts
.plot
= !0;
1099 ctx
->opts
.path
= optarg
;
1109 err(2, "Need exactly one variable");
1110 ctx
->varname
= argv
[0];
1111 ctx
->sum
= ctx
->occurrences
= 0;
1114 filt
->ev_type
= EVTR_TYPE_STMT
;
1115 filt
->var
= ctx
->varname
;
1116 if (!ctx
->opts
.plot
)
1119 if (!(ctx
->plotter
= plotter_factory()))
1120 err(1, "can't allocate plotter");
1121 if (!(ctx
->plotter_ctx
= ctx
->plotter
->plot_init(ctx
->opts
.path
)))
1122 err(1, "can't allocate plotter context");
1124 if ((ctx
->time_plot
= ctx
->plotter
->plot_new(ctx
->plotter_ctx
,
1127 err(1, "can't create histogram");
1133 stats_integer_each(void *_ctx
, evtr_event_t ev
)
1135 struct stats_integer_ctx
*ctx
= _ctx
;
1136 if (EVTR_VAL_INT
!= ev
->stmt
.val
->type
) {
1137 fprintf(stderr
, "event at %jd (cpu %d) does not treat %s as an"
1138 "integer variable; ignored\n", ev
->ts
, ev
->cpu
,
1143 ctx
->plotter
->plot_line(ctx
->plotter_ctx
, ctx
->time_plot
,
1144 (double)ev
->ts
, (double)ev
->stmt
.val
->num
);
1145 ctx
->sum
+= ev
->stmt
.val
->num
;
1151 stats_integer_report(void *_ctx
)
1153 struct stats_integer_ctx
*ctx
= _ctx
;
1154 printf("median for variable %s is %lf\n", ctx
->varname
,
1155 (double)ctx
->sum
/ ctx
->occurrences
);
1157 ctx
->plotter
->plot_finish(ctx
->plotter_ctx
);
1162 struct stats_completion_ctx
{
1163 struct stats_completion_options
{
1167 struct plotter
*plotter
;
1169 plotid_t durations_plot
;
1170 const char *varname
;
1173 struct hashtab
*ctors
;
1174 uintmax_t historical_dtors
;
1175 uintmax_t uncompleted_events
;
1176 uintmax_t begun_events
;
1177 uintmax_t completed_events
;
1178 uintmax_t completed_duration_sum
;
1183 evtr_variable_value_t val
;
1189 ctor_data_new(evtr_event_t ev
)
1191 struct ctor_data
*cd
;
1193 if (!(cd
= malloc(sizeof(*cd
))))
1195 cd
->val
= ev
->stmt
.val
;
1202 stats_completion_prepare(int argc
, char **argv
, struct evtr_filter
*filt
)
1204 struct stats_completion_ctx
*ctx
;
1207 if (!(ctx
= calloc(1, sizeof(*ctx
))))
1212 while ((ch
= getopt(argc
, argv
, "p:")) != -1) {
1215 ctx
->opts
.plot
= !0;
1216 ctx
->opts
.path
= optarg
;
1225 err(2, "need a variable, a constructor and a destructor");
1226 if (!(ctx
->ctors
= ehash_new()))
1228 ctx
->ctors
->hashfunc
= &hashfunc_ctor
;
1229 ctx
->ctors
->cmpfunc
= &cmpfunc_ctor
;
1230 if (!(ctx
->durations
= vector_new()))
1232 ctx
->varname
= argv
[0];
1233 ctx
->ctor
= argv
[1];
1234 ctx
->dtor
= argv
[2];
1238 filt
->ev_type
= EVTR_TYPE_STMT
;
1239 filt
->var
= ctx
->varname
;
1241 if (!ctx
->opts
.plot
)
1244 if (!(ctx
->plotter
= plotter_factory()))
1245 err(1, "can't allocate plotter");
1246 if (!(ctx
->plotter_ctx
= ctx
->plotter
->plot_init(ctx
->opts
.path
)))
1247 err(1, "can't allocate plotter context");
1249 if ((ctx
->durations_plot
= ctx
->plotter
->plot_new(ctx
->plotter_ctx
,
1252 err(1, "can't create histogram");
1263 stats_completion_each(void *_ctx
, evtr_event_t ev
)
1265 struct stats_completion_ctx
*ctx
= _ctx
;
1266 struct ctor_data
*cd
;
1268 if (ev
->stmt
.val
->type
!= EVTR_VAL_CTOR
) {
1269 fprintf(stderr
, "event at %jd (cpu %d) does not assign to %s "
1270 "with a data constructor; ignored\n", ev
->ts
, ev
->cpu
,
1274 if (!strcmp(ev
->stmt
.val
->ctor
.name
, ctx
->ctor
)) {
1276 if (!ehash_find(ctx
->ctors
, (uintptr_t)ev
->stmt
.val
, &v
)) {
1277 /* XXX:better diagnostic */
1278 fprintf(stderr
, "duplicate ctor\n");
1279 err(3, "giving up");
1281 if (!(cd
= ctor_data_new(ev
)))
1282 err(1, "out of memory");
1284 if (!ehash_insert(ctx
->ctors
, (uintptr_t)ev
->stmt
.val
, v
))
1285 err(1, "out of memory");
1286 ++ctx
->begun_events
;
1287 } else if (!strcmp(ev
->stmt
.val
->ctor
.name
, ctx
->dtor
)) {
1289 const char *tmp
= ev
->stmt
.val
->ctor
.name
;
1290 ev
->stmt
.val
->ctor
.name
= ctx
->ctor
;
1291 if (ehash_find(ctx
->ctors
, (uintptr_t)ev
->stmt
.val
, &v
)) {
1292 ++ctx
->historical_dtors
;
1293 ev
->stmt
.val
->ctor
.name
= tmp
;
1296 cd
= (struct ctor_data
*)v
;
1297 if (cd
->ts
>= ev
->ts
) {
1298 /* XXX:better diagnostic */
1299 fprintf(stderr
, "destructor preceds constructor;"
1301 ev
->stmt
.val
->ctor
.name
= tmp
;
1305 ctx
->plotter
->plot_histogram(ctx
->plotter_ctx
,
1306 ctx
->durations_plot
,
1307 (double)(ev
->ts
- cd
->ts
));
1308 vector_push(ctx
->durations
, ev
->ts
- cd
->ts
);
1309 ++ctx
->completed_events
;
1310 ctx
->completed_duration_sum
+= ev
->ts
- cd
->ts
;
1311 if (ehash_delete(ctx
->ctors
, (uintptr_t)ev
->stmt
.val
))
1312 err(3, "ctor disappeared from hash!");
1313 ev
->stmt
.val
->ctor
.name
= tmp
;
1315 fprintf(stderr
, "event at %jd (cpu %d) assigns to %s "
1316 "with an unexpected data constructor; ignored\n",
1317 ev
->ts
, ev
->cpu
, ctx
->varname
);
1324 stats_completion_report(void *_ctx
)
1326 struct stats_completion_ctx
*ctx
= _ctx
;
1329 printf("Events completed without having started:\t%jd\n",
1330 ctx
->historical_dtors
);
1331 printf("Events started but didn't complete:\t%jd\n",
1332 ctx
->begun_events
- ctx
->completed_events
);
1333 avg
= (double)ctx
->completed_duration_sum
/ ctx
->completed_events
;
1334 printf("Average event duration:\t%lf (stddev %lf)\n", avg
,
1335 stddev(ctx
->durations
, avg
));
1338 ctx
->plotter
->plot_finish(ctx
->plotter_ctx
);
1339 vector_destroy(ctx
->durations
);
1344 static struct stats_ops cmd_stat_ops
[] = {
1346 .statscmd
= "integer",
1347 .prepare
= &stats_integer_prepare
,
1348 .each_event
= &stats_integer_each
,
1349 .report
= &stats_integer_report
,
1352 .statscmd
= "completion",
1353 .prepare
= &stats_completion_prepare
,
1354 .each_event
= &stats_completion_each
,
1355 .report
= &stats_completion_report
,
1364 cmd_stats(int argc
, char **argv
)
1366 struct evtr_event ev
;
1367 struct evtr_query
*q
;
1368 struct evtr_filter filt
;
1369 struct cpu_table cputab
;
1371 uint64_t last_ts
= 0;
1372 struct stats_ops
*statsops
= &cmd_stat_ops
[0];
1375 for (; statsops
->statscmd
; ++statsops
) {
1376 if (!strcmp(statsops
->statscmd
, argv
[1]))
1379 if (!statsops
->statscmd
)
1380 err(2, "No such stats type: %s", argv
[1]);
1384 cputab_init(&cputab
);
1386 * Assume all cores run on the same frequency
1387 * for now. There's no reason to complicate
1388 * things unless we can detect frequency change
1391 * Note that the code is very simplistic and will
1392 * produce garbage if the kernel doesn't fixup
1393 * the timestamps for cores running with different
1396 freq
= cputab
.cpus
[0].freq
;
1397 freq
/= 1000000; /* we want to print out usecs */
1398 printd(MISC
, "using freq = %lf\n", freq
);
1400 if (!(statctx
= statsops
->prepare(argc
, argv
, &filt
)))
1401 err(1, "Can't allocate stats context");
1402 q
= evtr_query_init(evtr
, &filt
, 1);
1404 err(1, "Can't initialize query");
1405 while(!evtr_query_next(q
, &ev
)) {
1410 assert(ev
.type
== EVTR_TYPE_STMT
);
1411 statsops
->each_event(statctx
, &ev
);
1414 if (evtr_query_error(q
)) {
1415 err(1, "%s", evtr_query_errmsg(q
));
1417 evtr_query_destroy(q
);
1418 statsops
->report(statctx
);
1425 cmd_summary(int argc
, char **argv
)
1427 struct evtr_filter filt
;
1428 struct evtr_event ev
;
1429 struct evtr_query
*q
;
1431 struct cpu_table cputab
;
1432 struct ts_interval global
;
1433 uintmax_t global_evcnt
;
1439 cputab_init(&cputab
);
1440 filt
.ev_type
= EVTR_TYPE_PROBE
;
1445 q
= evtr_query_init(evtr
, &filt
, 1);
1447 err(1, "Can't initialize query\n");
1448 while(!evtr_query_next(q
, &ev
)) {
1449 struct cpu
*c
= &cputab
.cpus
[ev
.cpu
];
1450 if (!c
->firstlast
.start
)
1451 c
->firstlast
.start
= ev
.ts
;
1453 c
->firstlast
.end
= ev
.ts
;
1455 if (evtr_query_error(q
)) {
1456 err(1, "%s", evtr_query_errmsg(q
));
1458 evtr_query_destroy(q
);
1460 find_first_last_ts(&cputab
, &global
);
1462 freq
= cputab
.cpus
[0].freq
;
1464 for (i
= 0; i
< cputab
.ncpus
; ++i
) {
1465 struct cpu
*c
= &cputab
.cpus
[i
];
1466 printf("CPU %d: %jd events in %.3lf secs\n", i
,
1467 c
->evcnt
, (c
->firstlast
.end
- c
->firstlast
.start
)
1469 global_evcnt
+= c
->evcnt
;
1471 printf("Total: %jd events on %d cpus in %.3lf secs\n", global_evcnt
,
1472 cputab
.ncpus
, (global
.end
- global
.start
) / freq
);
1478 main(int argc
, char **argv
)
1482 struct command
*cmd
;
1485 while ((ch
= getopt(argc
, argv
, "f:D:")) != -1) {
1488 opt_infile
= optarg
;
1491 if ((tmp
= strchr(optarg
, ':'))) {
1493 evtr_set_debug(tmp
);
1495 printd_set_flags(optarg
, &evtranalyze_debug
);
1505 err(2, "need to specify a command\n");
1508 err(2, "you need to specify an input file\n");
1509 } else if (!strcmp(opt_infile
, "-")) {
1512 inf
= fopen(opt_infile
, "r");
1514 err(2, "Can't open input file\n");
1518 if (!(evtr
= evtr_open_read(inf
))) {
1519 err(1, "Can't open evtr stream\n");
1523 for (cmd
= commands
; cmd
->name
!= NULL
; ++cmd
) {
1524 if (strcmp(argv
[0], cmd
->name
))
1526 cmd
->func(argc
, argv
);
1530 err(2, "no such command: %s\n", argv
[0]);