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.
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"
23 struct perf_session
*session
;
28 static struct data__file
*data__files
;
29 static int data__files_cnt
;
31 #define data__for_each_file_start(i, d, s) \
32 for (i = s, d = &data__files[s]; \
33 i < data__files_cnt; \
34 i++, d = &data__files[i])
36 #define data__for_each_file(i, d) data__for_each_file_start(i, d, 0)
38 static char diff__default_sort_order
[] = "dso,symbol";
40 static bool show_period
;
41 static bool show_formula
;
42 static bool show_baseline_only
;
43 static bool sort_compute
;
45 static s64 compute_wdiff_w1
;
46 static s64 compute_wdiff_w2
;
51 COMPUTE_WEIGHTED_DIFF
,
55 const char *compute_names
[COMPUTE_MAX
] = {
56 [COMPUTE_DELTA
] = "delta",
57 [COMPUTE_RATIO
] = "ratio",
58 [COMPUTE_WEIGHTED_DIFF
] = "wdiff",
63 static int setup_compute_opt_wdiff(char *opt
)
73 w2_str
= strchr(opt
, ',');
81 compute_wdiff_w1
= strtol(w1_str
, NULL
, 10);
82 compute_wdiff_w2
= strtol(w2_str
, NULL
, 10);
84 if (!compute_wdiff_w1
|| !compute_wdiff_w2
)
87 pr_debug("compute wdiff w1(%" PRId64
") w2(%" PRId64
")\n",
88 compute_wdiff_w1
, compute_wdiff_w2
);
94 pr_err("Failed: wrong weight data, use 'wdiff:w1,w2'\n");
99 static int setup_compute_opt(char *opt
)
101 if (compute
== COMPUTE_WEIGHTED_DIFF
)
102 return setup_compute_opt_wdiff(opt
);
105 pr_err("Failed: extra option specified '%s'", opt
);
112 static int setup_compute(const struct option
*opt
, const char *str
,
113 int unset __maybe_unused
)
115 int *cp
= (int *) opt
->value
;
116 char *cstr
= (char *) str
;
128 cstr
= (char *) ++str
;
133 option
= strchr(str
, ':');
135 unsigned len
= option
++ - str
;
138 * The str data are not writeable, so we need
139 * to use another buffer.
142 /* No option value is longer. */
143 if (len
>= sizeof(buf
))
146 strncpy(buf
, str
, len
);
151 for (i
= 0; i
< COMPUTE_MAX
; i
++)
152 if (!strcmp(cstr
, compute_names
[i
])) {
154 return setup_compute_opt(option
);
157 pr_err("Failed: '%s' is not computation method "
158 "(use 'delta','ratio' or 'wdiff')\n", str
);
162 double perf_diff__period_percent(struct hist_entry
*he
, u64 period
)
164 u64 total
= he
->hists
->stats
.total_period
;
165 return (period
* 100.0) / total
;
168 double perf_diff__compute_delta(struct hist_entry
*he
, struct hist_entry
*pair
)
170 double old_percent
= perf_diff__period_percent(he
, he
->stat
.period
);
171 double new_percent
= perf_diff__period_percent(pair
, pair
->stat
.period
);
173 pair
->diff
.period_ratio_delta
= new_percent
- old_percent
;
174 pair
->diff
.computed
= true;
175 return pair
->diff
.period_ratio_delta
;
178 double perf_diff__compute_ratio(struct hist_entry
*he
, struct hist_entry
*pair
)
180 double old_period
= he
->stat
.period
?: 1;
181 double new_period
= pair
->stat
.period
;
183 pair
->diff
.computed
= true;
184 pair
->diff
.period_ratio
= new_period
/ old_period
;
185 return pair
->diff
.period_ratio
;
188 s64
perf_diff__compute_wdiff(struct hist_entry
*he
, struct hist_entry
*pair
)
190 u64 old_period
= he
->stat
.period
;
191 u64 new_period
= pair
->stat
.period
;
193 pair
->diff
.computed
= true;
194 pair
->diff
.wdiff
= new_period
* compute_wdiff_w2
-
195 old_period
* compute_wdiff_w1
;
197 return pair
->diff
.wdiff
;
200 static int formula_delta(struct hist_entry
*he
, struct hist_entry
*pair
,
201 char *buf
, size_t size
)
203 return scnprintf(buf
, size
,
204 "(%" PRIu64
" * 100 / %" PRIu64
") - "
205 "(%" PRIu64
" * 100 / %" PRIu64
")",
206 pair
->stat
.period
, pair
->hists
->stats
.total_period
,
207 he
->stat
.period
, he
->hists
->stats
.total_period
);
210 static int formula_ratio(struct hist_entry
*he
, struct hist_entry
*pair
,
211 char *buf
, size_t size
)
213 double old_period
= he
->stat
.period
;
214 double new_period
= pair
->stat
.period
;
216 return scnprintf(buf
, size
, "%.0F / %.0F", new_period
, old_period
);
219 static int formula_wdiff(struct hist_entry
*he
, struct hist_entry
*pair
,
220 char *buf
, size_t size
)
222 u64 old_period
= he
->stat
.period
;
223 u64 new_period
= pair
->stat
.period
;
225 return scnprintf(buf
, size
,
226 "(%" PRIu64
" * " "%" PRId64
") - (%" PRIu64
" * " "%" PRId64
")",
227 new_period
, compute_wdiff_w2
, old_period
, compute_wdiff_w1
);
230 int perf_diff__formula(struct hist_entry
*he
, struct hist_entry
*pair
,
231 char *buf
, size_t size
)
235 return formula_delta(he
, pair
, buf
, size
);
237 return formula_ratio(he
, pair
, buf
, size
);
238 case COMPUTE_WEIGHTED_DIFF
:
239 return formula_wdiff(he
, pair
, buf
, size
);
247 static int hists__add_entry(struct hists
*self
,
248 struct addr_location
*al
, u64 period
,
251 if (__hists__add_entry(self
, al
, NULL
, period
, weight
) != NULL
)
256 static int diff__process_sample_event(struct perf_tool
*tool __maybe_unused
,
257 union perf_event
*event
,
258 struct perf_sample
*sample
,
259 struct perf_evsel
*evsel
,
260 struct machine
*machine
)
262 struct addr_location al
;
264 if (perf_event__preprocess_sample(event
, machine
, &al
, sample
, NULL
) < 0) {
265 pr_warning("problem processing %d event, skipping it.\n",
273 if (hists__add_entry(&evsel
->hists
, &al
, sample
->period
, sample
->weight
)) {
274 pr_warning("problem incrementing symbol period, skipping event\n");
278 evsel
->hists
.stats
.total_period
+= sample
->period
;
282 static struct perf_tool tool
= {
283 .sample
= diff__process_sample_event
,
284 .mmap
= perf_event__process_mmap
,
285 .comm
= perf_event__process_comm
,
286 .exit
= perf_event__process_exit
,
287 .fork
= perf_event__process_fork
,
288 .lost
= perf_event__process_lost
,
289 .ordered_samples
= true,
290 .ordering_requires_timestamps
= true,
293 static struct perf_evsel
*evsel_match(struct perf_evsel
*evsel
,
294 struct perf_evlist
*evlist
)
296 struct perf_evsel
*e
;
298 list_for_each_entry(e
, &evlist
->entries
, node
)
299 if (perf_evsel__match2(evsel
, e
))
305 static void perf_evlist__collapse_resort(struct perf_evlist
*evlist
)
307 struct perf_evsel
*evsel
;
309 list_for_each_entry(evsel
, &evlist
->entries
, node
) {
310 struct hists
*hists
= &evsel
->hists
;
312 hists__collapse_resort(hists
);
316 static void hists__baseline_only(struct hists
*hists
)
318 struct rb_root
*root
;
319 struct rb_node
*next
;
321 if (sort__need_collapse
)
322 root
= &hists
->entries_collapsed
;
324 root
= hists
->entries_in
;
326 next
= rb_first(root
);
327 while (next
!= NULL
) {
328 struct hist_entry
*he
= rb_entry(next
, struct hist_entry
, rb_node_in
);
330 next
= rb_next(&he
->rb_node_in
);
331 if (!hist_entry__next_pair(he
)) {
332 rb_erase(&he
->rb_node_in
, root
);
333 hist_entry__free(he
);
338 static void hists__precompute(struct hists
*hists
)
340 struct rb_root
*root
;
341 struct rb_node
*next
;
343 if (sort__need_collapse
)
344 root
= &hists
->entries_collapsed
;
346 root
= hists
->entries_in
;
348 next
= rb_first(root
);
349 while (next
!= NULL
) {
350 struct hist_entry
*he
= rb_entry(next
, struct hist_entry
, rb_node_in
);
351 struct hist_entry
*pair
= hist_entry__next_pair(he
);
353 next
= rb_next(&he
->rb_node_in
);
359 perf_diff__compute_delta(he
, pair
);
362 perf_diff__compute_ratio(he
, pair
);
364 case COMPUTE_WEIGHTED_DIFF
:
365 perf_diff__compute_wdiff(he
, pair
);
373 static int64_t cmp_doubles(double l
, double r
)
384 hist_entry__cmp_compute(struct hist_entry
*left
, struct hist_entry
*right
,
390 double l
= left
->diff
.period_ratio_delta
;
391 double r
= right
->diff
.period_ratio_delta
;
393 return cmp_doubles(l
, r
);
397 double l
= left
->diff
.period_ratio
;
398 double r
= right
->diff
.period_ratio
;
400 return cmp_doubles(l
, r
);
402 case COMPUTE_WEIGHTED_DIFF
:
404 s64 l
= left
->diff
.wdiff
;
405 s64 r
= right
->diff
.wdiff
;
416 static void insert_hist_entry_by_compute(struct rb_root
*root
,
417 struct hist_entry
*he
,
420 struct rb_node
**p
= &root
->rb_node
;
421 struct rb_node
*parent
= NULL
;
422 struct hist_entry
*iter
;
426 iter
= rb_entry(parent
, struct hist_entry
, rb_node
);
427 if (hist_entry__cmp_compute(he
, iter
, c
) < 0)
433 rb_link_node(&he
->rb_node
, parent
, p
);
434 rb_insert_color(&he
->rb_node
, root
);
437 static void hists__compute_resort(struct hists
*hists
)
439 struct rb_root
*root
;
440 struct rb_node
*next
;
442 if (sort__need_collapse
)
443 root
= &hists
->entries_collapsed
;
445 root
= hists
->entries_in
;
447 hists
->entries
= RB_ROOT
;
448 next
= rb_first(root
);
450 hists
->nr_entries
= 0;
451 hists
->stats
.total_period
= 0;
452 hists__reset_col_len(hists
);
454 while (next
!= NULL
) {
455 struct hist_entry
*he
;
457 he
= rb_entry(next
, struct hist_entry
, rb_node_in
);
458 next
= rb_next(&he
->rb_node_in
);
460 insert_hist_entry_by_compute(&hists
->entries
, he
, compute
);
461 hists__inc_nr_entries(hists
, he
);
465 static void hists__process(struct hists
*base
, struct hists
*new)
467 hists__match(base
, new);
469 if (show_baseline_only
)
470 hists__baseline_only(base
);
472 hists__link(base
, new);
475 hists__precompute(base
);
476 hists__compute_resort(base
);
478 hists__output_resort(base
);
481 hists__fprintf(base
, true, 0, 0, 0, stdout
);
484 static void data__fprintf(void)
486 struct data__file
*d
;
489 fprintf(stdout
, "# Data files:\n");
491 data__for_each_file(i
, d
)
492 fprintf(stdout
, "# [%d] %s %s\n",
494 !d
->idx
? "(Baseline)" : "");
496 fprintf(stdout
, "#\n");
499 static void data_process(void)
501 struct perf_evlist
*evlist_old
= data__files
[0].session
->evlist
;
502 struct perf_evlist
*evlist_new
= data__files
[1].session
->evlist
;
503 struct perf_evsel
*evsel_old
;
506 list_for_each_entry(evsel_old
, &evlist_old
->entries
, node
) {
507 struct perf_evsel
*evsel_new
;
509 evsel_new
= evsel_match(evsel_old
, evlist_new
);
513 fprintf(stdout
, "%s# Event '%s'\n#\n", first
? "" : "\n",
514 perf_evsel__name(evsel_old
));
521 hists__process(&evsel_old
->hists
, &evsel_new
->hists
);
525 static int __cmd_diff(void)
527 struct data__file
*d
;
528 int ret
= -EINVAL
, i
;
530 data__for_each_file(i
, d
) {
531 d
->session
= perf_session__new(d
->file
, O_RDONLY
, force
,
534 pr_err("Failed to open %s\n", d
->file
);
539 ret
= perf_session__process_events(d
->session
, &tool
);
541 pr_err("Failed to process %s\n", d
->file
);
545 perf_evlist__collapse_resort(d
->session
->evlist
);
551 data__for_each_file(i
, d
) {
553 perf_session__delete(d
->session
);
560 static const char * const diff_usage
[] = {
561 "perf diff [<options>] [old_file] [new_file]",
565 static const struct option options
[] = {
566 OPT_INCR('v', "verbose", &verbose
,
567 "be more verbose (show symbol address, etc)"),
568 OPT_BOOLEAN('b', "baseline-only", &show_baseline_only
,
569 "Show only items with match in baseline"),
570 OPT_CALLBACK('c', "compute", &compute
,
571 "delta,ratio,wdiff:w1,w2 (default delta)",
572 "Entries differential computation selection",
574 OPT_BOOLEAN('p', "period", &show_period
,
575 "Show period values."),
576 OPT_BOOLEAN('F', "formula", &show_formula
,
578 OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace
,
579 "dump raw trace in ASCII"),
580 OPT_BOOLEAN('f', "force", &force
, "don't complain, do it"),
581 OPT_BOOLEAN('m', "modules", &symbol_conf
.use_modules
,
582 "load module symbols - WARNING: use only with -k and LIVE kernel"),
583 OPT_STRING('d', "dsos", &symbol_conf
.dso_list_str
, "dso[,dso...]",
584 "only consider symbols in these dsos"),
585 OPT_STRING('C', "comms", &symbol_conf
.comm_list_str
, "comm[,comm...]",
586 "only consider symbols in these comms"),
587 OPT_STRING('S', "symbols", &symbol_conf
.sym_list_str
, "symbol[,symbol...]",
588 "only consider these symbols"),
589 OPT_STRING('s', "sort", &sort_order
, "key[,key2...]",
590 "sort by key(s): pid, comm, dso, symbol, parent"),
591 OPT_STRING('t', "field-separator", &symbol_conf
.field_sep
, "separator",
592 "separator for columns, no spaces will be added between "
593 "columns '.' is reserved."),
594 OPT_STRING(0, "symfs", &symbol_conf
.symfs
, "directory",
595 "Look for files with symbols relative to this directory"),
599 static void ui_init(void)
602 * Display baseline/delta/ratio
603 * formula/periods columns.
605 perf_hpp__column_enable(PERF_HPP__BASELINE
);
609 perf_hpp__column_enable(PERF_HPP__DELTA
);
612 perf_hpp__column_enable(PERF_HPP__RATIO
);
614 case COMPUTE_WEIGHTED_DIFF
:
615 perf_hpp__column_enable(PERF_HPP__WEIGHTED_DIFF
);
622 perf_hpp__column_enable(PERF_HPP__FORMULA
);
625 perf_hpp__column_enable(PERF_HPP__PERIOD
);
626 perf_hpp__column_enable(PERF_HPP__PERIOD_BASELINE
);
630 static int data_init(int argc
, const char **argv
)
632 struct data__file
*d
;
633 static const char *defaults
[] = {
643 usage_with_options(diff_usage
, options
);
645 defaults
[0] = argv
[0];
646 defaults
[1] = argv
[1];
648 defaults
[1] = argv
[0];
649 } else if (symbol_conf
.default_guest_vmlinux_name
||
650 symbol_conf
.default_guest_kallsyms
) {
651 defaults
[0] = "perf.data.host";
652 defaults
[1] = "perf.data.guest";
655 data__files
= zalloc(sizeof(*data__files
) * data__files_cnt
);
659 data__for_each_file(i
, d
) {
660 d
->file
= defaults
[i
];
667 int cmd_diff(int argc
, const char **argv
, const char *prefix __maybe_unused
)
669 sort_order
= diff__default_sort_order
;
670 argc
= parse_options(argc
, argv
, options
, diff_usage
, 0);
672 if (symbol__init() < 0)
675 if (data_init(argc
, argv
) < 0)
680 if (setup_sorting() < 0)
681 usage_with_options(diff_usage
, options
);
685 sort__setup_elide(NULL
);