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 <ctype.h>
34 #include <err.h>
35 #include <inttypes.h>
36 #include <libgen.h>
37 #include <math.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42 
43 #include <evtr.h>
44 #include "xml.h"
45 #include "svg.h"
46 #include "plotter.h"
47 #include "trivial.h"
48 
49 enum {
50 	NR_TOP_THREADS = 5,
51 	NR_BUCKETS = 1021,
52 };
53 
54 struct rows {
55 	double row_increment;
56 	double row_off;
57 };
58 
59 #define CMD_PROTO(name)	\
60 	static int cmd_ ## name(int, char **)
61 
62 CMD_PROTO(show);
63 CMD_PROTO(svg);
64 CMD_PROTO(stats);
65 CMD_PROTO(summary);
66 
67 struct command {
68 	const char *name;
69 	int (*func)(int argc, char **argv);
70 } commands[] = {
71 	{
72 		.name = "show",
73 		.func = &cmd_show,
74 	},
75 	{
76 		.name = "svg",
77 		.func = &cmd_svg,
78 	},
79 	{
80 		.name = "stats",
81 		.func = &cmd_stats,
82 	},
83 	{
84 		.name = "summary",
85 		.func = &cmd_summary,
86 	},
87 	{
88 		.name = NULL,
89 	},
90 };
91 
92 evtr_t evtr;
93 static char *opt_infile;
94 unsigned evtranalyze_debug;
95 
96 static
97 void
98 printd_set_flags(const char *str, unsigned int *flags)
99 {
100 	/*
101 	 * This is suboptimal as we don't detect
102 	 * invalid flags.
103 	 */
104 	for (; *str; ++str) {
105 		if ('A' == *str) {
106 			*flags = -1;
107 			return;
108 		}
109 		if (!islower(*str))
110 			err(2, "invalid debug flag %c\n", *str);
111 		*flags |= 1 << (*str - 'a');
112 	}
113 }
114 
115 struct hashentry {
116 	uintptr_t key;
117 	uintptr_t val;
118 	struct hashentry *next;
119 };
120 
121 struct hashtab {
122 	struct hashentry *buckets[NR_BUCKETS];
123 	uintptr_t (*hashfunc)(uintptr_t);
124 	uintptr_t (*cmpfunc)(uintptr_t, uintptr_t);
125 };
126 
127 static int
128 ehash_find(const struct hashtab *tab, uintptr_t key, uintptr_t *val)
129 {
130 	struct hashentry *ent;
131 
132 	for(ent = tab->buckets[tab->hashfunc(key)];
133 	    ent && tab->cmpfunc(ent->key, key);
134 	    ent = ent->next);
135 
136 	if (!ent)
137 		return !0;
138 	*val = ent->val;
139 	return 0;
140 }
141 
142 static struct hashentry *
143 ehash_insert(struct hashtab *tab, uintptr_t key, uintptr_t val)
144 {
145 	struct hashentry *ent;
146 	int hsh;
147 
148 	if (!(ent = malloc(sizeof(*ent)))) {
149 		fprintf(stderr, "out of memory\n");
150 		return NULL;
151 	}
152 	hsh = tab->hashfunc(key);
153 	ent->next = tab->buckets[hsh];
154 	ent->key = key;
155 	ent->val = val;
156 	tab->buckets[hsh] = ent;
157 	return ent;
158 }
159 static
160 int
161 ehash_delete(struct hashtab *tab, uintptr_t key)
162 {
163 	struct hashentry *ent, *prev;
164 
165 	prev = NULL;
166 	for(ent = tab->buckets[tab->hashfunc(key)];
167 	    ent && tab->cmpfunc(ent->key, key);
168 	    prev = ent, ent = ent->next);
169 	if (!ent)
170 		return !0;
171 	if (prev)
172 		prev->next = ent->next;
173 	else
174 		tab->buckets[tab->hashfunc(key)] = ent->next;
175 	free(ent);
176 	return 0;
177 }
178 
179 static
180 uintptr_t
181 cmpfunc_pointer(uintptr_t a, uintptr_t b)
182 {
183 	return b - a;
184 }
185 
186 static
187 uintptr_t
188 hashfunc_pointer(uintptr_t p)
189 {
190 	return p % NR_BUCKETS;
191 }
192 
193 static
194 uintptr_t
195 hashfunc_string(uintptr_t p)
196 {
197 	const char *str = (char *)p;
198         unsigned long hash = 5381;
199         int c;
200 
201         while ((c = *str++))
202             hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
203 	return hash  % NR_BUCKETS;
204 }
205 
206 static struct hashtab *
207 ehash_new(void)
208 {
209 	struct hashtab *tab;
210 	if (!(tab = calloc(sizeof(struct hashtab), 1)))
211 		return tab;
212 	tab->hashfunc = &hashfunc_pointer;
213 	tab->cmpfunc = &cmpfunc_pointer;
214 	return tab;
215 }
216 
217 /* returns 0 if equal */
218 static
219 int
220 cmp_vals(evtr_variable_value_t a, evtr_variable_value_t b)
221 {
222 	if (a->type != b->type)
223 		return !0;
224 	switch (a->type) {
225 	case EVTR_VAL_NIL:
226 		return 0;
227 	case EVTR_VAL_INT:
228 		return !(a->num == b->num);
229 	case EVTR_VAL_STR:
230 		return strcmp(a->str, b->str);
231 	case EVTR_VAL_HASH:
232 		return !0;	/* come on! */
233 	case EVTR_VAL_CTOR:
234 		err(3, "not implemented");
235 	}
236 	err(3, "can't get here");
237 }
238 
239 static
240 uintptr_t
241 cmpfunc_ctor(uintptr_t _a, uintptr_t _b)
242 {
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))
247 		return !0;
248 	vala = TAILQ_FIRST(&vala->ctor.args);
249 	valb = TAILQ_FIRST(&valb->ctor.args);
250 	for (;;) {
251 		if (!vala && !valb)
252 			return 0;
253 		if ((vala && !valb) || (valb && !vala))
254 			return !0;
255 		if (cmp_vals(vala, valb))
256 			return !0;
257 		vala = TAILQ_NEXT(vala, link);
258 		valb = TAILQ_NEXT(valb, link);
259 	}
260 }
261 
262 static
263 uintptr_t
264 hashfunc_ctor(uintptr_t _p)
265 {
266 	evtr_variable_value_t val, ctor_val = (evtr_variable_value_t)_p;
267 	char buf[1024], *p = &buf[0];
268 	size_t len;
269 
270 	p = buf;
271 	assert(ctor_val->type == EVTR_VAL_CTOR);
272 	len = strlcpy(buf, ctor_val->ctor.name, sizeof(buf));
273 	if (len >= sizeof(buf))
274 		goto done;
275 
276 	TAILQ_FOREACH(val, &ctor_val->ctor.args, link) {
277 		switch (val->type) {
278 		case EVTR_VAL_NIL:
279 			assert(!"can't happen");
280 			break;
281 		case EVTR_VAL_INT:
282 			len += snprintf(p + len, sizeof(buf) - len - 1,
283 					"%jd", val->num);
284 			break;
285 		case EVTR_VAL_STR:
286 			len = strlcat(p, val->str, sizeof(buf));
287 			break;
288 		case EVTR_VAL_HASH:
289 			break;	/* come on! */
290 		case EVTR_VAL_CTOR:
291 			err(3, "not implemented");
292 		}
293 		if (len >= (sizeof(buf) - 1))
294 			break;
295 	}
296 done:
297 	buf[sizeof(buf) - 1] = '\0';
298 	return hashfunc_string((uintptr_t)buf);
299 }
300 
301 typedef struct vector {
302 	uintmax_t *vals;
303 	int used;
304 	int allocated;
305 } *vector_t;
306 
307 static vector_t
308 vector_new(void)
309 {
310 	vector_t v;
311 	if (!(v = malloc(sizeof(*v))))
312 		return v;
313 	v->allocated = 2;
314 	if (!(v->vals = malloc(v->allocated * sizeof(v->vals[0])))) {
315 		free(v);
316 		return NULL;
317 	}
318 	v->allocated =
319 	v->used = 0;
320 	return v;
321 }
322 
323 static
324 void
325 vector_push(vector_t v, uintmax_t val)
326 {
327 	uintmax_t *tmp;
328 	if (v->used == v->allocated) {
329 		tmp = realloc(v->vals, 2 * v->allocated * sizeof(v->vals[0]));
330 		if (!tmp)
331 			err(1, "out of memory");
332 		v->vals = tmp;
333 		v->allocated *= 2;
334 	}
335 	v->vals[v->used++] = val;
336 }
337 
338 static
339 void
340 vector_destroy(vector_t v)
341 {
342 	free(v->vals);
343 	free(v);
344 }
345 
346 static
347 int
348 vector_nelems(vector_t v)
349 {
350 	return v->used;
351 }
352 
353 #define vector_foreach(v, val, i)					\
354 	for (i = 0, val = v->vals[0]; i < v->used; val = v->vals[++i])
355 
356 static
357 double
358 stddev(vector_t v, double avg)
359 {
360 	uintmax_t val;
361 	int i;
362 	double diff, sqr_sum = 0.0;
363 
364 	if (vector_nelems(v) < 2)
365 		return 1 / 0.0;
366 	vector_foreach(v, val, i) {
367 		diff = val - avg;
368 		sqr_sum += diff * diff;
369 	}
370 	return sqrt(sqr_sum / (vector_nelems(v) - 1));
371 }
372 
373 static
374 void
375 usage(void)
376 {
377 	fprintf(stderr, "bad usage :P\n");
378 	exit(2);
379 }
380 
381 static
382 void
383 rows_init(struct rows *rows, int n, double height, double perc)
384 {
385 	double row_h;
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));
392 }
393 
394 static
395 void
396 rows_n(struct rows *rows, int n, double *y, double *height)
397 {
398 	*y = n * rows->row_increment + rows->row_off;
399 	*height = rows->row_increment - 2 * rows->row_off;
400 }
401 
402 /*
403  * Which fontsize to use so that the string fits in the
404  * given rect.
405  */
406 static
407 double
408 fontsize_for_rect(double width, double height, int textlen)
409 {
410 	double wpc, maxh;
411 	/*
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.
415 	 *
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.
419 	 */
420 	/* available width per character */
421 	wpc = width / textlen;
422 	/*
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.
426 	 */
427 #define GLYPH_HIGHT_TO_WIDTH 1.5
428 	maxh = GLYPH_HIGHT_TO_WIDTH * wpc * 0.9;
429 	if (height > maxh) {
430 		height = maxh;
431 	} else if (height < 0.01) {
432 		height = 0.01;
433 	} else {
434 		/* rounding (XXX: make cheaper)*/
435 		height = log(height);
436 		height = round(height);
437 		height = exp(height);
438 	}
439 	return height;
440 }
441 
442 struct pass_hook {
443 	void (*pre)(void *);
444 	void (*event)(void *, evtr_event_t);
445 	void (*post)(void *);
446 	void *data;
447 	struct evtr_filter *filts;
448 	int nfilts;
449 };
450 
451 struct thread_info {
452 	uint64_t runtime;
453 };
454 
455 struct ts_interval {
456 	uint64_t start;
457 	uint64_t end;
458 };
459 
460 struct td_switch_ctx {
461 	svg_document_t svg;
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;
468 	double width;
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;
474 	struct cpu_table {
475 		struct cpu *cpus;
476 		int ncpus;
477 	} cputab;
478 	struct evtr_thread **top_threads;
479 	int nr_top_threads;
480 	double thread_rows_yoff;
481 };
482 
483 struct cpu {
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;
489 	double freq;
490 	uintmax_t evcnt;
491 };
492 
493 static
494 void
495 do_pass(struct pass_hook *hooks, int nhooks)
496 {
497 	struct evtr_filter *filts = NULL;
498 	int nfilts = 0, i;
499 	struct evtr_query *q;
500 	struct evtr_event ev;
501 
502 	for (i = 0; i < nhooks; ++i) {
503 		struct pass_hook *h = &hooks[i];
504 		if (h->pre)
505 			h->pre(h->data);
506 		if (h->nfilts > 0) {
507 			filts = realloc(filts, (nfilts + h->nfilts) *
508 					sizeof(struct evtr_filter));
509 			if (!filts)
510 				err(1, "Out of memory");
511 			memcpy(filts + nfilts, h->filts,
512 			       h->nfilts * sizeof(struct evtr_filter));
513 			nfilts += h->nfilts;
514 		}
515 	}
516 	q = evtr_query_init(evtr, filts, nfilts);
517 	if (!q)
518 		err(1, "Can't initialize query\n");
519 	while(!evtr_query_next(q, &ev)) {
520 		for (i = 0; i < nhooks; ++i) {
521 			if (hooks[i].event)
522 				hooks[i].event(hooks[i].data, &ev);
523 		}
524 	}
525 	if (evtr_query_error(q)) {
526 		err(1, evtr_query_errmsg(q));
527 	}
528 	evtr_query_destroy(q);
529 
530 	for (i = 0; i < nhooks; ++i) {
531 		if (hooks[i].post)
532 			hooks[i].post(hooks[i].data);
533 	}
534 	if (evtr_rewind(evtr))
535 		err(1, "Can't rewind event stream\n");
536 }
537 
538 static
539 void
540 draw_thread_run(struct td_switch_ctx *ctx, struct cpu *c, evtr_event_t ev, int row)
541 {
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);
548 }
549 
550 static
551 void
552 draw_ctx_switch(struct td_switch_ctx *ctx, struct cpu *c, evtr_event_t ev)
553 {
554 	struct svg_transform textrot;
555 	char comm[100];
556 	double x, w, fs, y, height;
557 	int textlen;
558 
559 	assert(ctx->xscale > 0.0);
560 	if (!c->ts)
561 		return;
562 	/* distance to previous context switch */
563 	w = (ev->ts - c->ts) * ctx->xscale;
564 	x = (ev->ts - ctx->firstlast.start) * ctx->xscale;
565 	if ((x - w) < 0) {
566 		fprintf(stderr, "(%ju - %ju) * %.20lf\n",
567 			(uintmax_t)ev->ts,
568 			(uintmax_t)ctx->firstlast.start, ctx->xscale);
569 		abort();
570 	}
571 
572 	rows_n(ctx->cpu_rows, c->i, &y, &height);
573 	assert(!isnan(y));
574 	assert(!isnan(height));
575 
576 	svg_rect_draw(ctx->svg, ctx->cpu_sw_rect, x - w, y, w, height);
577 
578 	/*
579 	 * Draw the text label describing the thread we
580 	 * switched out of.
581 	 */
582 	textrot.tx = x - w;
583 	textrot.ty = y;
584 	textrot.sx = 1.0;
585 	textrot.sy = 1.0;
586 	textrot.rot = 90.0;
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';
593 	/*
594 	 * Note the width and hight are exchanged because
595 	 * the bounding rectangle is rotated by 90 degrees.
596 	 */
597 	fs = fontsize_for_rect(height, w, textlen);
598 	svg_text_draw(ctx->svg, ctx->thread_label, &textrot, comm,
599 		      fs);
600 }
601 
602 
603 /*
604  * The stats for ntd have changed, update ->top_threads
605  */
606 static
607 void
608 top_threads_update(struct td_switch_ctx *ctx, struct evtr_thread *ntd)
609 {
610 	struct thread_info *tdi = ntd->userdata;
611 	int i, j;
612 	for (i = 0; i < ctx->nr_top_threads; ++i) {
613 		struct evtr_thread *td = ctx->top_threads[i];
614 		if (td == ntd) {
615 			/*
616 			 * ntd is already in top_threads and it is at
617 			 * the correct ranking
618 			 */
619 			break;
620 		}
621 		if (!td) {
622 			/* empty slot -- just insert our thread */
623 			ctx->top_threads[i] = ntd;
624 			break;
625 		}
626 		if (((struct thread_info *)td->userdata)->runtime >=
627 		    tdi->runtime) {
628 			/* this thread ranks higher than we do. Move on */
629 			continue;
630 		}
631 		/*
632 		 * OK, we've found the first thread that we outrank, so we
633 		 * need to replace it w/ our thread.
634 		 */
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;
638 
639 			/* tmp holds the thread we replace */
640 			tmp = ctx->top_threads[j];
641 			ctx->top_threads[j] = td;
642 			if (tmp == ntd) {
643 				/*
644 				 * Our thread was already in the top list,
645 				 * and we just removed the second instance.
646 				 * Nothing more to do.
647 				 */
648 				break;
649 			}
650 			td = tmp;
651 		}
652 		break;
653 	}
654 }
655 
656 static
657 void
658 ctxsw_prepare_event(void *_ctx, evtr_event_t ev)
659 {
660 	struct td_switch_ctx *ctx = _ctx;
661 	struct cpu *c, *cpus = ctx->cputab.cpus;
662 	struct thread_info *tdi;
663 
664 	(void)evtr;
665 	printd(INTV, "test1 (%ju:%ju) : %ju\n",
666 	       (uintmax_t)ctx->interval.start,
667 	       (uintmax_t)ctx->interval.end,
668 	       (uintmax_t)ev->ts);
669 	if ((ev->ts > ctx->interval.end) ||
670 	    (ev->ts < ctx->interval.start))
671 		return;
672 	printd(INTV, "PREPEV on %d\n", ev->cpu);
673 
674 	/* update first/last timestamps */
675 	c = &cpus[ev->cpu];
676 	if (!c->firstlast.start) {
677 		c->firstlast.start = ev->ts;
678 	}
679 	c->firstlast.end = ev->ts;
680 	/*
681 	 * c->td can be null when this is the first ctxsw event we
682 	 * observe for a cpu
683 	 */
684 	if (c->td) {
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;
690 			tdi->runtime = 0;
691 		}
692 		tdi = c->td->userdata;
693 		tdi->runtime += ev->ts - c->ts;
694 		top_threads_update(ctx, c->td);
695 	}
696 
697 	/* Notice that ev->td is the new thread for ctxsw events */
698 	c->td = ev->td;
699 	c->ts = ev->ts;
700 }
701 
702 static
703 void
704 find_first_last_ts(struct cpu_table *cputab, struct ts_interval *fl)
705 {
706 	struct cpu *cpus = &cputab->cpus[0];
707 	int i;
708 
709 	fl->start = -1;
710 	fl->end = 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;
721 		cpus[i].td = NULL;
722 		cpus[i].ts = 0;
723 	}
724 	printd(INTV, "global (%jd, %jd)\n", (uintmax_t)fl->start, (uintmax_t)fl->end);
725 }
726 
727 static
728 void
729 ctxsw_prepare_post(void *_ctx)
730 {
731 	struct td_switch_ctx *ctx = _ctx;
732 
733 	find_first_last_ts(&ctx->cputab, &ctx->firstlast);
734 }
735 
736 static
737 void
738 ctxsw_draw_pre(void *_ctx)
739 {
740 	struct td_switch_ctx *ctx = _ctx;
741 	struct svg_transform textrot;
742 	char comm[100];
743 	double y, height, fs;
744 	int i, textlen;
745 	struct evtr_thread *td;
746 
747 	textrot.tx = 0.0 - 0.2;	/* XXX */
748 	textrot.sx = 1.0;
749 	textrot.sy = 1.0;
750 	textrot.rot = 270.0;
751 
752 	for (i = 0; i < ctx->nr_top_threads; ++i) {
753 		td = ctx->top_threads[i];
754 		if (!td)
755 			break;
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)",
760 				   td->comm, td->id);
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);
765 
766 		textrot.ty = y + ctx->thread_rows_yoff + height;
767 		svg_text_draw(ctx->svg, ctx->thread_label, &textrot,
768 			      comm, fs);
769 	}
770 }
771 
772 static
773 void
774 ctxsw_draw_event(void *_ctx, evtr_event_t ev)
775 {
776 	struct td_switch_ctx *ctx = _ctx;
777 	struct cpu *c = &ctx->cputab.cpus[ev->cpu];
778 	int i;
779 
780 	/*
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.
784 	 */
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))
790 		return;
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])
797 				break;
798 			if (ctx->top_threads[i] == c->td) {
799 				draw_thread_run(ctx, c, ev, i);
800 				break;
801 			}
802 		}
803 		c->td = ev->td;
804 		c->ts = ev->ts;
805 	}
806 }
807 
808 static
809 void
810 cputab_init(struct cpu_table *ct)
811 {
812 	struct cpu *cpus;
813 	double *freqs;
814 	int i;
815 
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");
821 	}
822 	cpus = ct->cpus;
823 	if (!(freqs = malloc(sizeof(double) * ct->ncpus))) {
824 		err(1, "Can't allocate memory\n");
825 	}
826 	if ((i = evtr_cpufreqs(evtr, freqs))) {
827 		warnc(i, "Can't get cpu frequencies\n");
828 		for (i = 0; i < ct->ncpus; ++i) {
829 			freqs[i] = -1.0;
830 		}
831 	}
832 
833 	/* initialize cpu array */
834 	for (i = 0; i < ct->ncpus; ++i) {
835 		cpus[i].td = NULL;
836 		cpus[i].ts = 0;
837 		cpus[i].i = i;
838 		cpus[i].firstlast.start = 0;
839 		cpus[i].firstlast.end = 0;
840 		cpus[i].evcnt = 0;
841 		cpus[i].freq = freqs[i];
842 	}
843 	free(freqs);
844 }
845 
846 static
847 void
848 parse_interval(const char *_str, struct ts_interval *ts,
849 	       struct cpu_table *cputab)
850 {
851 	double s, e, freq;
852 	const char *str = _str + 1;
853 
854 	if ('c' == *_str) {	/* cycles */
855 		if (sscanf(str, "%" SCNu64 ":%" SCNu64,
856 			   &ts->start,
857 			   &ts->end) == 2)
858 			return;
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 */
863 			if (freq < 0.0) {
864 				fprintf(stderr, "No frequency information"
865 					" available\n");
866 				err(2, "Can't convert time to cycles\n");
867 			}
868 			ts->start = s * freq;
869 			ts->end = e * freq;
870 			return;
871 		}
872 	}
873 	fprintf(stderr, "invalid interval format: %s\n", _str);
874 	usage();
875 }
876 
877 
878 static
879 int
880 cmd_svg(int argc, char **argv)
881 {
882 	svg_document_t svg;
883 	int ch;
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] = {
889 		{
890 			.flags = 0,
891 			.cpu = -1,
892 			.ev_type = EVTR_TYPE_PROBE,
893 		},
894 		{
895 			.flags = 0,
896 			.cpu = -1,
897 			.ev_type = EVTR_TYPE_PROBE,
898 		},
899 	};
900 	struct pass_hook ctxsw_prepare = {
901 		.pre = NULL,
902 		.event = ctxsw_prepare_event,
903 		.post = ctxsw_prepare_post,
904 		.data = &td_ctx,
905 		.filts = ctxsw_filts,
906 		.nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
907 	}, ctxsw_draw = {
908 		.pre = ctxsw_draw_pre,
909 		.event = ctxsw_draw_event,
910 		.post = NULL,
911 		.data = &td_ctx,
912 		.filts = ctxsw_filts,
913 		.nfilts = sizeof(ctxsw_filts)/sizeof(ctxsw_filts[0]),
914 	};
915 
916 	/*
917 	 * We are interested in thread switch and preemption
918 	 * events, but we don't use the data directly. Instead
919 	 * we rely on ev->td.
920 	 */
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() */
927 
928 	optind = 0;
929 	optreset = 1;
930 	while ((ch = getopt(argc, argv, "i:o:")) != -1) {
931 		switch (ch) {
932 		case 'i':
933 			parse_interval(optarg, &td_ctx.interval,
934 				       &td_ctx.cputab);
935 			break;
936 		case 'o':
937 			outpath = optarg;
938 			break;
939 		default:
940 			usage();
941 		}
942 
943 	}
944 	argc -= optind;
945 	argv += optind;
946 
947 	height = 200.0;
948 	width = 700.0;
949 	td_ctx.width = width;
950 
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");
956 
957 	/*
958 	 * Create rectangles to use for output.
959 	 */
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);
970 	td_ctx.svg = svg;
971 	td_ctx.xscale = -1.0;
972 	td_ctx.cpu_rows = &cpu_rows;
973 
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,
981 	       td_ctx.xscale);
982 
983 	do_pass(&ctxsw_draw, 1);
984 
985 	svg_document_close(svg);
986 	return 0;
987 }
988 
989 static
990 int
991 cmd_show(int argc, char **argv)
992 {
993 	struct evtr_event ev;
994 	struct evtr_query *q;
995 	struct evtr_filter filt;
996 	struct cpu_table cputab;
997 	double freq;
998 	int ch;
999 	uint64_t last_ts = 0;
1000 
1001 	cputab_init(&cputab);
1002 	/*
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
1006 	 * events as well.
1007 	 *
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
1011 	 * frequencies.
1012 	 */
1013 	freq = cputab.cpus[0].freq;
1014 	freq /= 1000000;	/* we want to print out usecs */
1015 	printd(MISC, "using freq = %lf\n", freq);
1016 	filt.flags = 0;
1017 	filt.cpu = -1;
1018 	filt.ev_type = EVTR_TYPE_PROBE;
1019 	filt.fmt = NULL;
1020 	optind = 0;
1021 	optreset = 1;
1022 	while ((ch = getopt(argc, argv, "f:")) != -1) {
1023 		switch (ch) {
1024 		case 'f':
1025 			filt.fmt = optarg;
1026 			break;
1027 		}
1028 	}
1029 	q = evtr_query_init(evtr, &filt, 1);
1030 	if (!q)
1031 		err(1, "Can't initialize query\n");
1032 	while(!evtr_query_next(q, &ev)) {
1033 		char buf[1024];
1034 
1035 		if (!last_ts)
1036 			last_ts = ev.ts;
1037 		if (freq < 0.0) {
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);
1042 		} else {
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);
1047 		}
1048 		if (ev.fmt) {
1049 			evtr_event_data(&ev, buf, sizeof(buf));
1050 			printf(" !\t%s\n", buf);
1051 		} else {
1052 			printf("\n");
1053 		}
1054 		last_ts = ev.ts;
1055 	}
1056 	if (evtr_query_error(q)) {
1057 		err(1, evtr_query_errmsg(q));
1058 	}
1059 	evtr_query_destroy(q);
1060 	return 0;
1061 }
1062 
1063 struct stats_ops {
1064 	const char *statscmd;
1065 	void *(*prepare)(int, char **, struct evtr_filter *);
1066 	void (*each_event)(void *, evtr_event_t);
1067 	void (*report)(void *);
1068 };
1069 
1070 struct stats_integer_ctx {
1071 	const char *varname;
1072 	struct {
1073 		int plot;
1074 		const char *path;
1075 	} opts;
1076 	void *plotter_ctx;
1077 	struct plotter *plotter;
1078 	plotid_t time_plot;
1079 	uintmax_t sum;
1080 	uintmax_t occurences;
1081 };
1082 
1083 static
1084 void *
1085 stats_integer_prepare(int argc, char **argv, struct evtr_filter *filt)
1086 {
1087 	struct stats_integer_ctx *ctx;
1088 	int ch;
1089 
1090 	if (!(ctx = calloc(1, sizeof(*ctx))))
1091 		return ctx;
1092 
1093 	optind = 0;
1094 	optreset = 1;
1095 	while ((ch = getopt(argc, argv, "p:")) != -1) {
1096 		switch (ch) {
1097 		case 'p':
1098 			ctx->opts.plot = !0;
1099 			ctx->opts.path = optarg;
1100 			break;
1101 		default:
1102 			usage();
1103 		}
1104 	}
1105 	argc -= optind;
1106 	argv += optind;
1107 
1108 	if (argc != 1)
1109 		err(2, "Need exactly one variable");
1110 	ctx->varname = argv[0];
1111 	ctx->sum = ctx->occurences = 0;
1112 	filt->flags = 0;
1113 	filt->cpu = -1;
1114 	filt->ev_type = EVTR_TYPE_STMT;
1115 	filt->var = ctx->varname;
1116 	if (!ctx->opts.plot)
1117 		return ctx;
1118 
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");
1123 
1124 	if ((ctx->time_plot = ctx->plotter->plot_new(ctx->plotter_ctx,
1125 							  PLOT_TYPE_LINE,
1126 							  ctx->varname)) < 0)
1127 		err(1, "can't create histogram");
1128 	return ctx;
1129 }
1130 
1131 static
1132 void
1133 stats_integer_each(void *_ctx, evtr_event_t ev)
1134 {
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,
1139 			ctx->varname);
1140 		return;
1141 	}
1142 	if (ctx->plotter)
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;
1146 	++ctx->occurences;
1147 }
1148 
1149 static
1150 void
1151 stats_integer_report(void *_ctx)
1152 {
1153 	struct stats_integer_ctx *ctx = _ctx;
1154 	printf("median for variable %s is %lf\n", ctx->varname,
1155 	       (double)ctx->sum / ctx->occurences);
1156 	if (ctx->plotter)
1157 		ctx->plotter->plot_finish(ctx->plotter_ctx);
1158 
1159 	free(ctx);
1160 }
1161 
1162 struct stats_completion_ctx {
1163 	struct stats_completion_options {
1164 		int plot;
1165 		const char *path;
1166 	} opts;
1167 	struct plotter *plotter;
1168 	void *plotter_ctx;
1169 	plotid_t durations_plot;
1170 	const char *varname;
1171 	const char *ctor;
1172 	const char *dtor;
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;
1179 	vector_t durations;
1180 };
1181 
1182 struct ctor_data {
1183 	evtr_variable_value_t val;
1184 	uintmax_t ts;
1185 };
1186 
1187 static
1188 struct ctor_data *
1189 ctor_data_new(evtr_event_t ev)
1190 {
1191 	struct ctor_data *cd;
1192 
1193 	if (!(cd = malloc(sizeof(*cd))))
1194 		return cd;
1195 	cd->val = ev->stmt.val;
1196 	cd->ts = ev->ts;
1197 	return cd;
1198 }
1199 
1200 static
1201 void *
1202 stats_completion_prepare(int argc, char **argv, struct evtr_filter *filt)
1203 {
1204 	struct stats_completion_ctx *ctx;
1205 	int ch;
1206 
1207 	if (!(ctx = calloc(1, sizeof(*ctx))))
1208 		return ctx;
1209 
1210 	optind = 0;
1211 	optreset = 1;
1212 	while ((ch = getopt(argc, argv, "p:")) != -1) {
1213 		switch (ch) {
1214 		case 'p':
1215 			ctx->opts.plot = !0;
1216 			ctx->opts.path = optarg;
1217 			break;
1218 		default:
1219 			usage();
1220 		}
1221 	}
1222 	argc -= optind;
1223 	argv += optind;
1224 	if (argc != 3)
1225 		err(2, "need a variable, a constructor and a destructor");
1226 	if (!(ctx->ctors = ehash_new()))
1227 		goto free_ctx;
1228 	ctx->ctors->hashfunc = &hashfunc_ctor;
1229 	ctx->ctors->cmpfunc = &cmpfunc_ctor;
1230 	if (!(ctx->durations = vector_new()))
1231 		goto free_ctors;
1232 	ctx->varname = argv[0];
1233 	ctx->ctor = argv[1];
1234 	ctx->dtor = argv[2];
1235 
1236 	filt->flags = 0;
1237 	filt->cpu = -1;
1238 	filt->ev_type = EVTR_TYPE_STMT;
1239 	filt->var = ctx->varname;
1240 
1241 	if (!ctx->opts.plot)
1242 		return ctx;
1243 
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");
1248 
1249 	if ((ctx->durations_plot = ctx->plotter->plot_new(ctx->plotter_ctx,
1250 							  PLOT_TYPE_HIST,
1251 							  ctx->varname)) < 0)
1252 		err(1, "can't create histogram");
1253 	return ctx;
1254 free_ctors:
1255 	;	/* XXX */
1256 free_ctx:
1257 	free(ctx);
1258 	return NULL;
1259 }
1260 
1261 static
1262 void
1263 stats_completion_each(void *_ctx, evtr_event_t ev)
1264 {
1265 	struct stats_completion_ctx *ctx = _ctx;
1266 	struct ctor_data *cd;
1267 
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,
1271 			ctx->varname);
1272 		return;
1273 	}
1274 	if (!strcmp(ev->stmt.val->ctor.name, ctx->ctor)) {
1275 		uintptr_t v;
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");
1280 		}
1281 		if (!(cd = ctor_data_new(ev)))
1282 			err(1, "out of memory");
1283 		v = (uintptr_t)cd;
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)) {
1288 		uintptr_t v;
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;
1294 			return;
1295 		}
1296 		cd = (struct ctor_data *)v;
1297 		if (cd->ts >= ev->ts) {
1298 			/* XXX:better diagnostic */
1299 			fprintf(stderr, "destructor preceds constructor;"
1300 				" ignored\n");
1301 			ev->stmt.val->ctor.name = tmp;
1302 			return;
1303 		}
1304 		if (ctx->plotter)
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;
1314 	} else {
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);
1318 		return;
1319 	}
1320 }
1321 
1322 static
1323 void
1324 stats_completion_report(void *_ctx)
1325 {
1326 	struct stats_completion_ctx *ctx = _ctx;
1327 	double avg;
1328 
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));
1336 
1337 	if (ctx->plotter)
1338 		ctx->plotter->plot_finish(ctx->plotter_ctx);
1339 	vector_destroy(ctx->durations);
1340 	/* XXX: hash */
1341 	free(ctx);
1342 }
1343 
1344 static struct stats_ops cmd_stat_ops[] = {
1345 	{
1346 		.statscmd = "integer",
1347 		.prepare = &stats_integer_prepare,
1348 		.each_event = &stats_integer_each,
1349 		.report = &stats_integer_report,
1350 	},
1351 	{
1352 		.statscmd = "completion",
1353 		.prepare = &stats_completion_prepare,
1354 		.each_event = &stats_completion_each,
1355 		.report = &stats_completion_report,
1356 	},
1357 	{
1358 		.statscmd = NULL,
1359 	},
1360 };
1361 
1362 static
1363 int
1364 cmd_stats(int argc, char **argv)
1365 {
1366 	struct evtr_event ev;
1367 	struct evtr_query *q;
1368 	struct evtr_filter filt;
1369 	struct cpu_table cputab;
1370 	double freq;
1371 	uint64_t last_ts = 0;
1372 	struct stats_ops *statsops = &cmd_stat_ops[0];
1373 	void *statctx;
1374 
1375 	for (; statsops->statscmd; ++statsops) {
1376 		if (!strcmp(statsops->statscmd, argv[1]))
1377 			break;
1378 	}
1379 	if (!statsops->statscmd)
1380 		err(2, "No such stats type: %s", argv[1]);
1381 
1382 	--argc;
1383 	++argv;
1384 	cputab_init(&cputab);
1385 	/*
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
1389 	 * events as well.
1390 	 *
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
1394 	 * frequencies.
1395 	 */
1396 	freq = cputab.cpus[0].freq;
1397 	freq /= 1000000;	/* we want to print out usecs */
1398 	printd(MISC, "using freq = %lf\n", freq);
1399 
1400 	if (!(statctx = statsops->prepare(argc, argv, &filt)))
1401 		err(1, "Can't allocate stats context");
1402 	q = evtr_query_init(evtr, &filt, 1);
1403 	if (!q)
1404 		err(1, "Can't initialize query");
1405 	while(!evtr_query_next(q, &ev)) {
1406 
1407 		if (!last_ts)
1408 			last_ts = ev.ts;
1409 
1410 		assert(ev.type == EVTR_TYPE_STMT);
1411 		statsops->each_event(statctx, &ev);
1412 		last_ts = ev.ts;
1413 	}
1414 	if (evtr_query_error(q)) {
1415 		err(1, evtr_query_errmsg(q));
1416 	}
1417 	evtr_query_destroy(q);
1418 	statsops->report(statctx);
1419 	return 0;
1420 }
1421 
1422 
1423 static
1424 int
1425 cmd_summary(int argc, char **argv)
1426 {
1427 	struct evtr_filter filt;
1428 	struct evtr_event ev;
1429 	struct evtr_query *q;
1430 	double freq;
1431 	struct cpu_table cputab;
1432 	struct ts_interval global;
1433 	uintmax_t global_evcnt;
1434 	int i;
1435 
1436 	(void)argc;
1437 	(void)argv;
1438 
1439 	cputab_init(&cputab);
1440 	filt.ev_type = EVTR_TYPE_PROBE;
1441 	filt.fmt = NULL;
1442 	filt.flags = 0;
1443 	filt.cpu = -1;
1444 
1445 	q = evtr_query_init(evtr, &filt, 1);
1446 	if (!q)
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;
1452 		++c->evcnt;
1453 		c->firstlast.end = ev.ts;
1454 	}
1455 	if (evtr_query_error(q)) {
1456 		err(1, evtr_query_errmsg(q));
1457 	}
1458 	evtr_query_destroy(q);
1459 
1460 	find_first_last_ts(&cputab, &global);
1461 
1462 	freq = cputab.cpus[0].freq;
1463 	global_evcnt = 0;
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)
1468 		       / freq);
1469 		global_evcnt += c->evcnt;
1470 	}
1471 	printf("Total: %jd events on %d cpus in %.3lf secs\n", global_evcnt,
1472 	       cputab.ncpus, (global.end - global.start) / freq);
1473 	return 0;
1474 }
1475 
1476 
1477 int
1478 main(int argc, char **argv)
1479 {
1480 	int ch;
1481 	FILE *inf;
1482 	struct command *cmd;
1483 	char *tmp;
1484 
1485 	while ((ch = getopt(argc, argv, "f:D:")) != -1) {
1486 		switch (ch) {
1487 		case 'f':
1488 			opt_infile = optarg;
1489 			break;
1490 		case 'D':
1491 			if ((tmp = strchr(optarg, ':'))) {
1492 				*tmp++ = '\0';
1493 				evtr_set_debug(tmp);
1494 			}
1495 			printd_set_flags(optarg, &evtranalyze_debug);
1496 			break;
1497 		default:
1498 			usage();
1499 		}
1500 	}
1501 	argc -= optind;
1502 	argv += optind;
1503 
1504 	if (argc == 0) {
1505 		err(2, "need to specify a command\n");
1506 	}
1507 	if (!opt_infile) {
1508 		err(2, "you need to specify an input file\n");
1509 	} else if (!strcmp(opt_infile, "-")) {
1510 		inf = stdin;
1511 	} else {
1512 		inf = fopen(opt_infile, "r");
1513 		if (!inf) {
1514 			err(2, "Can't open input file\n");
1515 		}
1516 	}
1517 
1518 	if (!(evtr = evtr_open_read(inf))) {
1519 		err(1, "Can't open evtr stream\n");
1520 	}
1521 
1522 
1523 	for (cmd = commands; cmd->name != NULL; ++cmd) {
1524 		if (strcmp(argv[0], cmd->name))
1525 			continue;
1526 		cmd->func(argc, argv);
1527 		break;
1528 	}
1529 	if (!cmd->name) {
1530 		err(2, "no such command: %s\n", argv[0]);
1531 	}
1532 
1533 	evtr_close(evtr);
1534 	return 0;
1535 }
1536