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, "%s", 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 char *tmpbuf; 1035 1036 if (!last_ts) 1037 last_ts = ev.ts; 1038 1039 tmpbuf = strdup(ev.file); 1040 1041 if (freq < 0.0) { 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); 1046 } else { 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); 1051 } 1052 free(tmpbuf); 1053 if (ev.fmt) { 1054 evtr_event_data(&ev, buf, sizeof(buf)); 1055 printf(" !\t%s\n", buf); 1056 } else { 1057 printf("\n"); 1058 } 1059 last_ts = ev.ts; 1060 } 1061 if (evtr_query_error(q)) { 1062 err(1, "%s", evtr_query_errmsg(q)); 1063 } 1064 evtr_query_destroy(q); 1065 return 0; 1066 } 1067 1068 struct stats_ops { 1069 const char *statscmd; 1070 void *(*prepare)(int, char **, struct evtr_filter *); 1071 void (*each_event)(void *, evtr_event_t); 1072 void (*report)(void *); 1073 }; 1074 1075 struct stats_integer_ctx { 1076 const char *varname; 1077 struct { 1078 int plot; 1079 const char *path; 1080 } opts; 1081 void *plotter_ctx; 1082 struct plotter *plotter; 1083 plotid_t time_plot; 1084 uintmax_t sum; 1085 uintmax_t occurrences; 1086 }; 1087 1088 static 1089 void * 1090 stats_integer_prepare(int argc, char **argv, struct evtr_filter *filt) 1091 { 1092 struct stats_integer_ctx *ctx; 1093 int ch; 1094 1095 if (!(ctx = calloc(1, sizeof(*ctx)))) 1096 return ctx; 1097 1098 optind = 0; 1099 optreset = 1; 1100 while ((ch = getopt(argc, argv, "p:")) != -1) { 1101 switch (ch) { 1102 case 'p': 1103 ctx->opts.plot = !0; 1104 ctx->opts.path = optarg; 1105 break; 1106 default: 1107 usage(); 1108 } 1109 } 1110 argc -= optind; 1111 argv += optind; 1112 1113 if (argc != 1) 1114 err(2, "Need exactly one variable"); 1115 ctx->varname = argv[0]; 1116 ctx->sum = ctx->occurrences = 0; 1117 filt->flags = 0; 1118 filt->cpu = -1; 1119 filt->ev_type = EVTR_TYPE_STMT; 1120 filt->var = ctx->varname; 1121 if (!ctx->opts.plot) 1122 return ctx; 1123 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"); 1128 1129 if ((ctx->time_plot = ctx->plotter->plot_new(ctx->plotter_ctx, 1130 PLOT_TYPE_LINE, 1131 ctx->varname)) < 0) 1132 err(1, "can't create histogram"); 1133 return ctx; 1134 } 1135 1136 static 1137 void 1138 stats_integer_each(void *_ctx, evtr_event_t ev) 1139 { 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, 1144 ctx->varname); 1145 return; 1146 } 1147 if (ctx->plotter) 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; 1151 ++ctx->occurrences; 1152 } 1153 1154 static 1155 void 1156 stats_integer_report(void *_ctx) 1157 { 1158 struct stats_integer_ctx *ctx = _ctx; 1159 printf("median for variable %s is %lf\n", ctx->varname, 1160 (double)ctx->sum / ctx->occurrences); 1161 if (ctx->plotter) 1162 ctx->plotter->plot_finish(ctx->plotter_ctx); 1163 1164 free(ctx); 1165 } 1166 1167 struct stats_completion_ctx { 1168 struct stats_completion_options { 1169 int plot; 1170 const char *path; 1171 } opts; 1172 struct plotter *plotter; 1173 void *plotter_ctx; 1174 plotid_t durations_plot; 1175 const char *varname; 1176 const char *ctor; 1177 const char *dtor; 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; 1184 vector_t durations; 1185 }; 1186 1187 struct ctor_data { 1188 evtr_variable_value_t val; 1189 uintmax_t ts; 1190 }; 1191 1192 static 1193 struct ctor_data * 1194 ctor_data_new(evtr_event_t ev) 1195 { 1196 struct ctor_data *cd; 1197 1198 if (!(cd = malloc(sizeof(*cd)))) 1199 return cd; 1200 cd->val = ev->stmt.val; 1201 cd->ts = ev->ts; 1202 return cd; 1203 } 1204 1205 static 1206 void * 1207 stats_completion_prepare(int argc, char **argv, struct evtr_filter *filt) 1208 { 1209 struct stats_completion_ctx *ctx; 1210 int ch; 1211 1212 if (!(ctx = calloc(1, sizeof(*ctx)))) 1213 return ctx; 1214 1215 optind = 0; 1216 optreset = 1; 1217 while ((ch = getopt(argc, argv, "p:")) != -1) { 1218 switch (ch) { 1219 case 'p': 1220 ctx->opts.plot = !0; 1221 ctx->opts.path = optarg; 1222 break; 1223 default: 1224 usage(); 1225 } 1226 } 1227 argc -= optind; 1228 argv += optind; 1229 if (argc != 3) 1230 err(2, "need a variable, a constructor and a destructor"); 1231 if (!(ctx->ctors = ehash_new())) 1232 goto free_ctx; 1233 ctx->ctors->hashfunc = &hashfunc_ctor; 1234 ctx->ctors->cmpfunc = &cmpfunc_ctor; 1235 if (!(ctx->durations = vector_new())) 1236 goto free_ctors; 1237 ctx->varname = argv[0]; 1238 ctx->ctor = argv[1]; 1239 ctx->dtor = argv[2]; 1240 1241 filt->flags = 0; 1242 filt->cpu = -1; 1243 filt->ev_type = EVTR_TYPE_STMT; 1244 filt->var = ctx->varname; 1245 1246 if (!ctx->opts.plot) 1247 return ctx; 1248 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"); 1253 1254 if ((ctx->durations_plot = ctx->plotter->plot_new(ctx->plotter_ctx, 1255 PLOT_TYPE_HIST, 1256 ctx->varname)) < 0) 1257 err(1, "can't create histogram"); 1258 return ctx; 1259 free_ctors: 1260 ; /* XXX */ 1261 free_ctx: 1262 free(ctx); 1263 return NULL; 1264 } 1265 1266 static 1267 void 1268 stats_completion_each(void *_ctx, evtr_event_t ev) 1269 { 1270 struct stats_completion_ctx *ctx = _ctx; 1271 struct ctor_data *cd; 1272 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, 1276 ctx->varname); 1277 return; 1278 } 1279 if (!strcmp(ev->stmt.val->ctor.name, ctx->ctor)) { 1280 uintptr_t v; 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"); 1285 } 1286 if (!(cd = ctor_data_new(ev))) 1287 err(1, "out of memory"); 1288 v = (uintptr_t)cd; 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)) { 1293 uintptr_t v; 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; 1299 return; 1300 } 1301 cd = (struct ctor_data *)v; 1302 if (cd->ts >= ev->ts) { 1303 /* XXX:better diagnostic */ 1304 fprintf(stderr, "destructor preceds constructor;" 1305 " ignored\n"); 1306 ev->stmt.val->ctor.name = tmp; 1307 return; 1308 } 1309 if (ctx->plotter) 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; 1319 } else { 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); 1323 return; 1324 } 1325 } 1326 1327 static 1328 void 1329 stats_completion_report(void *_ctx) 1330 { 1331 struct stats_completion_ctx *ctx = _ctx; 1332 double avg; 1333 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)); 1341 1342 if (ctx->plotter) 1343 ctx->plotter->plot_finish(ctx->plotter_ctx); 1344 vector_destroy(ctx->durations); 1345 /* XXX: hash */ 1346 free(ctx); 1347 } 1348 1349 static struct stats_ops cmd_stat_ops[] = { 1350 { 1351 .statscmd = "integer", 1352 .prepare = &stats_integer_prepare, 1353 .each_event = &stats_integer_each, 1354 .report = &stats_integer_report, 1355 }, 1356 { 1357 .statscmd = "completion", 1358 .prepare = &stats_completion_prepare, 1359 .each_event = &stats_completion_each, 1360 .report = &stats_completion_report, 1361 }, 1362 { 1363 .statscmd = NULL, 1364 }, 1365 }; 1366 1367 static 1368 int 1369 cmd_stats(int argc, char **argv) 1370 { 1371 struct evtr_event ev; 1372 struct evtr_query *q; 1373 struct evtr_filter filt; 1374 struct cpu_table cputab; 1375 double freq; 1376 uint64_t last_ts = 0; 1377 struct stats_ops *statsops = &cmd_stat_ops[0]; 1378 void *statctx; 1379 1380 for (; statsops->statscmd; ++statsops) { 1381 if (!strcmp(statsops->statscmd, argv[1])) 1382 break; 1383 } 1384 if (!statsops->statscmd) 1385 err(2, "No such stats type: %s", argv[1]); 1386 1387 --argc; 1388 ++argv; 1389 cputab_init(&cputab); 1390 /* 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 1394 * events as well. 1395 * 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 1399 * frequencies. 1400 */ 1401 freq = cputab.cpus[0].freq; 1402 freq /= 1000000; /* we want to print out usecs */ 1403 printd(MISC, "using freq = %lf\n", freq); 1404 1405 if (!(statctx = statsops->prepare(argc, argv, &filt))) 1406 err(1, "Can't allocate stats context"); 1407 q = evtr_query_init(evtr, &filt, 1); 1408 if (!q) 1409 err(1, "Can't initialize query"); 1410 while(!evtr_query_next(q, &ev)) { 1411 1412 if (!last_ts) 1413 last_ts = ev.ts; 1414 1415 assert(ev.type == EVTR_TYPE_STMT); 1416 statsops->each_event(statctx, &ev); 1417 last_ts = ev.ts; 1418 } 1419 if (evtr_query_error(q)) { 1420 err(1, "%s", evtr_query_errmsg(q)); 1421 } 1422 evtr_query_destroy(q); 1423 statsops->report(statctx); 1424 return 0; 1425 } 1426 1427 1428 static 1429 int 1430 cmd_summary(int argc, char **argv) 1431 { 1432 struct evtr_filter filt; 1433 struct evtr_event ev; 1434 struct evtr_query *q; 1435 double freq; 1436 struct cpu_table cputab; 1437 struct ts_interval global; 1438 uintmax_t global_evcnt; 1439 int i; 1440 1441 (void)argc; 1442 (void)argv; 1443 1444 cputab_init(&cputab); 1445 filt.ev_type = EVTR_TYPE_PROBE; 1446 filt.fmt = NULL; 1447 filt.flags = 0; 1448 filt.cpu = -1; 1449 1450 q = evtr_query_init(evtr, &filt, 1); 1451 if (!q) 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; 1457 ++c->evcnt; 1458 c->firstlast.end = ev.ts; 1459 } 1460 if (evtr_query_error(q)) { 1461 err(1, "%s", evtr_query_errmsg(q)); 1462 } 1463 evtr_query_destroy(q); 1464 1465 find_first_last_ts(&cputab, &global); 1466 1467 freq = cputab.cpus[0].freq; 1468 global_evcnt = 0; 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) 1473 / freq); 1474 global_evcnt += c->evcnt; 1475 } 1476 printf("Total: %jd events on %d cpus in %.3lf secs\n", global_evcnt, 1477 cputab.ncpus, (global.end - global.start) / freq); 1478 return 0; 1479 } 1480 1481 1482 int 1483 main(int argc, char **argv) 1484 { 1485 int ch; 1486 FILE *inf; 1487 struct command *cmd; 1488 char *tmp; 1489 1490 while ((ch = getopt(argc, argv, "f:D:")) != -1) { 1491 switch (ch) { 1492 case 'f': 1493 opt_infile = optarg; 1494 break; 1495 case 'D': 1496 if ((tmp = strchr(optarg, ':'))) { 1497 *tmp++ = '\0'; 1498 evtr_set_debug(tmp); 1499 } 1500 printd_set_flags(optarg, &evtranalyze_debug); 1501 break; 1502 default: 1503 usage(); 1504 } 1505 } 1506 argc -= optind; 1507 argv += optind; 1508 1509 if (argc == 0) { 1510 err(2, "need to specify a command\n"); 1511 } 1512 if (!opt_infile) { 1513 err(2, "you need to specify an input file\n"); 1514 } else if (!strcmp(opt_infile, "-")) { 1515 inf = stdin; 1516 } else { 1517 inf = fopen(opt_infile, "r"); 1518 if (!inf) { 1519 err(2, "Can't open input file\n"); 1520 } 1521 } 1522 1523 if (!(evtr = evtr_open_read(inf))) { 1524 err(1, "Can't open evtr stream\n"); 1525 } 1526 1527 1528 for (cmd = commands; cmd->name != NULL; ++cmd) { 1529 if (strcmp(argv[0], cmd->name)) 1530 continue; 1531 cmd->func(argc, argv); 1532 break; 1533 } 1534 if (!cmd->name) { 1535 err(2, "no such command: %s\n", argv[0]); 1536 } 1537 1538 evtr_close(evtr); 1539 return 0; 1540 } 1541