1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * builtin-diff.c 4 * 5 * Builtin diff command: Analyze two perf.data input files, look up and read 6 * DSOs and symbol information, sort them and produce a diff. 7 */ 8 #include "builtin.h" 9 10 #include "util/debug.h" 11 #include "util/event.h" 12 #include "util/hist.h" 13 #include "util/evsel.h" 14 #include "util/evlist.h" 15 #include "util/session.h" 16 #include "util/tool.h" 17 #include "util/sort.h" 18 #include "util/symbol.h" 19 #include "util/util.h" 20 #include "util/data.h" 21 #include "util/config.h" 22 #include "util/time-utils.h" 23 24 #include <errno.h> 25 #include <inttypes.h> 26 #include <stdlib.h> 27 #include <math.h> 28 29 struct perf_diff { 30 struct perf_tool tool; 31 const char *time_str; 32 struct perf_time_interval *ptime_range; 33 int range_size; 34 int range_num; 35 }; 36 37 /* Diff command specific HPP columns. */ 38 enum { 39 PERF_HPP_DIFF__BASELINE, 40 PERF_HPP_DIFF__PERIOD, 41 PERF_HPP_DIFF__PERIOD_BASELINE, 42 PERF_HPP_DIFF__DELTA, 43 PERF_HPP_DIFF__RATIO, 44 PERF_HPP_DIFF__WEIGHTED_DIFF, 45 PERF_HPP_DIFF__FORMULA, 46 PERF_HPP_DIFF__DELTA_ABS, 47 48 PERF_HPP_DIFF__MAX_INDEX 49 }; 50 51 struct diff_hpp_fmt { 52 struct perf_hpp_fmt fmt; 53 int idx; 54 char *header; 55 int header_width; 56 }; 57 58 struct data__file { 59 struct perf_session *session; 60 struct perf_data data; 61 int idx; 62 struct hists *hists; 63 struct diff_hpp_fmt fmt[PERF_HPP_DIFF__MAX_INDEX]; 64 }; 65 66 static struct data__file *data__files; 67 static int data__files_cnt; 68 69 #define data__for_each_file_start(i, d, s) \ 70 for (i = s, d = &data__files[s]; \ 71 i < data__files_cnt; \ 72 i++, d = &data__files[i]) 73 74 #define data__for_each_file(i, d) data__for_each_file_start(i, d, 0) 75 #define data__for_each_file_new(i, d) data__for_each_file_start(i, d, 1) 76 77 static bool force; 78 static bool show_period; 79 static bool show_formula; 80 static bool show_baseline_only; 81 static unsigned int sort_compute = 1; 82 83 static s64 compute_wdiff_w1; 84 static s64 compute_wdiff_w2; 85 86 static const char *cpu_list; 87 static DECLARE_BITMAP(cpu_bitmap, MAX_NR_CPUS); 88 89 enum { 90 COMPUTE_DELTA, 91 COMPUTE_RATIO, 92 COMPUTE_WEIGHTED_DIFF, 93 COMPUTE_DELTA_ABS, 94 COMPUTE_MAX, 95 }; 96 97 const char *compute_names[COMPUTE_MAX] = { 98 [COMPUTE_DELTA] = "delta", 99 [COMPUTE_DELTA_ABS] = "delta-abs", 100 [COMPUTE_RATIO] = "ratio", 101 [COMPUTE_WEIGHTED_DIFF] = "wdiff", 102 }; 103 104 static int compute = COMPUTE_DELTA_ABS; 105 106 static int compute_2_hpp[COMPUTE_MAX] = { 107 [COMPUTE_DELTA] = PERF_HPP_DIFF__DELTA, 108 [COMPUTE_DELTA_ABS] = PERF_HPP_DIFF__DELTA_ABS, 109 [COMPUTE_RATIO] = PERF_HPP_DIFF__RATIO, 110 [COMPUTE_WEIGHTED_DIFF] = PERF_HPP_DIFF__WEIGHTED_DIFF, 111 }; 112 113 #define MAX_COL_WIDTH 70 114 115 static struct header_column { 116 const char *name; 117 int width; 118 } columns[PERF_HPP_DIFF__MAX_INDEX] = { 119 [PERF_HPP_DIFF__BASELINE] = { 120 .name = "Baseline", 121 }, 122 [PERF_HPP_DIFF__PERIOD] = { 123 .name = "Period", 124 .width = 14, 125 }, 126 [PERF_HPP_DIFF__PERIOD_BASELINE] = { 127 .name = "Base period", 128 .width = 14, 129 }, 130 [PERF_HPP_DIFF__DELTA] = { 131 .name = "Delta", 132 .width = 7, 133 }, 134 [PERF_HPP_DIFF__DELTA_ABS] = { 135 .name = "Delta Abs", 136 .width = 7, 137 }, 138 [PERF_HPP_DIFF__RATIO] = { 139 .name = "Ratio", 140 .width = 14, 141 }, 142 [PERF_HPP_DIFF__WEIGHTED_DIFF] = { 143 .name = "Weighted diff", 144 .width = 14, 145 }, 146 [PERF_HPP_DIFF__FORMULA] = { 147 .name = "Formula", 148 .width = MAX_COL_WIDTH, 149 } 150 }; 151 152 static int setup_compute_opt_wdiff(char *opt) 153 { 154 char *w1_str = opt; 155 char *w2_str; 156 157 int ret = -EINVAL; 158 159 if (!opt) 160 goto out; 161 162 w2_str = strchr(opt, ','); 163 if (!w2_str) 164 goto out; 165 166 *w2_str++ = 0x0; 167 if (!*w2_str) 168 goto out; 169 170 compute_wdiff_w1 = strtol(w1_str, NULL, 10); 171 compute_wdiff_w2 = strtol(w2_str, NULL, 10); 172 173 if (!compute_wdiff_w1 || !compute_wdiff_w2) 174 goto out; 175 176 pr_debug("compute wdiff w1(%" PRId64 ") w2(%" PRId64 ")\n", 177 compute_wdiff_w1, compute_wdiff_w2); 178 179 ret = 0; 180 181 out: 182 if (ret) 183 pr_err("Failed: wrong weight data, use 'wdiff:w1,w2'\n"); 184 185 return ret; 186 } 187 188 static int setup_compute_opt(char *opt) 189 { 190 if (compute == COMPUTE_WEIGHTED_DIFF) 191 return setup_compute_opt_wdiff(opt); 192 193 if (opt) { 194 pr_err("Failed: extra option specified '%s'", opt); 195 return -EINVAL; 196 } 197 198 return 0; 199 } 200 201 static int setup_compute(const struct option *opt, const char *str, 202 int unset __maybe_unused) 203 { 204 int *cp = (int *) opt->value; 205 char *cstr = (char *) str; 206 char buf[50]; 207 unsigned i; 208 char *option; 209 210 if (!str) { 211 *cp = COMPUTE_DELTA; 212 return 0; 213 } 214 215 option = strchr(str, ':'); 216 if (option) { 217 unsigned len = option++ - str; 218 219 /* 220 * The str data are not writeable, so we need 221 * to use another buffer. 222 */ 223 224 /* No option value is longer. */ 225 if (len >= sizeof(buf)) 226 return -EINVAL; 227 228 strncpy(buf, str, len); 229 buf[len] = 0x0; 230 cstr = buf; 231 } 232 233 for (i = 0; i < COMPUTE_MAX; i++) 234 if (!strcmp(cstr, compute_names[i])) { 235 *cp = i; 236 return setup_compute_opt(option); 237 } 238 239 pr_err("Failed: '%s' is not computation method " 240 "(use 'delta','ratio' or 'wdiff')\n", str); 241 return -EINVAL; 242 } 243 244 static double period_percent(struct hist_entry *he, u64 period) 245 { 246 u64 total = hists__total_period(he->hists); 247 248 return (period * 100.0) / total; 249 } 250 251 static double compute_delta(struct hist_entry *he, struct hist_entry *pair) 252 { 253 double old_percent = period_percent(he, he->stat.period); 254 double new_percent = period_percent(pair, pair->stat.period); 255 256 pair->diff.period_ratio_delta = new_percent - old_percent; 257 pair->diff.computed = true; 258 return pair->diff.period_ratio_delta; 259 } 260 261 static double compute_ratio(struct hist_entry *he, struct hist_entry *pair) 262 { 263 double old_period = he->stat.period ?: 1; 264 double new_period = pair->stat.period; 265 266 pair->diff.computed = true; 267 pair->diff.period_ratio = new_period / old_period; 268 return pair->diff.period_ratio; 269 } 270 271 static s64 compute_wdiff(struct hist_entry *he, struct hist_entry *pair) 272 { 273 u64 old_period = he->stat.period; 274 u64 new_period = pair->stat.period; 275 276 pair->diff.computed = true; 277 pair->diff.wdiff = new_period * compute_wdiff_w2 - 278 old_period * compute_wdiff_w1; 279 280 return pair->diff.wdiff; 281 } 282 283 static int formula_delta(struct hist_entry *he, struct hist_entry *pair, 284 char *buf, size_t size) 285 { 286 u64 he_total = he->hists->stats.total_period; 287 u64 pair_total = pair->hists->stats.total_period; 288 289 if (symbol_conf.filter_relative) { 290 he_total = he->hists->stats.total_non_filtered_period; 291 pair_total = pair->hists->stats.total_non_filtered_period; 292 } 293 return scnprintf(buf, size, 294 "(%" PRIu64 " * 100 / %" PRIu64 ") - " 295 "(%" PRIu64 " * 100 / %" PRIu64 ")", 296 pair->stat.period, pair_total, 297 he->stat.period, he_total); 298 } 299 300 static int formula_ratio(struct hist_entry *he, struct hist_entry *pair, 301 char *buf, size_t size) 302 { 303 double old_period = he->stat.period; 304 double new_period = pair->stat.period; 305 306 return scnprintf(buf, size, "%.0F / %.0F", new_period, old_period); 307 } 308 309 static int formula_wdiff(struct hist_entry *he, struct hist_entry *pair, 310 char *buf, size_t size) 311 { 312 u64 old_period = he->stat.period; 313 u64 new_period = pair->stat.period; 314 315 return scnprintf(buf, size, 316 "(%" PRIu64 " * " "%" PRId64 ") - (%" PRIu64 " * " "%" PRId64 ")", 317 new_period, compute_wdiff_w2, old_period, compute_wdiff_w1); 318 } 319 320 static int formula_fprintf(struct hist_entry *he, struct hist_entry *pair, 321 char *buf, size_t size) 322 { 323 switch (compute) { 324 case COMPUTE_DELTA: 325 case COMPUTE_DELTA_ABS: 326 return formula_delta(he, pair, buf, size); 327 case COMPUTE_RATIO: 328 return formula_ratio(he, pair, buf, size); 329 case COMPUTE_WEIGHTED_DIFF: 330 return formula_wdiff(he, pair, buf, size); 331 default: 332 BUG_ON(1); 333 } 334 335 return -1; 336 } 337 338 static int diff__process_sample_event(struct perf_tool *tool, 339 union perf_event *event, 340 struct perf_sample *sample, 341 struct perf_evsel *evsel, 342 struct machine *machine) 343 { 344 struct perf_diff *pdiff = container_of(tool, struct perf_diff, tool); 345 struct addr_location al; 346 struct hists *hists = evsel__hists(evsel); 347 int ret = -1; 348 349 if (perf_time__ranges_skip_sample(pdiff->ptime_range, pdiff->range_num, 350 sample->time)) { 351 return 0; 352 } 353 354 if (machine__resolve(machine, &al, sample) < 0) { 355 pr_warning("problem processing %d event, skipping it.\n", 356 event->header.type); 357 return -1; 358 } 359 360 if (cpu_list && !test_bit(sample->cpu, cpu_bitmap)) { 361 ret = 0; 362 goto out_put; 363 } 364 365 if (!hists__add_entry(hists, &al, NULL, NULL, NULL, sample, true)) { 366 pr_warning("problem incrementing symbol period, skipping event\n"); 367 goto out_put; 368 } 369 370 /* 371 * The total_period is updated here before going to the output 372 * tree since normally only the baseline hists will call 373 * hists__output_resort() and precompute needs the total 374 * period in order to sort entries by percentage delta. 375 */ 376 hists->stats.total_period += sample->period; 377 if (!al.filtered) 378 hists->stats.total_non_filtered_period += sample->period; 379 ret = 0; 380 out_put: 381 addr_location__put(&al); 382 return ret; 383 } 384 385 static struct perf_diff pdiff = { 386 .tool = { 387 .sample = diff__process_sample_event, 388 .mmap = perf_event__process_mmap, 389 .mmap2 = perf_event__process_mmap2, 390 .comm = perf_event__process_comm, 391 .exit = perf_event__process_exit, 392 .fork = perf_event__process_fork, 393 .lost = perf_event__process_lost, 394 .namespaces = perf_event__process_namespaces, 395 .ordered_events = true, 396 .ordering_requires_timestamps = true, 397 }, 398 }; 399 400 static struct perf_evsel *evsel_match(struct perf_evsel *evsel, 401 struct perf_evlist *evlist) 402 { 403 struct perf_evsel *e; 404 405 evlist__for_each_entry(evlist, e) { 406 if (perf_evsel__match2(evsel, e)) 407 return e; 408 } 409 410 return NULL; 411 } 412 413 static void perf_evlist__collapse_resort(struct perf_evlist *evlist) 414 { 415 struct perf_evsel *evsel; 416 417 evlist__for_each_entry(evlist, evsel) { 418 struct hists *hists = evsel__hists(evsel); 419 420 hists__collapse_resort(hists, NULL); 421 } 422 } 423 424 static struct data__file *fmt_to_data_file(struct perf_hpp_fmt *fmt) 425 { 426 struct diff_hpp_fmt *dfmt = container_of(fmt, struct diff_hpp_fmt, fmt); 427 void *ptr = dfmt - dfmt->idx; 428 struct data__file *d = container_of(ptr, struct data__file, fmt); 429 430 return d; 431 } 432 433 static struct hist_entry* 434 get_pair_data(struct hist_entry *he, struct data__file *d) 435 { 436 if (hist_entry__has_pairs(he)) { 437 struct hist_entry *pair; 438 439 list_for_each_entry(pair, &he->pairs.head, pairs.node) 440 if (pair->hists == d->hists) 441 return pair; 442 } 443 444 return NULL; 445 } 446 447 static struct hist_entry* 448 get_pair_fmt(struct hist_entry *he, struct diff_hpp_fmt *dfmt) 449 { 450 struct data__file *d = fmt_to_data_file(&dfmt->fmt); 451 452 return get_pair_data(he, d); 453 } 454 455 static void hists__baseline_only(struct hists *hists) 456 { 457 struct rb_root_cached *root; 458 struct rb_node *next; 459 460 if (hists__has(hists, need_collapse)) 461 root = &hists->entries_collapsed; 462 else 463 root = hists->entries_in; 464 465 next = rb_first_cached(root); 466 while (next != NULL) { 467 struct hist_entry *he = rb_entry(next, struct hist_entry, rb_node_in); 468 469 next = rb_next(&he->rb_node_in); 470 if (!hist_entry__next_pair(he)) { 471 rb_erase_cached(&he->rb_node_in, root); 472 hist_entry__delete(he); 473 } 474 } 475 } 476 477 static void hists__precompute(struct hists *hists) 478 { 479 struct rb_root_cached *root; 480 struct rb_node *next; 481 482 if (hists__has(hists, need_collapse)) 483 root = &hists->entries_collapsed; 484 else 485 root = hists->entries_in; 486 487 next = rb_first_cached(root); 488 while (next != NULL) { 489 struct hist_entry *he, *pair; 490 struct data__file *d; 491 int i; 492 493 he = rb_entry(next, struct hist_entry, rb_node_in); 494 next = rb_next(&he->rb_node_in); 495 496 data__for_each_file_new(i, d) { 497 pair = get_pair_data(he, d); 498 if (!pair) 499 continue; 500 501 switch (compute) { 502 case COMPUTE_DELTA: 503 case COMPUTE_DELTA_ABS: 504 compute_delta(he, pair); 505 break; 506 case COMPUTE_RATIO: 507 compute_ratio(he, pair); 508 break; 509 case COMPUTE_WEIGHTED_DIFF: 510 compute_wdiff(he, pair); 511 break; 512 default: 513 BUG_ON(1); 514 } 515 } 516 } 517 } 518 519 static int64_t cmp_doubles(double l, double r) 520 { 521 if (l > r) 522 return -1; 523 else if (l < r) 524 return 1; 525 else 526 return 0; 527 } 528 529 static int64_t 530 __hist_entry__cmp_compute(struct hist_entry *left, struct hist_entry *right, 531 int c) 532 { 533 switch (c) { 534 case COMPUTE_DELTA: 535 { 536 double l = left->diff.period_ratio_delta; 537 double r = right->diff.period_ratio_delta; 538 539 return cmp_doubles(l, r); 540 } 541 case COMPUTE_DELTA_ABS: 542 { 543 double l = fabs(left->diff.period_ratio_delta); 544 double r = fabs(right->diff.period_ratio_delta); 545 546 return cmp_doubles(l, r); 547 } 548 case COMPUTE_RATIO: 549 { 550 double l = left->diff.period_ratio; 551 double r = right->diff.period_ratio; 552 553 return cmp_doubles(l, r); 554 } 555 case COMPUTE_WEIGHTED_DIFF: 556 { 557 s64 l = left->diff.wdiff; 558 s64 r = right->diff.wdiff; 559 560 return r - l; 561 } 562 default: 563 BUG_ON(1); 564 } 565 566 return 0; 567 } 568 569 static int64_t 570 hist_entry__cmp_compute(struct hist_entry *left, struct hist_entry *right, 571 int c, int sort_idx) 572 { 573 bool pairs_left = hist_entry__has_pairs(left); 574 bool pairs_right = hist_entry__has_pairs(right); 575 struct hist_entry *p_right, *p_left; 576 577 if (!pairs_left && !pairs_right) 578 return 0; 579 580 if (!pairs_left || !pairs_right) 581 return pairs_left ? -1 : 1; 582 583 p_left = get_pair_data(left, &data__files[sort_idx]); 584 p_right = get_pair_data(right, &data__files[sort_idx]); 585 586 if (!p_left && !p_right) 587 return 0; 588 589 if (!p_left || !p_right) 590 return p_left ? -1 : 1; 591 592 /* 593 * We have 2 entries of same kind, let's 594 * make the data comparison. 595 */ 596 return __hist_entry__cmp_compute(p_left, p_right, c); 597 } 598 599 static int64_t 600 hist_entry__cmp_compute_idx(struct hist_entry *left, struct hist_entry *right, 601 int c, int sort_idx) 602 { 603 struct hist_entry *p_right, *p_left; 604 605 p_left = get_pair_data(left, &data__files[sort_idx]); 606 p_right = get_pair_data(right, &data__files[sort_idx]); 607 608 if (!p_left && !p_right) 609 return 0; 610 611 if (!p_left || !p_right) 612 return p_left ? -1 : 1; 613 614 if (c != COMPUTE_DELTA && c != COMPUTE_DELTA_ABS) { 615 /* 616 * The delta can be computed without the baseline, but 617 * others are not. Put those entries which have no 618 * values below. 619 */ 620 if (left->dummy && right->dummy) 621 return 0; 622 623 if (left->dummy || right->dummy) 624 return left->dummy ? 1 : -1; 625 } 626 627 return __hist_entry__cmp_compute(p_left, p_right, c); 628 } 629 630 static int64_t 631 hist_entry__cmp_nop(struct perf_hpp_fmt *fmt __maybe_unused, 632 struct hist_entry *left __maybe_unused, 633 struct hist_entry *right __maybe_unused) 634 { 635 return 0; 636 } 637 638 static int64_t 639 hist_entry__cmp_baseline(struct perf_hpp_fmt *fmt __maybe_unused, 640 struct hist_entry *left, struct hist_entry *right) 641 { 642 if (left->stat.period == right->stat.period) 643 return 0; 644 return left->stat.period > right->stat.period ? 1 : -1; 645 } 646 647 static int64_t 648 hist_entry__cmp_delta(struct perf_hpp_fmt *fmt, 649 struct hist_entry *left, struct hist_entry *right) 650 { 651 struct data__file *d = fmt_to_data_file(fmt); 652 653 return hist_entry__cmp_compute(right, left, COMPUTE_DELTA, d->idx); 654 } 655 656 static int64_t 657 hist_entry__cmp_delta_abs(struct perf_hpp_fmt *fmt, 658 struct hist_entry *left, struct hist_entry *right) 659 { 660 struct data__file *d = fmt_to_data_file(fmt); 661 662 return hist_entry__cmp_compute(right, left, COMPUTE_DELTA_ABS, d->idx); 663 } 664 665 static int64_t 666 hist_entry__cmp_ratio(struct perf_hpp_fmt *fmt, 667 struct hist_entry *left, struct hist_entry *right) 668 { 669 struct data__file *d = fmt_to_data_file(fmt); 670 671 return hist_entry__cmp_compute(right, left, COMPUTE_RATIO, d->idx); 672 } 673 674 static int64_t 675 hist_entry__cmp_wdiff(struct perf_hpp_fmt *fmt, 676 struct hist_entry *left, struct hist_entry *right) 677 { 678 struct data__file *d = fmt_to_data_file(fmt); 679 680 return hist_entry__cmp_compute(right, left, COMPUTE_WEIGHTED_DIFF, d->idx); 681 } 682 683 static int64_t 684 hist_entry__cmp_delta_idx(struct perf_hpp_fmt *fmt __maybe_unused, 685 struct hist_entry *left, struct hist_entry *right) 686 { 687 return hist_entry__cmp_compute_idx(right, left, COMPUTE_DELTA, 688 sort_compute); 689 } 690 691 static int64_t 692 hist_entry__cmp_delta_abs_idx(struct perf_hpp_fmt *fmt __maybe_unused, 693 struct hist_entry *left, struct hist_entry *right) 694 { 695 return hist_entry__cmp_compute_idx(right, left, COMPUTE_DELTA_ABS, 696 sort_compute); 697 } 698 699 static int64_t 700 hist_entry__cmp_ratio_idx(struct perf_hpp_fmt *fmt __maybe_unused, 701 struct hist_entry *left, struct hist_entry *right) 702 { 703 return hist_entry__cmp_compute_idx(right, left, COMPUTE_RATIO, 704 sort_compute); 705 } 706 707 static int64_t 708 hist_entry__cmp_wdiff_idx(struct perf_hpp_fmt *fmt __maybe_unused, 709 struct hist_entry *left, struct hist_entry *right) 710 { 711 return hist_entry__cmp_compute_idx(right, left, COMPUTE_WEIGHTED_DIFF, 712 sort_compute); 713 } 714 715 static void hists__process(struct hists *hists) 716 { 717 if (show_baseline_only) 718 hists__baseline_only(hists); 719 720 hists__precompute(hists); 721 hists__output_resort(hists, NULL); 722 723 hists__fprintf(hists, !quiet, 0, 0, 0, stdout, 724 !symbol_conf.use_callchain); 725 } 726 727 static void data__fprintf(void) 728 { 729 struct data__file *d; 730 int i; 731 732 fprintf(stdout, "# Data files:\n"); 733 734 data__for_each_file(i, d) 735 fprintf(stdout, "# [%d] %s %s\n", 736 d->idx, d->data.path, 737 !d->idx ? "(Baseline)" : ""); 738 739 fprintf(stdout, "#\n"); 740 } 741 742 static void data_process(void) 743 { 744 struct perf_evlist *evlist_base = data__files[0].session->evlist; 745 struct perf_evsel *evsel_base; 746 bool first = true; 747 748 evlist__for_each_entry(evlist_base, evsel_base) { 749 struct hists *hists_base = evsel__hists(evsel_base); 750 struct data__file *d; 751 int i; 752 753 data__for_each_file_new(i, d) { 754 struct perf_evlist *evlist = d->session->evlist; 755 struct perf_evsel *evsel; 756 struct hists *hists; 757 758 evsel = evsel_match(evsel_base, evlist); 759 if (!evsel) 760 continue; 761 762 hists = evsel__hists(evsel); 763 d->hists = hists; 764 765 hists__match(hists_base, hists); 766 767 if (!show_baseline_only) 768 hists__link(hists_base, hists); 769 } 770 771 if (!quiet) { 772 fprintf(stdout, "%s# Event '%s'\n#\n", first ? "" : "\n", 773 perf_evsel__name(evsel_base)); 774 } 775 776 first = false; 777 778 if (verbose > 0 || ((data__files_cnt > 2) && !quiet)) 779 data__fprintf(); 780 781 /* Don't sort callchain for perf diff */ 782 perf_evsel__reset_sample_bit(evsel_base, CALLCHAIN); 783 784 hists__process(hists_base); 785 } 786 } 787 788 static void data__free(struct data__file *d) 789 { 790 int col; 791 792 for (col = 0; col < PERF_HPP_DIFF__MAX_INDEX; col++) { 793 struct diff_hpp_fmt *fmt = &d->fmt[col]; 794 795 zfree(&fmt->header); 796 } 797 } 798 799 static int abstime_str_dup(char **pstr) 800 { 801 char *str = NULL; 802 803 if (pdiff.time_str && strchr(pdiff.time_str, ':')) { 804 str = strdup(pdiff.time_str); 805 if (!str) 806 return -ENOMEM; 807 } 808 809 *pstr = str; 810 return 0; 811 } 812 813 static int parse_absolute_time(struct data__file *d, char **pstr) 814 { 815 char *p = *pstr; 816 int ret; 817 818 /* 819 * Absolute timestamp for one file has the format: a.b,c.d 820 * For multiple files, the format is: a.b,c.d:a.b,c.d 821 */ 822 p = strchr(*pstr, ':'); 823 if (p) { 824 if (p == *pstr) { 825 pr_err("Invalid time string\n"); 826 return -EINVAL; 827 } 828 829 *p = 0; 830 p++; 831 if (*p == 0) { 832 pr_err("Invalid time string\n"); 833 return -EINVAL; 834 } 835 } 836 837 ret = perf_time__parse_for_ranges(*pstr, d->session, 838 &pdiff.ptime_range, 839 &pdiff.range_size, 840 &pdiff.range_num); 841 if (ret < 0) 842 return ret; 843 844 if (!p || *p == 0) 845 *pstr = NULL; 846 else 847 *pstr = p; 848 849 return ret; 850 } 851 852 static int parse_percent_time(struct data__file *d) 853 { 854 int ret; 855 856 ret = perf_time__parse_for_ranges(pdiff.time_str, d->session, 857 &pdiff.ptime_range, 858 &pdiff.range_size, 859 &pdiff.range_num); 860 return ret; 861 } 862 863 static int parse_time_str(struct data__file *d, char *abstime_ostr, 864 char **pabstime_tmp) 865 { 866 int ret = 0; 867 868 if (abstime_ostr) 869 ret = parse_absolute_time(d, pabstime_tmp); 870 else if (pdiff.time_str) 871 ret = parse_percent_time(d); 872 873 return ret; 874 } 875 876 static int __cmd_diff(void) 877 { 878 struct data__file *d; 879 int ret, i; 880 char *abstime_ostr, *abstime_tmp; 881 882 ret = abstime_str_dup(&abstime_ostr); 883 if (ret) 884 return ret; 885 886 abstime_tmp = abstime_ostr; 887 ret = -EINVAL; 888 889 data__for_each_file(i, d) { 890 d->session = perf_session__new(&d->data, false, &pdiff.tool); 891 if (!d->session) { 892 pr_err("Failed to open %s\n", d->data.path); 893 ret = -1; 894 goto out_delete; 895 } 896 897 if (pdiff.time_str) { 898 ret = parse_time_str(d, abstime_ostr, &abstime_tmp); 899 if (ret < 0) 900 goto out_delete; 901 } 902 903 if (cpu_list) { 904 ret = perf_session__cpu_bitmap(d->session, cpu_list, 905 cpu_bitmap); 906 if (ret < 0) 907 goto out_delete; 908 } 909 910 ret = perf_session__process_events(d->session); 911 if (ret) { 912 pr_err("Failed to process %s\n", d->data.path); 913 goto out_delete; 914 } 915 916 perf_evlist__collapse_resort(d->session->evlist); 917 918 if (pdiff.ptime_range) 919 zfree(&pdiff.ptime_range); 920 } 921 922 data_process(); 923 924 out_delete: 925 data__for_each_file(i, d) { 926 perf_session__delete(d->session); 927 data__free(d); 928 } 929 930 free(data__files); 931 932 if (pdiff.ptime_range) 933 zfree(&pdiff.ptime_range); 934 935 if (abstime_ostr) 936 free(abstime_ostr); 937 938 return ret; 939 } 940 941 static const char * const diff_usage[] = { 942 "perf diff [<options>] [old_file] [new_file]", 943 NULL, 944 }; 945 946 static const struct option options[] = { 947 OPT_INCR('v', "verbose", &verbose, 948 "be more verbose (show symbol address, etc)"), 949 OPT_BOOLEAN('q', "quiet", &quiet, "Do not show any message"), 950 OPT_BOOLEAN('b', "baseline-only", &show_baseline_only, 951 "Show only items with match in baseline"), 952 OPT_CALLBACK('c', "compute", &compute, 953 "delta,delta-abs,ratio,wdiff:w1,w2 (default delta-abs)", 954 "Entries differential computation selection", 955 setup_compute), 956 OPT_BOOLEAN('p', "period", &show_period, 957 "Show period values."), 958 OPT_BOOLEAN('F', "formula", &show_formula, 959 "Show formula."), 960 OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace, 961 "dump raw trace in ASCII"), 962 OPT_BOOLEAN('f', "force", &force, "don't complain, do it"), 963 OPT_STRING(0, "kallsyms", &symbol_conf.kallsyms_name, 964 "file", "kallsyms pathname"), 965 OPT_BOOLEAN('m', "modules", &symbol_conf.use_modules, 966 "load module symbols - WARNING: use only with -k and LIVE kernel"), 967 OPT_STRING('d', "dsos", &symbol_conf.dso_list_str, "dso[,dso...]", 968 "only consider symbols in these dsos"), 969 OPT_STRING('C', "comms", &symbol_conf.comm_list_str, "comm[,comm...]", 970 "only consider symbols in these comms"), 971 OPT_STRING('S', "symbols", &symbol_conf.sym_list_str, "symbol[,symbol...]", 972 "only consider these symbols"), 973 OPT_STRING('s', "sort", &sort_order, "key[,key2...]", 974 "sort by key(s): pid, comm, dso, symbol, parent, cpu, srcline, ..." 975 " Please refer the man page for the complete list."), 976 OPT_STRING_NOEMPTY('t', "field-separator", &symbol_conf.field_sep, "separator", 977 "separator for columns, no spaces will be added between " 978 "columns '.' is reserved."), 979 OPT_CALLBACK(0, "symfs", NULL, "directory", 980 "Look for files with symbols relative to this directory", 981 symbol__config_symfs), 982 OPT_UINTEGER('o', "order", &sort_compute, "Specify compute sorting."), 983 OPT_CALLBACK(0, "percentage", NULL, "relative|absolute", 984 "How to display percentage of filtered entries", parse_filter_percentage), 985 OPT_STRING(0, "time", &pdiff.time_str, "str", 986 "Time span (time percent or absolute timestamp)"), 987 OPT_STRING(0, "cpu", &cpu_list, "cpu", "list of cpus to profile"), 988 OPT_STRING(0, "pid", &symbol_conf.pid_list_str, "pid[,pid...]", 989 "only consider symbols in these pids"), 990 OPT_STRING(0, "tid", &symbol_conf.tid_list_str, "tid[,tid...]", 991 "only consider symbols in these tids"), 992 OPT_END() 993 }; 994 995 static double baseline_percent(struct hist_entry *he) 996 { 997 u64 total = hists__total_period(he->hists); 998 999 return 100.0 * he->stat.period / total; 1000 } 1001 1002 static int hpp__color_baseline(struct perf_hpp_fmt *fmt, 1003 struct perf_hpp *hpp, struct hist_entry *he) 1004 { 1005 struct diff_hpp_fmt *dfmt = 1006 container_of(fmt, struct diff_hpp_fmt, fmt); 1007 double percent = baseline_percent(he); 1008 char pfmt[20] = " "; 1009 1010 if (!he->dummy) { 1011 scnprintf(pfmt, 20, "%%%d.2f%%%%", dfmt->header_width - 1); 1012 return percent_color_snprintf(hpp->buf, hpp->size, 1013 pfmt, percent); 1014 } else 1015 return scnprintf(hpp->buf, hpp->size, "%*s", 1016 dfmt->header_width, pfmt); 1017 } 1018 1019 static int hpp__entry_baseline(struct hist_entry *he, char *buf, size_t size) 1020 { 1021 double percent = baseline_percent(he); 1022 const char *fmt = symbol_conf.field_sep ? "%.2f" : "%6.2f%%"; 1023 int ret = 0; 1024 1025 if (!he->dummy) 1026 ret = scnprintf(buf, size, fmt, percent); 1027 1028 return ret; 1029 } 1030 1031 static int __hpp__color_compare(struct perf_hpp_fmt *fmt, 1032 struct perf_hpp *hpp, struct hist_entry *he, 1033 int comparison_method) 1034 { 1035 struct diff_hpp_fmt *dfmt = 1036 container_of(fmt, struct diff_hpp_fmt, fmt); 1037 struct hist_entry *pair = get_pair_fmt(he, dfmt); 1038 double diff; 1039 s64 wdiff; 1040 char pfmt[20] = " "; 1041 1042 if (!pair) 1043 goto no_print; 1044 1045 switch (comparison_method) { 1046 case COMPUTE_DELTA: 1047 if (pair->diff.computed) 1048 diff = pair->diff.period_ratio_delta; 1049 else 1050 diff = compute_delta(he, pair); 1051 1052 scnprintf(pfmt, 20, "%%%+d.2f%%%%", dfmt->header_width - 1); 1053 return percent_color_snprintf(hpp->buf, hpp->size, 1054 pfmt, diff); 1055 case COMPUTE_RATIO: 1056 if (he->dummy) 1057 goto dummy_print; 1058 if (pair->diff.computed) 1059 diff = pair->diff.period_ratio; 1060 else 1061 diff = compute_ratio(he, pair); 1062 1063 scnprintf(pfmt, 20, "%%%d.6f", dfmt->header_width); 1064 return value_color_snprintf(hpp->buf, hpp->size, 1065 pfmt, diff); 1066 case COMPUTE_WEIGHTED_DIFF: 1067 if (he->dummy) 1068 goto dummy_print; 1069 if (pair->diff.computed) 1070 wdiff = pair->diff.wdiff; 1071 else 1072 wdiff = compute_wdiff(he, pair); 1073 1074 scnprintf(pfmt, 20, "%%14ld", dfmt->header_width); 1075 return color_snprintf(hpp->buf, hpp->size, 1076 get_percent_color(wdiff), 1077 pfmt, wdiff); 1078 default: 1079 BUG_ON(1); 1080 } 1081 dummy_print: 1082 return scnprintf(hpp->buf, hpp->size, "%*s", 1083 dfmt->header_width, "N/A"); 1084 no_print: 1085 return scnprintf(hpp->buf, hpp->size, "%*s", 1086 dfmt->header_width, pfmt); 1087 } 1088 1089 static int hpp__color_delta(struct perf_hpp_fmt *fmt, 1090 struct perf_hpp *hpp, struct hist_entry *he) 1091 { 1092 return __hpp__color_compare(fmt, hpp, he, COMPUTE_DELTA); 1093 } 1094 1095 static int hpp__color_ratio(struct perf_hpp_fmt *fmt, 1096 struct perf_hpp *hpp, struct hist_entry *he) 1097 { 1098 return __hpp__color_compare(fmt, hpp, he, COMPUTE_RATIO); 1099 } 1100 1101 static int hpp__color_wdiff(struct perf_hpp_fmt *fmt, 1102 struct perf_hpp *hpp, struct hist_entry *he) 1103 { 1104 return __hpp__color_compare(fmt, hpp, he, COMPUTE_WEIGHTED_DIFF); 1105 } 1106 1107 static void 1108 hpp__entry_unpair(struct hist_entry *he, int idx, char *buf, size_t size) 1109 { 1110 switch (idx) { 1111 case PERF_HPP_DIFF__PERIOD_BASELINE: 1112 scnprintf(buf, size, "%" PRIu64, he->stat.period); 1113 break; 1114 1115 default: 1116 break; 1117 } 1118 } 1119 1120 static void 1121 hpp__entry_pair(struct hist_entry *he, struct hist_entry *pair, 1122 int idx, char *buf, size_t size) 1123 { 1124 double diff; 1125 double ratio; 1126 s64 wdiff; 1127 1128 switch (idx) { 1129 case PERF_HPP_DIFF__DELTA: 1130 case PERF_HPP_DIFF__DELTA_ABS: 1131 if (pair->diff.computed) 1132 diff = pair->diff.period_ratio_delta; 1133 else 1134 diff = compute_delta(he, pair); 1135 1136 scnprintf(buf, size, "%+4.2F%%", diff); 1137 break; 1138 1139 case PERF_HPP_DIFF__RATIO: 1140 /* No point for ratio number if we are dummy.. */ 1141 if (he->dummy) { 1142 scnprintf(buf, size, "N/A"); 1143 break; 1144 } 1145 1146 if (pair->diff.computed) 1147 ratio = pair->diff.period_ratio; 1148 else 1149 ratio = compute_ratio(he, pair); 1150 1151 if (ratio > 0.0) 1152 scnprintf(buf, size, "%14.6F", ratio); 1153 break; 1154 1155 case PERF_HPP_DIFF__WEIGHTED_DIFF: 1156 /* No point for wdiff number if we are dummy.. */ 1157 if (he->dummy) { 1158 scnprintf(buf, size, "N/A"); 1159 break; 1160 } 1161 1162 if (pair->diff.computed) 1163 wdiff = pair->diff.wdiff; 1164 else 1165 wdiff = compute_wdiff(he, pair); 1166 1167 if (wdiff != 0) 1168 scnprintf(buf, size, "%14ld", wdiff); 1169 break; 1170 1171 case PERF_HPP_DIFF__FORMULA: 1172 formula_fprintf(he, pair, buf, size); 1173 break; 1174 1175 case PERF_HPP_DIFF__PERIOD: 1176 scnprintf(buf, size, "%" PRIu64, pair->stat.period); 1177 break; 1178 1179 default: 1180 BUG_ON(1); 1181 }; 1182 } 1183 1184 static void 1185 __hpp__entry_global(struct hist_entry *he, struct diff_hpp_fmt *dfmt, 1186 char *buf, size_t size) 1187 { 1188 struct hist_entry *pair = get_pair_fmt(he, dfmt); 1189 int idx = dfmt->idx; 1190 1191 /* baseline is special */ 1192 if (idx == PERF_HPP_DIFF__BASELINE) 1193 hpp__entry_baseline(he, buf, size); 1194 else { 1195 if (pair) 1196 hpp__entry_pair(he, pair, idx, buf, size); 1197 else 1198 hpp__entry_unpair(he, idx, buf, size); 1199 } 1200 } 1201 1202 static int hpp__entry_global(struct perf_hpp_fmt *_fmt, struct perf_hpp *hpp, 1203 struct hist_entry *he) 1204 { 1205 struct diff_hpp_fmt *dfmt = 1206 container_of(_fmt, struct diff_hpp_fmt, fmt); 1207 char buf[MAX_COL_WIDTH] = " "; 1208 1209 __hpp__entry_global(he, dfmt, buf, MAX_COL_WIDTH); 1210 1211 if (symbol_conf.field_sep) 1212 return scnprintf(hpp->buf, hpp->size, "%s", buf); 1213 else 1214 return scnprintf(hpp->buf, hpp->size, "%*s", 1215 dfmt->header_width, buf); 1216 } 1217 1218 static int hpp__header(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, 1219 struct hists *hists __maybe_unused, 1220 int line __maybe_unused, 1221 int *span __maybe_unused) 1222 { 1223 struct diff_hpp_fmt *dfmt = 1224 container_of(fmt, struct diff_hpp_fmt, fmt); 1225 1226 BUG_ON(!dfmt->header); 1227 return scnprintf(hpp->buf, hpp->size, dfmt->header); 1228 } 1229 1230 static int hpp__width(struct perf_hpp_fmt *fmt, 1231 struct perf_hpp *hpp __maybe_unused, 1232 struct hists *hists __maybe_unused) 1233 { 1234 struct diff_hpp_fmt *dfmt = 1235 container_of(fmt, struct diff_hpp_fmt, fmt); 1236 1237 BUG_ON(dfmt->header_width <= 0); 1238 return dfmt->header_width; 1239 } 1240 1241 static void init_header(struct data__file *d, struct diff_hpp_fmt *dfmt) 1242 { 1243 #define MAX_HEADER_NAME 100 1244 char buf_indent[MAX_HEADER_NAME]; 1245 char buf[MAX_HEADER_NAME]; 1246 const char *header = NULL; 1247 int width = 0; 1248 1249 BUG_ON(dfmt->idx >= PERF_HPP_DIFF__MAX_INDEX); 1250 header = columns[dfmt->idx].name; 1251 width = columns[dfmt->idx].width; 1252 1253 /* Only our defined HPP fmts should appear here. */ 1254 BUG_ON(!header); 1255 1256 if (data__files_cnt > 2) 1257 scnprintf(buf, MAX_HEADER_NAME, "%s/%d", header, d->idx); 1258 1259 #define NAME (data__files_cnt > 2 ? buf : header) 1260 dfmt->header_width = width; 1261 width = (int) strlen(NAME); 1262 if (dfmt->header_width < width) 1263 dfmt->header_width = width; 1264 1265 scnprintf(buf_indent, MAX_HEADER_NAME, "%*s", 1266 dfmt->header_width, NAME); 1267 1268 dfmt->header = strdup(buf_indent); 1269 #undef MAX_HEADER_NAME 1270 #undef NAME 1271 } 1272 1273 static void data__hpp_register(struct data__file *d, int idx) 1274 { 1275 struct diff_hpp_fmt *dfmt = &d->fmt[idx]; 1276 struct perf_hpp_fmt *fmt = &dfmt->fmt; 1277 1278 dfmt->idx = idx; 1279 1280 fmt->header = hpp__header; 1281 fmt->width = hpp__width; 1282 fmt->entry = hpp__entry_global; 1283 fmt->cmp = hist_entry__cmp_nop; 1284 fmt->collapse = hist_entry__cmp_nop; 1285 1286 /* TODO more colors */ 1287 switch (idx) { 1288 case PERF_HPP_DIFF__BASELINE: 1289 fmt->color = hpp__color_baseline; 1290 fmt->sort = hist_entry__cmp_baseline; 1291 break; 1292 case PERF_HPP_DIFF__DELTA: 1293 fmt->color = hpp__color_delta; 1294 fmt->sort = hist_entry__cmp_delta; 1295 break; 1296 case PERF_HPP_DIFF__RATIO: 1297 fmt->color = hpp__color_ratio; 1298 fmt->sort = hist_entry__cmp_ratio; 1299 break; 1300 case PERF_HPP_DIFF__WEIGHTED_DIFF: 1301 fmt->color = hpp__color_wdiff; 1302 fmt->sort = hist_entry__cmp_wdiff; 1303 break; 1304 case PERF_HPP_DIFF__DELTA_ABS: 1305 fmt->color = hpp__color_delta; 1306 fmt->sort = hist_entry__cmp_delta_abs; 1307 break; 1308 default: 1309 fmt->sort = hist_entry__cmp_nop; 1310 break; 1311 } 1312 1313 init_header(d, dfmt); 1314 perf_hpp__column_register(fmt); 1315 perf_hpp__register_sort_field(fmt); 1316 } 1317 1318 static int ui_init(void) 1319 { 1320 struct data__file *d; 1321 struct perf_hpp_fmt *fmt; 1322 int i; 1323 1324 data__for_each_file(i, d) { 1325 1326 /* 1327 * Baseline or compute realted columns: 1328 * 1329 * PERF_HPP_DIFF__BASELINE 1330 * PERF_HPP_DIFF__DELTA 1331 * PERF_HPP_DIFF__RATIO 1332 * PERF_HPP_DIFF__WEIGHTED_DIFF 1333 */ 1334 data__hpp_register(d, i ? compute_2_hpp[compute] : 1335 PERF_HPP_DIFF__BASELINE); 1336 1337 /* 1338 * And the rest: 1339 * 1340 * PERF_HPP_DIFF__FORMULA 1341 * PERF_HPP_DIFF__PERIOD 1342 * PERF_HPP_DIFF__PERIOD_BASELINE 1343 */ 1344 if (show_formula && i) 1345 data__hpp_register(d, PERF_HPP_DIFF__FORMULA); 1346 1347 if (show_period) 1348 data__hpp_register(d, i ? PERF_HPP_DIFF__PERIOD : 1349 PERF_HPP_DIFF__PERIOD_BASELINE); 1350 } 1351 1352 if (!sort_compute) 1353 return 0; 1354 1355 /* 1356 * Prepend an fmt to sort on columns at 'sort_compute' first. 1357 * This fmt is added only to the sort list but not to the 1358 * output fields list. 1359 * 1360 * Note that this column (data) can be compared twice - one 1361 * for this 'sort_compute' fmt and another for the normal 1362 * diff_hpp_fmt. But it shouldn't a problem as most entries 1363 * will be sorted out by first try or baseline and comparing 1364 * is not a costly operation. 1365 */ 1366 fmt = zalloc(sizeof(*fmt)); 1367 if (fmt == NULL) { 1368 pr_err("Memory allocation failed\n"); 1369 return -1; 1370 } 1371 1372 fmt->cmp = hist_entry__cmp_nop; 1373 fmt->collapse = hist_entry__cmp_nop; 1374 1375 switch (compute) { 1376 case COMPUTE_DELTA: 1377 fmt->sort = hist_entry__cmp_delta_idx; 1378 break; 1379 case COMPUTE_RATIO: 1380 fmt->sort = hist_entry__cmp_ratio_idx; 1381 break; 1382 case COMPUTE_WEIGHTED_DIFF: 1383 fmt->sort = hist_entry__cmp_wdiff_idx; 1384 break; 1385 case COMPUTE_DELTA_ABS: 1386 fmt->sort = hist_entry__cmp_delta_abs_idx; 1387 break; 1388 default: 1389 BUG_ON(1); 1390 } 1391 1392 perf_hpp__prepend_sort_field(fmt); 1393 return 0; 1394 } 1395 1396 static int data_init(int argc, const char **argv) 1397 { 1398 struct data__file *d; 1399 static const char *defaults[] = { 1400 "perf.data.old", 1401 "perf.data", 1402 }; 1403 bool use_default = true; 1404 int i; 1405 1406 data__files_cnt = 2; 1407 1408 if (argc) { 1409 if (argc == 1) 1410 defaults[1] = argv[0]; 1411 else { 1412 data__files_cnt = argc; 1413 use_default = false; 1414 } 1415 } else if (perf_guest) { 1416 defaults[0] = "perf.data.host"; 1417 defaults[1] = "perf.data.guest"; 1418 } 1419 1420 if (sort_compute >= (unsigned int) data__files_cnt) { 1421 pr_err("Order option out of limit.\n"); 1422 return -EINVAL; 1423 } 1424 1425 data__files = zalloc(sizeof(*data__files) * data__files_cnt); 1426 if (!data__files) 1427 return -ENOMEM; 1428 1429 data__for_each_file(i, d) { 1430 struct perf_data *data = &d->data; 1431 1432 data->path = use_default ? defaults[i] : argv[i]; 1433 data->mode = PERF_DATA_MODE_READ, 1434 data->force = force, 1435 1436 d->idx = i; 1437 } 1438 1439 return 0; 1440 } 1441 1442 static int diff__config(const char *var, const char *value, 1443 void *cb __maybe_unused) 1444 { 1445 if (!strcmp(var, "diff.order")) { 1446 int ret; 1447 if (perf_config_int(&ret, var, value) < 0) 1448 return -1; 1449 sort_compute = ret; 1450 return 0; 1451 } 1452 if (!strcmp(var, "diff.compute")) { 1453 if (!strcmp(value, "delta")) { 1454 compute = COMPUTE_DELTA; 1455 } else if (!strcmp(value, "delta-abs")) { 1456 compute = COMPUTE_DELTA_ABS; 1457 } else if (!strcmp(value, "ratio")) { 1458 compute = COMPUTE_RATIO; 1459 } else if (!strcmp(value, "wdiff")) { 1460 compute = COMPUTE_WEIGHTED_DIFF; 1461 } else { 1462 pr_err("Invalid compute method: %s\n", value); 1463 return -1; 1464 } 1465 } 1466 1467 return 0; 1468 } 1469 1470 int cmd_diff(int argc, const char **argv) 1471 { 1472 int ret = hists__init(); 1473 1474 if (ret < 0) 1475 return ret; 1476 1477 perf_config(diff__config, NULL); 1478 1479 argc = parse_options(argc, argv, options, diff_usage, 0); 1480 1481 if (quiet) 1482 perf_quiet_option(); 1483 1484 if (symbol__init(NULL) < 0) 1485 return -1; 1486 1487 if (data_init(argc, argv) < 0) 1488 return -1; 1489 1490 if (ui_init() < 0) 1491 return -1; 1492 1493 sort__mode = SORT_MODE__DIFF; 1494 1495 if (setup_sorting(NULL) < 0) 1496 usage_with_options(diff_usage, options); 1497 1498 setup_pager(); 1499 1500 sort__setup_elide(NULL); 1501 1502 return __cmd_diff(); 1503 } 1504