1 /* 2 * builtin-diff.c 3 * 4 * Builtin diff command: Analyze two perf.data input files, look up and read 5 * DSOs and symbol information, sort them and produce a diff. 6 */ 7 #include "builtin.h" 8 9 #include "util/debug.h" 10 #include "util/event.h" 11 #include "util/hist.h" 12 #include "util/evsel.h" 13 #include "util/evlist.h" 14 #include "util/session.h" 15 #include "util/tool.h" 16 #include "util/sort.h" 17 #include "util/symbol.h" 18 #include "util/util.h" 19 20 #include <stdlib.h> 21 22 static char const *input_old = "perf.data.old", 23 *input_new = "perf.data"; 24 static char diff__default_sort_order[] = "dso,symbol"; 25 static bool force; 26 static bool show_displacement; 27 static bool show_period; 28 static bool show_formula; 29 static bool show_baseline_only; 30 static bool sort_compute; 31 32 static s64 compute_wdiff_w1; 33 static s64 compute_wdiff_w2; 34 35 enum { 36 COMPUTE_DELTA, 37 COMPUTE_RATIO, 38 COMPUTE_WEIGHTED_DIFF, 39 COMPUTE_MAX, 40 }; 41 42 const char *compute_names[COMPUTE_MAX] = { 43 [COMPUTE_DELTA] = "delta", 44 [COMPUTE_RATIO] = "ratio", 45 [COMPUTE_WEIGHTED_DIFF] = "wdiff", 46 }; 47 48 static int compute; 49 50 static int setup_compute_opt_wdiff(char *opt) 51 { 52 char *w1_str = opt; 53 char *w2_str; 54 55 int ret = -EINVAL; 56 57 if (!opt) 58 goto out; 59 60 w2_str = strchr(opt, ','); 61 if (!w2_str) 62 goto out; 63 64 *w2_str++ = 0x0; 65 if (!*w2_str) 66 goto out; 67 68 compute_wdiff_w1 = strtol(w1_str, NULL, 10); 69 compute_wdiff_w2 = strtol(w2_str, NULL, 10); 70 71 if (!compute_wdiff_w1 || !compute_wdiff_w2) 72 goto out; 73 74 pr_debug("compute wdiff w1(%" PRId64 ") w2(%" PRId64 ")\n", 75 compute_wdiff_w1, compute_wdiff_w2); 76 77 ret = 0; 78 79 out: 80 if (ret) 81 pr_err("Failed: wrong weight data, use 'wdiff:w1,w2'\n"); 82 83 return ret; 84 } 85 86 static int setup_compute_opt(char *opt) 87 { 88 if (compute == COMPUTE_WEIGHTED_DIFF) 89 return setup_compute_opt_wdiff(opt); 90 91 if (opt) { 92 pr_err("Failed: extra option specified '%s'", opt); 93 return -EINVAL; 94 } 95 96 return 0; 97 } 98 99 static int setup_compute(const struct option *opt, const char *str, 100 int unset __maybe_unused) 101 { 102 int *cp = (int *) opt->value; 103 char *cstr = (char *) str; 104 char buf[50]; 105 unsigned i; 106 char *option; 107 108 if (!str) { 109 *cp = COMPUTE_DELTA; 110 return 0; 111 } 112 113 if (*str == '+') { 114 sort_compute = true; 115 cstr = (char *) ++str; 116 if (!*str) 117 return 0; 118 } 119 120 option = strchr(str, ':'); 121 if (option) { 122 unsigned len = option++ - str; 123 124 /* 125 * The str data are not writeable, so we need 126 * to use another buffer. 127 */ 128 129 /* No option value is longer. */ 130 if (len >= sizeof(buf)) 131 return -EINVAL; 132 133 strncpy(buf, str, len); 134 buf[len] = 0x0; 135 cstr = buf; 136 } 137 138 for (i = 0; i < COMPUTE_MAX; i++) 139 if (!strcmp(cstr, compute_names[i])) { 140 *cp = i; 141 return setup_compute_opt(option); 142 } 143 144 pr_err("Failed: '%s' is not computation method " 145 "(use 'delta','ratio' or 'wdiff')\n", str); 146 return -EINVAL; 147 } 148 149 static double get_period_percent(struct hist_entry *he, u64 period) 150 { 151 u64 total = he->hists->stats.total_period; 152 return (period * 100.0) / total; 153 } 154 155 double perf_diff__compute_delta(struct hist_entry *he) 156 { 157 struct hist_entry *pair = hist_entry__next_pair(he); 158 double new_percent = get_period_percent(he, he->stat.period); 159 double old_percent = pair ? get_period_percent(pair, pair->stat.period) : 0.0; 160 161 he->diff.period_ratio_delta = new_percent - old_percent; 162 he->diff.computed = true; 163 return he->diff.period_ratio_delta; 164 } 165 166 double perf_diff__compute_ratio(struct hist_entry *he) 167 { 168 struct hist_entry *pair = hist_entry__next_pair(he); 169 double new_period = he->stat.period; 170 double old_period = pair ? pair->stat.period : 0; 171 172 he->diff.computed = true; 173 he->diff.period_ratio = pair ? (new_period / old_period) : 0; 174 return he->diff.period_ratio; 175 } 176 177 s64 perf_diff__compute_wdiff(struct hist_entry *he) 178 { 179 struct hist_entry *pair = hist_entry__next_pair(he); 180 u64 new_period = he->stat.period; 181 u64 old_period = pair ? pair->stat.period : 0; 182 183 he->diff.computed = true; 184 185 if (!pair) 186 he->diff.wdiff = 0; 187 else 188 he->diff.wdiff = new_period * compute_wdiff_w2 - 189 old_period * compute_wdiff_w1; 190 191 return he->diff.wdiff; 192 } 193 194 static int formula_delta(struct hist_entry *he, char *buf, size_t size) 195 { 196 struct hist_entry *pair = hist_entry__next_pair(he); 197 198 if (!pair) 199 return -1; 200 201 return scnprintf(buf, size, 202 "(%" PRIu64 " * 100 / %" PRIu64 ") - " 203 "(%" PRIu64 " * 100 / %" PRIu64 ")", 204 he->stat.period, he->hists->stats.total_period, 205 pair->stat.period, pair->hists->stats.total_period); 206 } 207 208 static int formula_ratio(struct hist_entry *he, char *buf, size_t size) 209 { 210 struct hist_entry *pair = hist_entry__next_pair(he); 211 double new_period = he->stat.period; 212 double old_period = pair ? pair->stat.period : 0; 213 214 if (!pair) 215 return -1; 216 217 return scnprintf(buf, size, "%.0F / %.0F", new_period, old_period); 218 } 219 220 static int formula_wdiff(struct hist_entry *he, char *buf, size_t size) 221 { 222 struct hist_entry *pair = hist_entry__next_pair(he); 223 u64 new_period = he->stat.period; 224 u64 old_period = pair ? pair->stat.period : 0; 225 226 if (!pair) 227 return -1; 228 229 return scnprintf(buf, size, 230 "(%" PRIu64 " * " "%" PRId64 ") - (%" PRIu64 " * " "%" PRId64 ")", 231 new_period, compute_wdiff_w2, old_period, compute_wdiff_w1); 232 } 233 234 int perf_diff__formula(char *buf, size_t size, struct hist_entry *he) 235 { 236 switch (compute) { 237 case COMPUTE_DELTA: 238 return formula_delta(he, buf, size); 239 case COMPUTE_RATIO: 240 return formula_ratio(he, buf, size); 241 case COMPUTE_WEIGHTED_DIFF: 242 return formula_wdiff(he, buf, size); 243 default: 244 BUG_ON(1); 245 } 246 247 return -1; 248 } 249 250 static int hists__add_entry(struct hists *self, 251 struct addr_location *al, u64 period) 252 { 253 if (__hists__add_entry(self, al, NULL, period) != NULL) 254 return 0; 255 return -ENOMEM; 256 } 257 258 static int diff__process_sample_event(struct perf_tool *tool __maybe_unused, 259 union perf_event *event, 260 struct perf_sample *sample, 261 struct perf_evsel *evsel, 262 struct machine *machine) 263 { 264 struct addr_location al; 265 266 if (perf_event__preprocess_sample(event, machine, &al, sample, NULL) < 0) { 267 pr_warning("problem processing %d event, skipping it.\n", 268 event->header.type); 269 return -1; 270 } 271 272 if (al.filtered) 273 return 0; 274 275 if (hists__add_entry(&evsel->hists, &al, sample->period)) { 276 pr_warning("problem incrementing symbol period, skipping event\n"); 277 return -1; 278 } 279 280 evsel->hists.stats.total_period += sample->period; 281 return 0; 282 } 283 284 static struct perf_tool tool = { 285 .sample = diff__process_sample_event, 286 .mmap = perf_event__process_mmap, 287 .comm = perf_event__process_comm, 288 .exit = perf_event__process_exit, 289 .fork = perf_event__process_fork, 290 .lost = perf_event__process_lost, 291 .ordered_samples = true, 292 .ordering_requires_timestamps = true, 293 }; 294 295 static void insert_hist_entry_by_name(struct rb_root *root, 296 struct hist_entry *he) 297 { 298 struct rb_node **p = &root->rb_node; 299 struct rb_node *parent = NULL; 300 struct hist_entry *iter; 301 302 while (*p != NULL) { 303 parent = *p; 304 iter = rb_entry(parent, struct hist_entry, rb_node); 305 if (hist_entry__cmp(he, iter) < 0) 306 p = &(*p)->rb_left; 307 else 308 p = &(*p)->rb_right; 309 } 310 311 rb_link_node(&he->rb_node, parent, p); 312 rb_insert_color(&he->rb_node, root); 313 } 314 315 static void hists__name_resort(struct hists *self, bool sort) 316 { 317 unsigned long position = 1; 318 struct rb_root tmp = RB_ROOT; 319 struct rb_node *next = rb_first(&self->entries); 320 321 while (next != NULL) { 322 struct hist_entry *n = rb_entry(next, struct hist_entry, rb_node); 323 324 next = rb_next(&n->rb_node); 325 n->position = position++; 326 327 if (sort) { 328 rb_erase(&n->rb_node, &self->entries); 329 insert_hist_entry_by_name(&tmp, n); 330 } 331 } 332 333 if (sort) 334 self->entries = tmp; 335 } 336 337 static struct perf_evsel *evsel_match(struct perf_evsel *evsel, 338 struct perf_evlist *evlist) 339 { 340 struct perf_evsel *e; 341 342 list_for_each_entry(e, &evlist->entries, node) 343 if (perf_evsel__match2(evsel, e)) 344 return e; 345 346 return NULL; 347 } 348 349 static void perf_evlist__resort_hists(struct perf_evlist *evlist, bool name) 350 { 351 struct perf_evsel *evsel; 352 353 list_for_each_entry(evsel, &evlist->entries, node) { 354 struct hists *hists = &evsel->hists; 355 356 hists__output_resort(hists); 357 358 /* 359 * The hists__name_resort only sets possition 360 * if name is false. 361 */ 362 if (name || ((!name) && show_displacement)) 363 hists__name_resort(hists, name); 364 } 365 } 366 367 static void hists__baseline_only(struct hists *hists) 368 { 369 struct rb_node *next = rb_first(&hists->entries); 370 371 while (next != NULL) { 372 struct hist_entry *he = rb_entry(next, struct hist_entry, rb_node); 373 374 next = rb_next(&he->rb_node); 375 if (!hist_entry__next_pair(he)) { 376 rb_erase(&he->rb_node, &hists->entries); 377 hist_entry__free(he); 378 } 379 } 380 } 381 382 static void hists__precompute(struct hists *hists) 383 { 384 struct rb_node *next = rb_first(&hists->entries); 385 386 while (next != NULL) { 387 struct hist_entry *he = rb_entry(next, struct hist_entry, rb_node); 388 389 next = rb_next(&he->rb_node); 390 391 switch (compute) { 392 case COMPUTE_DELTA: 393 perf_diff__compute_delta(he); 394 break; 395 case COMPUTE_RATIO: 396 perf_diff__compute_ratio(he); 397 break; 398 case COMPUTE_WEIGHTED_DIFF: 399 perf_diff__compute_wdiff(he); 400 break; 401 default: 402 BUG_ON(1); 403 } 404 } 405 } 406 407 static int64_t cmp_doubles(double l, double r) 408 { 409 if (l > r) 410 return -1; 411 else if (l < r) 412 return 1; 413 else 414 return 0; 415 } 416 417 static int64_t 418 hist_entry__cmp_compute(struct hist_entry *left, struct hist_entry *right, 419 int c) 420 { 421 switch (c) { 422 case COMPUTE_DELTA: 423 { 424 double l = left->diff.period_ratio_delta; 425 double r = right->diff.period_ratio_delta; 426 427 return cmp_doubles(l, r); 428 } 429 case COMPUTE_RATIO: 430 { 431 double l = left->diff.period_ratio; 432 double r = right->diff.period_ratio; 433 434 return cmp_doubles(l, r); 435 } 436 case COMPUTE_WEIGHTED_DIFF: 437 { 438 s64 l = left->diff.wdiff; 439 s64 r = right->diff.wdiff; 440 441 return r - l; 442 } 443 default: 444 BUG_ON(1); 445 } 446 447 return 0; 448 } 449 450 static void insert_hist_entry_by_compute(struct rb_root *root, 451 struct hist_entry *he, 452 int c) 453 { 454 struct rb_node **p = &root->rb_node; 455 struct rb_node *parent = NULL; 456 struct hist_entry *iter; 457 458 while (*p != NULL) { 459 parent = *p; 460 iter = rb_entry(parent, struct hist_entry, rb_node); 461 if (hist_entry__cmp_compute(he, iter, c) < 0) 462 p = &(*p)->rb_left; 463 else 464 p = &(*p)->rb_right; 465 } 466 467 rb_link_node(&he->rb_node, parent, p); 468 rb_insert_color(&he->rb_node, root); 469 } 470 471 static void hists__compute_resort(struct hists *hists) 472 { 473 struct rb_root tmp = RB_ROOT; 474 struct rb_node *next = rb_first(&hists->entries); 475 476 while (next != NULL) { 477 struct hist_entry *he = rb_entry(next, struct hist_entry, rb_node); 478 479 next = rb_next(&he->rb_node); 480 481 rb_erase(&he->rb_node, &hists->entries); 482 insert_hist_entry_by_compute(&tmp, he, compute); 483 } 484 485 hists->entries = tmp; 486 } 487 488 static void hists__process(struct hists *old, struct hists *new) 489 { 490 hists__match(new, old); 491 492 if (show_baseline_only) 493 hists__baseline_only(new); 494 else 495 hists__link(new, old); 496 497 if (sort_compute) { 498 hists__precompute(new); 499 hists__compute_resort(new); 500 } 501 502 hists__fprintf(new, true, 0, 0, stdout); 503 } 504 505 static int __cmd_diff(void) 506 { 507 int ret, i; 508 #define older (session[0]) 509 #define newer (session[1]) 510 struct perf_session *session[2]; 511 struct perf_evlist *evlist_new, *evlist_old; 512 struct perf_evsel *evsel; 513 bool first = true; 514 515 older = perf_session__new(input_old, O_RDONLY, force, false, 516 &tool); 517 newer = perf_session__new(input_new, O_RDONLY, force, false, 518 &tool); 519 if (session[0] == NULL || session[1] == NULL) 520 return -ENOMEM; 521 522 for (i = 0; i < 2; ++i) { 523 ret = perf_session__process_events(session[i], &tool); 524 if (ret) 525 goto out_delete; 526 } 527 528 evlist_old = older->evlist; 529 evlist_new = newer->evlist; 530 531 perf_evlist__resort_hists(evlist_old, true); 532 perf_evlist__resort_hists(evlist_new, false); 533 534 list_for_each_entry(evsel, &evlist_new->entries, node) { 535 struct perf_evsel *evsel_old; 536 537 evsel_old = evsel_match(evsel, evlist_old); 538 if (!evsel_old) 539 continue; 540 541 fprintf(stdout, "%s# Event '%s'\n#\n", first ? "" : "\n", 542 perf_evsel__name(evsel)); 543 544 first = false; 545 546 hists__process(&evsel_old->hists, &evsel->hists); 547 } 548 549 out_delete: 550 for (i = 0; i < 2; ++i) 551 perf_session__delete(session[i]); 552 return ret; 553 #undef older 554 #undef newer 555 } 556 557 static const char * const diff_usage[] = { 558 "perf diff [<options>] [old_file] [new_file]", 559 NULL, 560 }; 561 562 static const struct option options[] = { 563 OPT_INCR('v', "verbose", &verbose, 564 "be more verbose (show symbol address, etc)"), 565 OPT_BOOLEAN('M', "displacement", &show_displacement, 566 "Show position displacement relative to baseline"), 567 OPT_BOOLEAN('b', "baseline-only", &show_baseline_only, 568 "Show only items with match in baseline"), 569 OPT_CALLBACK('c', "compute", &compute, 570 "delta,ratio,wdiff:w1,w2 (default delta)", 571 "Entries differential computation selection", 572 setup_compute), 573 OPT_BOOLEAN('p', "period", &show_period, 574 "Show period values."), 575 OPT_BOOLEAN('F', "formula", &show_formula, 576 "Show formula."), 577 OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace, 578 "dump raw trace in ASCII"), 579 OPT_BOOLEAN('f', "force", &force, "don't complain, do it"), 580 OPT_BOOLEAN('m', "modules", &symbol_conf.use_modules, 581 "load module symbols - WARNING: use only with -k and LIVE kernel"), 582 OPT_STRING('d', "dsos", &symbol_conf.dso_list_str, "dso[,dso...]", 583 "only consider symbols in these dsos"), 584 OPT_STRING('C', "comms", &symbol_conf.comm_list_str, "comm[,comm...]", 585 "only consider symbols in these comms"), 586 OPT_STRING('S', "symbols", &symbol_conf.sym_list_str, "symbol[,symbol...]", 587 "only consider these symbols"), 588 OPT_STRING('s', "sort", &sort_order, "key[,key2...]", 589 "sort by key(s): pid, comm, dso, symbol, parent"), 590 OPT_STRING('t', "field-separator", &symbol_conf.field_sep, "separator", 591 "separator for columns, no spaces will be added between " 592 "columns '.' is reserved."), 593 OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory", 594 "Look for files with symbols relative to this directory"), 595 OPT_END() 596 }; 597 598 static void ui_init(void) 599 { 600 perf_hpp__init(); 601 602 /* No overhead column. */ 603 perf_hpp__column_enable(PERF_HPP__OVERHEAD, false); 604 605 /* 606 * Display baseline/delta/ratio/displacement/ 607 * formula/periods columns. 608 */ 609 perf_hpp__column_enable(PERF_HPP__BASELINE, true); 610 611 switch (compute) { 612 case COMPUTE_DELTA: 613 perf_hpp__column_enable(PERF_HPP__DELTA, true); 614 break; 615 case COMPUTE_RATIO: 616 perf_hpp__column_enable(PERF_HPP__RATIO, true); 617 break; 618 case COMPUTE_WEIGHTED_DIFF: 619 perf_hpp__column_enable(PERF_HPP__WEIGHTED_DIFF, true); 620 break; 621 default: 622 BUG_ON(1); 623 }; 624 625 if (show_displacement) 626 perf_hpp__column_enable(PERF_HPP__DISPL, true); 627 628 if (show_formula) 629 perf_hpp__column_enable(PERF_HPP__FORMULA, true); 630 631 if (show_period) { 632 perf_hpp__column_enable(PERF_HPP__PERIOD, true); 633 perf_hpp__column_enable(PERF_HPP__PERIOD_BASELINE, true); 634 } 635 } 636 637 int cmd_diff(int argc, const char **argv, const char *prefix __maybe_unused) 638 { 639 sort_order = diff__default_sort_order; 640 argc = parse_options(argc, argv, options, diff_usage, 0); 641 if (argc) { 642 if (argc > 2) 643 usage_with_options(diff_usage, options); 644 if (argc == 2) { 645 input_old = argv[0]; 646 input_new = argv[1]; 647 } else 648 input_new = argv[0]; 649 } else if (symbol_conf.default_guest_vmlinux_name || 650 symbol_conf.default_guest_kallsyms) { 651 input_old = "perf.data.host"; 652 input_new = "perf.data.guest"; 653 } 654 655 symbol_conf.exclude_other = false; 656 if (symbol__init() < 0) 657 return -1; 658 659 ui_init(); 660 661 setup_sorting(diff_usage, options); 662 setup_pager(); 663 664 sort_entry__setup_elide(&sort_dso, symbol_conf.dso_list, "dso", NULL); 665 sort_entry__setup_elide(&sort_comm, symbol_conf.comm_list, "comm", NULL); 666 sort_entry__setup_elide(&sort_sym, symbol_conf.sym_list, "symbol", NULL); 667 668 return __cmd_diff(); 669 } 670