diff options
Diffstat (limited to 'tools/perf/builtin-diff.c')
-rw-r--r-- | tools/perf/builtin-diff.c | 437 |
1 files changed, 400 insertions, 37 deletions
diff --git a/tools/perf/builtin-diff.c b/tools/perf/builtin-diff.c index a0b531c14b97..93b852f8a5d5 100644 --- a/tools/perf/builtin-diff.c +++ b/tools/perf/builtin-diff.c | |||
@@ -24,6 +24,228 @@ static char const *input_old = "perf.data.old", | |||
24 | static char diff__default_sort_order[] = "dso,symbol"; | 24 | static char diff__default_sort_order[] = "dso,symbol"; |
25 | static bool force; | 25 | static bool force; |
26 | static bool show_displacement; | 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 | } | ||
27 | 249 | ||
28 | static int hists__add_entry(struct hists *self, | 250 | static int hists__add_entry(struct hists *self, |
29 | struct addr_location *al, u64 period) | 251 | struct addr_location *al, u64 period) |
@@ -47,7 +269,7 @@ static int diff__process_sample_event(struct perf_tool *tool __maybe_unused, | |||
47 | return -1; | 269 | return -1; |
48 | } | 270 | } |
49 | 271 | ||
50 | if (al.filtered || al.sym == NULL) | 272 | if (al.filtered) |
51 | return 0; | 273 | return 0; |
52 | 274 | ||
53 | if (hists__add_entry(&evsel->hists, &al, sample->period)) { | 275 | if (hists__add_entry(&evsel->hists, &al, sample->period)) { |
@@ -63,8 +285,8 @@ static struct perf_tool tool = { | |||
63 | .sample = diff__process_sample_event, | 285 | .sample = diff__process_sample_event, |
64 | .mmap = perf_event__process_mmap, | 286 | .mmap = perf_event__process_mmap, |
65 | .comm = perf_event__process_comm, | 287 | .comm = perf_event__process_comm, |
66 | .exit = perf_event__process_task, | 288 | .exit = perf_event__process_exit, |
67 | .fork = perf_event__process_task, | 289 | .fork = perf_event__process_fork, |
68 | .lost = perf_event__process_lost, | 290 | .lost = perf_event__process_lost, |
69 | .ordered_samples = true, | 291 | .ordered_samples = true, |
70 | .ordering_requires_timestamps = true, | 292 | .ordering_requires_timestamps = true, |
@@ -112,36 +334,6 @@ static void hists__name_resort(struct hists *self, bool sort) | |||
112 | self->entries = tmp; | 334 | self->entries = tmp; |
113 | } | 335 | } |
114 | 336 | ||
115 | static struct hist_entry *hists__find_entry(struct hists *self, | ||
116 | struct hist_entry *he) | ||
117 | { | ||
118 | struct rb_node *n = self->entries.rb_node; | ||
119 | |||
120 | while (n) { | ||
121 | struct hist_entry *iter = rb_entry(n, struct hist_entry, rb_node); | ||
122 | int64_t cmp = hist_entry__cmp(he, iter); | ||
123 | |||
124 | if (cmp < 0) | ||
125 | n = n->rb_left; | ||
126 | else if (cmp > 0) | ||
127 | n = n->rb_right; | ||
128 | else | ||
129 | return iter; | ||
130 | } | ||
131 | |||
132 | return NULL; | ||
133 | } | ||
134 | |||
135 | static void hists__match(struct hists *older, struct hists *newer) | ||
136 | { | ||
137 | struct rb_node *nd; | ||
138 | |||
139 | for (nd = rb_first(&newer->entries); nd; nd = rb_next(nd)) { | ||
140 | struct hist_entry *pos = rb_entry(nd, struct hist_entry, rb_node); | ||
141 | pos->pair = hists__find_entry(older, pos); | ||
142 | } | ||
143 | } | ||
144 | |||
145 | static struct perf_evsel *evsel_match(struct perf_evsel *evsel, | 337 | static struct perf_evsel *evsel_match(struct perf_evsel *evsel, |
146 | struct perf_evlist *evlist) | 338 | struct perf_evlist *evlist) |
147 | { | 339 | { |
@@ -172,6 +364,144 @@ static void perf_evlist__resort_hists(struct perf_evlist *evlist, bool name) | |||
172 | } | 364 | } |
173 | } | 365 | } |
174 | 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 | |||
175 | static int __cmd_diff(void) | 505 | static int __cmd_diff(void) |
176 | { | 506 | { |
177 | int ret, i; | 507 | int ret, i; |
@@ -213,8 +543,7 @@ static int __cmd_diff(void) | |||
213 | 543 | ||
214 | first = false; | 544 | first = false; |
215 | 545 | ||
216 | hists__match(&evsel_old->hists, &evsel->hists); | 546 | hists__process(&evsel_old->hists, &evsel->hists); |
217 | hists__fprintf(&evsel->hists, true, 0, 0, stdout); | ||
218 | } | 547 | } |
219 | 548 | ||
220 | out_delete: | 549 | out_delete: |
@@ -235,6 +564,16 @@ static const struct option options[] = { | |||
235 | "be more verbose (show symbol address, etc)"), | 564 | "be more verbose (show symbol address, etc)"), |
236 | OPT_BOOLEAN('M', "displacement", &show_displacement, | 565 | OPT_BOOLEAN('M', "displacement", &show_displacement, |
237 | "Show position displacement relative to baseline"), | 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."), | ||
238 | OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace, | 577 | OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace, |
239 | "dump raw trace in ASCII"), | 578 | "dump raw trace in ASCII"), |
240 | OPT_BOOLEAN('f', "force", &force, "don't complain, do it"), | 579 | OPT_BOOLEAN('f', "force", &force, "don't complain, do it"), |
@@ -263,12 +602,36 @@ static void ui_init(void) | |||
263 | /* No overhead column. */ | 602 | /* No overhead column. */ |
264 | perf_hpp__column_enable(PERF_HPP__OVERHEAD, false); | 603 | perf_hpp__column_enable(PERF_HPP__OVERHEAD, false); |
265 | 604 | ||
266 | /* Display baseline/delta/displacement columns. */ | 605 | /* |
606 | * Display baseline/delta/ratio/displacement/ | ||
607 | * formula/periods columns. | ||
608 | */ | ||
267 | perf_hpp__column_enable(PERF_HPP__BASELINE, true); | 609 | perf_hpp__column_enable(PERF_HPP__BASELINE, true); |
268 | perf_hpp__column_enable(PERF_HPP__DELTA, 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 | }; | ||
269 | 624 | ||
270 | if (show_displacement) | 625 | if (show_displacement) |
271 | perf_hpp__column_enable(PERF_HPP__DISPL, true); | 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 | } | ||
272 | } | 635 | } |
273 | 636 | ||
274 | int cmd_diff(int argc, const char **argv, const char *prefix __maybe_unused) | 637 | int cmd_diff(int argc, const char **argv, const char *prefix __maybe_unused) |