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
)) {
1039 tmpbuf
= strdup(ev
.file
);
1042 printf("%s\t%ju cycles\t[%.3d]\t%s:%d",
1043 ev
.td
? ev
.td
->comm
: "unknown",
1044 (uintmax_t)(ev
.ts
- last_ts
), ev
.cpu
,
1045 basename(tmpbuf
), ev
.line
);
1047 printf("%s\t%.3lf usecs\t[%.3d]\t%s:%d",
1048 ev
.td
? ev
.td
->comm
: "unknown",
1049 (ev
.ts
- last_ts
) / freq
, ev
.cpu
,
1050 basename(tmpbuf
), ev
.line
);
1054 evtr_event_data(&ev
, buf
, sizeof(buf
));
1055 printf(" !\t%s\n", buf
);
1061 if (evtr_query_error(q
)) {
1062 err(1, "%s", evtr_query_errmsg(q
));
1064 evtr_query_destroy(q
);
1069 const char *statscmd
;
1070 void *(*prepare
)(int, char **, struct evtr_filter
*);
1071 void (*each_event
)(void *, evtr_event_t
);
1072 void (*report
)(void *);
1075 struct stats_integer_ctx
{
1076 const char *varname
;
1082 struct plotter
*plotter
;
1085 uintmax_t occurrences
;
1090 stats_integer_prepare(int argc
, char **argv
, struct evtr_filter
*filt
)
1092 struct stats_integer_ctx
*ctx
;
1095 if (!(ctx
= calloc(1, sizeof(*ctx
))))
1100 while ((ch
= getopt(argc
, argv
, "p:")) != -1) {
1103 ctx
->opts
.plot
= !0;
1104 ctx
->opts
.path
= optarg
;
1114 err(2, "Need exactly one variable");
1115 ctx
->varname
= argv
[0];
1116 ctx
->sum
= ctx
->occurrences
= 0;
1119 filt
->ev_type
= EVTR_TYPE_STMT
;
1120 filt
->var
= ctx
->varname
;
1121 if (!ctx
->opts
.plot
)
1124 if (!(ctx
->plotter
= plotter_factory()))
1125 err(1, "can't allocate plotter");
1126 if (!(ctx
->plotter_ctx
= ctx
->plotter
->plot_init(ctx
->opts
.path
)))
1127 err(1, "can't allocate plotter context");
1129 if ((ctx
->time_plot
= ctx
->plotter
->plot_new(ctx
->plotter_ctx
,
1132 err(1, "can't create histogram");
1138 stats_integer_each(void *_ctx
, evtr_event_t ev
)
1140 struct stats_integer_ctx
*ctx
= _ctx
;
1141 if (EVTR_VAL_INT
!= ev
->stmt
.val
->type
) {
1142 fprintf(stderr
, "event at %jd (cpu %d) does not treat %s as an"
1143 "integer variable; ignored\n", ev
->ts
, ev
->cpu
,
1148 ctx
->plotter
->plot_line(ctx
->plotter_ctx
, ctx
->time_plot
,
1149 (double)ev
->ts
, (double)ev
->stmt
.val
->num
);
1150 ctx
->sum
+= ev
->stmt
.val
->num
;
1156 stats_integer_report(void *_ctx
)
1158 struct stats_integer_ctx
*ctx
= _ctx
;
1159 printf("median for variable %s is %lf\n", ctx
->varname
,
1160 (double)ctx
->sum
/ ctx
->occurrences
);
1162 ctx
->plotter
->plot_finish(ctx
->plotter_ctx
);
1167 struct stats_completion_ctx
{
1168 struct stats_completion_options
{
1172 struct plotter
*plotter
;
1174 plotid_t durations_plot
;
1175 const char *varname
;
1178 struct hashtab
*ctors
;
1179 uintmax_t historical_dtors
;
1180 uintmax_t uncompleted_events
;
1181 uintmax_t begun_events
;
1182 uintmax_t completed_events
;
1183 uintmax_t completed_duration_sum
;
1188 evtr_variable_value_t val
;
1194 ctor_data_new(evtr_event_t ev
)
1196 struct ctor_data
*cd
;
1198 if (!(cd
= malloc(sizeof(*cd
))))
1200 cd
->val
= ev
->stmt
.val
;
1207 stats_completion_prepare(int argc
, char **argv
, struct evtr_filter
*filt
)
1209 struct stats_completion_ctx
*ctx
;
1212 if (!(ctx
= calloc(1, sizeof(*ctx
))))
1217 while ((ch
= getopt(argc
, argv
, "p:")) != -1) {
1220 ctx
->opts
.plot
= !0;
1221 ctx
->opts
.path
= optarg
;
1230 err(2, "need a variable, a constructor and a destructor");
1231 if (!(ctx
->ctors
= ehash_new()))
1233 ctx
->ctors
->hashfunc
= &hashfunc_ctor
;
1234 ctx
->ctors
->cmpfunc
= &cmpfunc_ctor
;
1235 if (!(ctx
->durations
= vector_new()))
1237 ctx
->varname
= argv
[0];
1238 ctx
->ctor
= argv
[1];
1239 ctx
->dtor
= argv
[2];
1243 filt
->ev_type
= EVTR_TYPE_STMT
;
1244 filt
->var
= ctx
->varname
;
1246 if (!ctx
->opts
.plot
)
1249 if (!(ctx
->plotter
= plotter_factory()))
1250 err(1, "can't allocate plotter");
1251 if (!(ctx
->plotter_ctx
= ctx
->plotter
->plot_init(ctx
->opts
.path
)))
1252 err(1, "can't allocate plotter context");
1254 if ((ctx
->durations_plot
= ctx
->plotter
->plot_new(ctx
->plotter_ctx
,
1257 err(1, "can't create histogram");
1268 stats_completion_each(void *_ctx
, evtr_event_t ev
)
1270 struct stats_completion_ctx
*ctx
= _ctx
;
1271 struct ctor_data
*cd
;
1273 if (ev
->stmt
.val
->type
!= EVTR_VAL_CTOR
) {
1274 fprintf(stderr
, "event at %jd (cpu %d) does not assign to %s "
1275 "with a data constructor; ignored\n", ev
->ts
, ev
->cpu
,
1279 if (!strcmp(ev
->stmt
.val
->ctor
.name
, ctx
->ctor
)) {
1281 if (!ehash_find(ctx
->ctors
, (uintptr_t)ev
->stmt
.val
, &v
)) {
1282 /* XXX:better diagnostic */
1283 fprintf(stderr
, "duplicate ctor\n");
1284 err(3, "giving up");
1286 if (!(cd
= ctor_data_new(ev
)))
1287 err(1, "out of memory");
1289 if (!ehash_insert(ctx
->ctors
, (uintptr_t)ev
->stmt
.val
, v
))
1290 err(1, "out of memory");
1291 ++ctx
->begun_events
;
1292 } else if (!strcmp(ev
->stmt
.val
->ctor
.name
, ctx
->dtor
)) {
1294 const char *tmp
= ev
->stmt
.val
->ctor
.name
;
1295 ev
->stmt
.val
->ctor
.name
= ctx
->ctor
;
1296 if (ehash_find(ctx
->ctors
, (uintptr_t)ev
->stmt
.val
, &v
)) {
1297 ++ctx
->historical_dtors
;
1298 ev
->stmt
.val
->ctor
.name
= tmp
;
1301 cd
= (struct ctor_data
*)v
;
1302 if (cd
->ts
>= ev
->ts
) {
1303 /* XXX:better diagnostic */
1304 fprintf(stderr
, "destructor preceds constructor;"
1306 ev
->stmt
.val
->ctor
.name
= tmp
;
1310 ctx
->plotter
->plot_histogram(ctx
->plotter_ctx
,
1311 ctx
->durations_plot
,
1312 (double)(ev
->ts
- cd
->ts
));
1313 vector_push(ctx
->durations
, ev
->ts
- cd
->ts
);
1314 ++ctx
->completed_events
;
1315 ctx
->completed_duration_sum
+= ev
->ts
- cd
->ts
;
1316 if (ehash_delete(ctx
->ctors
, (uintptr_t)ev
->stmt
.val
))
1317 err(3, "ctor disappeared from hash!");
1318 ev
->stmt
.val
->ctor
.name
= tmp
;
1320 fprintf(stderr
, "event at %jd (cpu %d) assigns to %s "
1321 "with an unexpected data constructor; ignored\n",
1322 ev
->ts
, ev
->cpu
, ctx
->varname
);
1329 stats_completion_report(void *_ctx
)
1331 struct stats_completion_ctx
*ctx
= _ctx
;
1334 printf("Events completed without having started:\t%jd\n",
1335 ctx
->historical_dtors
);
1336 printf("Events started but didn't complete:\t%jd\n",
1337 ctx
->begun_events
- ctx
->completed_events
);
1338 avg
= (double)ctx
->completed_duration_sum
/ ctx
->completed_events
;
1339 printf("Average event duration:\t%lf (stddev %lf)\n", avg
,
1340 stddev(ctx
->durations
, avg
));
1343 ctx
->plotter
->plot_finish(ctx
->plotter_ctx
);
1344 vector_destroy(ctx
->durations
);
1349 static struct stats_ops cmd_stat_ops
[] = {
1351 .statscmd
= "integer",
1352 .prepare
= &stats_integer_prepare
,
1353 .each_event
= &stats_integer_each
,
1354 .report
= &stats_integer_report
,
1357 .statscmd
= "completion",
1358 .prepare
= &stats_completion_prepare
,
1359 .each_event
= &stats_completion_each
,
1360 .report
= &stats_completion_report
,
1369 cmd_stats(int argc
, char **argv
)
1371 struct evtr_event ev
;
1372 struct evtr_query
*q
;
1373 struct evtr_filter filt
;
1374 struct cpu_table cputab
;
1376 uint64_t last_ts
= 0;
1377 struct stats_ops
*statsops
= &cmd_stat_ops
[0];
1380 for (; statsops
->statscmd
; ++statsops
) {
1381 if (!strcmp(statsops
->statscmd
, argv
[1]))
1384 if (!statsops
->statscmd
)
1385 err(2, "No such stats type: %s", argv
[1]);
1389 cputab_init(&cputab
);
1391 * Assume all cores run on the same frequency
1392 * for now. There's no reason to complicate
1393 * things unless we can detect frequency change
1396 * Note that the code is very simplistic and will
1397 * produce garbage if the kernel doesn't fixup
1398 * the timestamps for cores running with different
1401 freq
= cputab
.cpus
[0].freq
;
1402 freq
/= 1000000; /* we want to print out usecs */
1403 printd(MISC
, "using freq = %lf\n", freq
);
1405 if (!(statctx
= statsops
->prepare(argc
, argv
, &filt
)))
1406 err(1, "Can't allocate stats context");
1407 q
= evtr_query_init(evtr
, &filt
, 1);
1409 err(1, "Can't initialize query");
1410 while(!evtr_query_next(q
, &ev
)) {
1415 assert(ev
.type
== EVTR_TYPE_STMT
);
1416 statsops
->each_event(statctx
, &ev
);
1419 if (evtr_query_error(q
)) {
1420 err(1, "%s", evtr_query_errmsg(q
));
1422 evtr_query_destroy(q
);
1423 statsops
->report(statctx
);
1430 cmd_summary(int argc
, char **argv
)
1432 struct evtr_filter filt
;
1433 struct evtr_event ev
;
1434 struct evtr_query
*q
;
1436 struct cpu_table cputab
;
1437 struct ts_interval global
;
1438 uintmax_t global_evcnt
;
1444 cputab_init(&cputab
);
1445 filt
.ev_type
= EVTR_TYPE_PROBE
;
1450 q
= evtr_query_init(evtr
, &filt
, 1);
1452 err(1, "Can't initialize query\n");
1453 while(!evtr_query_next(q
, &ev
)) {
1454 struct cpu
*c
= &cputab
.cpus
[ev
.cpu
];
1455 if (!c
->firstlast
.start
)
1456 c
->firstlast
.start
= ev
.ts
;
1458 c
->firstlast
.end
= ev
.ts
;
1460 if (evtr_query_error(q
)) {
1461 err(1, "%s", evtr_query_errmsg(q
));
1463 evtr_query_destroy(q
);
1465 find_first_last_ts(&cputab
, &global
);
1467 freq
= cputab
.cpus
[0].freq
;
1469 for (i
= 0; i
< cputab
.ncpus
; ++i
) {
1470 struct cpu
*c
= &cputab
.cpus
[i
];
1471 printf("CPU %d: %jd events in %.3lf secs\n", i
,
1472 c
->evcnt
, (c
->firstlast
.end
- c
->firstlast
.start
)
1474 global_evcnt
+= c
->evcnt
;
1476 printf("Total: %jd events on %d cpus in %.3lf secs\n", global_evcnt
,
1477 cputab
.ncpus
, (global
.end
- global
.start
) / freq
);
1483 main(int argc
, char **argv
)
1487 struct command
*cmd
;
1490 while ((ch
= getopt(argc
, argv
, "f:D:")) != -1) {
1493 opt_infile
= optarg
;
1496 if ((tmp
= strchr(optarg
, ':'))) {
1498 evtr_set_debug(tmp
);
1500 printd_set_flags(optarg
, &evtranalyze_debug
);
1510 err(2, "need to specify a command\n");
1513 err(2, "you need to specify an input file\n");
1514 } else if (!strcmp(opt_infile
, "-")) {
1517 inf
= fopen(opt_infile
, "r");
1519 err(2, "Can't open input file\n");
1523 if (!(evtr
= evtr_open_read(inf
))) {
1524 err(1, "Can't open evtr stream\n");
1528 for (cmd
= commands
; cmd
->name
!= NULL
; ++cmd
) {
1529 if (strcmp(argv
[0], cmd
->name
))
1531 cmd
->func(argc
, argv
);
1535 err(2, "no such command: %s\n", argv
[0]);