linux/tools/perf/builtin-diff.c
<<
>>
Prefs
   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
  22static char const *input_old = "perf.data.old",
  23                  *input_new = "perf.data";
  24static char       diff__default_sort_order[] = "dso,symbol";
  25static bool  force;
  26static bool show_displacement;
  27static bool show_period;
  28static bool show_formula;
  29static bool show_baseline_only;
  30static bool sort_compute;
  31
  32static s64 compute_wdiff_w1;
  33static s64 compute_wdiff_w2;
  34
  35enum {
  36        COMPUTE_DELTA,
  37        COMPUTE_RATIO,
  38        COMPUTE_WEIGHTED_DIFF,
  39        COMPUTE_MAX,
  40};
  41
  42const char *compute_names[COMPUTE_MAX] = {
  43        [COMPUTE_DELTA] = "delta",
  44        [COMPUTE_RATIO] = "ratio",
  45        [COMPUTE_WEIGHTED_DIFF] = "wdiff",
  46};
  47
  48static int compute;
  49
  50static 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
  86static 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
  99static 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
 149static 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
 155double 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
 166double 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
 177s64 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
 194static 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
 208static 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
 220static 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
 234int 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
 250static 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
 258static 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
 284static 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
 295static 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
 315static 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
 337static 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
 349static 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
 367static 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
 382static 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
 407static 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
 417static int64_t
 418hist_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
 450static 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
 471static 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
 488static 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
 505static 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
 549out_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
 557static const char * const diff_usage[] = {
 558        "perf diff [<options>] [old_file] [new_file]",
 559        NULL,
 560};
 561
 562static 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
 598static 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
 637int 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