]> Pileus Git - ~andy/linux/blob - tools/perf/builtin-diff.c
perf diff: Introduce tool to show performance difference
[~andy/linux] / tools / perf / builtin-diff.c
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/session.h"
13 #include "util/sort.h"
14 #include "util/symbol.h"
15 #include "util/util.h"
16
17 #include <stdlib.h>
18
19 static char        const *input_old = "perf.data.old",
20                          *input_new = "perf.data";
21 static int         force;
22 static bool        show_percent;
23
24 struct symbol_conf symbol_conf;
25
26 static int perf_session__add_hist_entry(struct perf_session *self,
27                                         struct addr_location *al, u64 count)
28 {
29         bool hit;
30         struct hist_entry *he = __perf_session__add_hist_entry(self, al, NULL,
31                                                                count, &hit);
32         if (he == NULL)
33                 return -ENOMEM;
34
35         if (hit)
36                 he->count += count;
37
38         return 0;
39 }
40
41 static int diff__process_sample_event(event_t *event, struct perf_session *session)
42 {
43         struct addr_location al;
44         struct sample_data data = { .period = 1, };
45
46         dump_printf("(IP, %d): %d: %p\n", event->header.misc,
47                     event->ip.pid, (void *)(long)event->ip.ip);
48
49         if (event__preprocess_sample(event, session, &al, NULL) < 0) {
50                 pr_warning("problem processing %d event, skipping it.\n",
51                            event->header.type);
52                 return -1;
53         }
54
55         event__parse_sample(event, session->sample_type, &data);
56
57         if (al.sym && perf_session__add_hist_entry(session, &al, data.period)) {
58                 pr_warning("problem incrementing symbol count, skipping event\n");
59                 return -1;
60         }
61
62         session->events_stats.total += data.period;
63         return 0;
64 }
65
66 static struct perf_event_ops event_ops = {
67         .process_sample_event = diff__process_sample_event,
68         .process_mmap_event   = event__process_mmap,
69         .process_comm_event   = event__process_comm,
70         .process_exit_event   = event__process_task,
71         .process_fork_event   = event__process_task,
72         .process_lost_event   = event__process_lost,
73 };
74
75 static void perf_session__insert_hist_entry_by_name(struct rb_root *root,
76                                                     struct hist_entry *he)
77 {
78         struct rb_node **p = &root->rb_node;
79         struct rb_node *parent = NULL;
80         struct hist_entry *iter;
81
82         while (*p != NULL) {
83                 int cmp;
84                 parent = *p;
85                 iter = rb_entry(parent, struct hist_entry, rb_node);
86
87                 cmp = strcmp(he->map->dso->name, iter->map->dso->name);
88                 if (cmp > 0)
89                         p = &(*p)->rb_left;
90                 else if (cmp < 0)
91                         p = &(*p)->rb_right;
92                 else {
93                         cmp = strcmp(he->sym->name, iter->sym->name);
94                         if (cmp > 0)
95                                 p = &(*p)->rb_left;
96                         else
97                                 p = &(*p)->rb_right;
98                 }
99         }
100
101         rb_link_node(&he->rb_node, parent, p);
102         rb_insert_color(&he->rb_node, root);
103 }
104
105 static void perf_session__resort_by_name(struct perf_session *self)
106 {
107         unsigned long position = 1;
108         struct rb_root tmp = RB_ROOT;
109         struct rb_node *next = rb_first(&self->hists);
110
111         while (next != NULL) {
112                 struct hist_entry *n = rb_entry(next, struct hist_entry, rb_node);
113
114                 next = rb_next(&n->rb_node);
115                 rb_erase(&n->rb_node, &self->hists);
116                 n->position = position++;
117                 perf_session__insert_hist_entry_by_name(&tmp, n);
118         }
119
120         self->hists = tmp;
121 }
122
123 static struct hist_entry *
124 perf_session__find_hist_entry_by_name(struct perf_session *self,
125                                       struct hist_entry *he)
126 {
127         struct rb_node *n = self->hists.rb_node;
128
129         while (n) {
130                 struct hist_entry *iter = rb_entry(n, struct hist_entry, rb_node);
131                 int cmp = strcmp(he->map->dso->name, iter->map->dso->name);
132
133                 if (cmp > 0)
134                         n = n->rb_left;
135                 else if (cmp < 0)
136                         n = n->rb_right;
137                 else {
138                         cmp = strcmp(he->sym->name, iter->sym->name);
139                         if (cmp > 0)
140                                 n = n->rb_left;
141                         else if (cmp < 0)
142                                 n = n->rb_right;
143                         else
144                                 return iter;
145                 }
146         }
147
148         return NULL;
149 }
150
151 static void perf_session__match_hists(struct perf_session *old_session,
152                                       struct perf_session *new_session)
153 {
154         struct rb_node *nd;
155
156         perf_session__resort_by_name(old_session);
157
158         for (nd = rb_first(&new_session->hists); nd; nd = rb_next(nd)) {
159                 struct hist_entry *pos = rb_entry(nd, struct hist_entry, rb_node);
160                 pos->pair = perf_session__find_hist_entry_by_name(old_session, pos);
161         }
162 }
163
164 static size_t hist_entry__fprintf_matched(struct hist_entry *self,
165                                           unsigned long pos,
166                                           struct perf_session *session,
167                                           struct perf_session *pair_session,
168                                           FILE *fp)
169 {
170         u64 old_count = 0;
171         char displacement[16];
172         size_t printed;
173
174         if (self->pair != NULL) {
175                 long pdiff = (long)self->pair->position - (long)pos;
176                 old_count = self->pair->count;
177                 if (pdiff == 0)
178                         goto blank;
179                 snprintf(displacement, sizeof(displacement), "%+4ld", pdiff);
180         } else {
181 blank:          memset(displacement, ' ', sizeof(displacement));
182         }
183
184         printed = fprintf(fp, "%4lu %5.5s ", pos, displacement);
185
186         if (show_percent) {
187                 double old_percent = (old_count * 100) / pair_session->events_stats.total,
188                        new_percent = (self->count * 100) / session->events_stats.total;
189                 double diff = old_percent - new_percent;
190
191                 if (verbose)
192                         printed += fprintf(fp, " %3.2f%% %3.2f%%", old_percent, new_percent);
193
194                 if ((u64)diff != 0)
195                         printed += fprintf(fp, " %+4.2F%%", diff);
196                 else
197                         printed += fprintf(fp, "       ");
198         } else {
199                 if (verbose)
200                         printed += fprintf(fp, " %9Lu %9Lu", old_count, self->count);
201                 printed += fprintf(fp, " %+9Ld", (s64)self->count - (s64)old_count);
202         }
203
204         return printed + fprintf(fp, " %25.25s   %s\n",
205                                  self->map->dso->name, self->sym->name);
206 }
207
208 static size_t perf_session__fprintf_matched_hists(struct perf_session *self,
209                                                   struct perf_session *pair,
210                                                   FILE *fp)
211 {
212         struct rb_node *nd;
213         size_t printed = 0;
214         unsigned long pos = 1;
215
216         for (nd = rb_first(&self->hists); nd; nd = rb_next(nd)) {
217                 struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
218                 printed += hist_entry__fprintf_matched(he, pos++, self, pair, fp);
219         }
220
221         return printed;
222 }
223
224 static int __cmd_diff(void)
225 {
226         int ret, i;
227         struct perf_session *session[2];
228
229         session[0] = perf_session__new(input_old, O_RDONLY, force, &symbol_conf);
230         session[1] = perf_session__new(input_new, O_RDONLY, force, &symbol_conf);
231         if (session[0] == NULL || session[1] == NULL)
232                 return -ENOMEM;
233
234         for (i = 0; i < 2; ++i) {
235                 ret = perf_session__process_events(session[i], &event_ops);
236                 if (ret)
237                         goto out_delete;
238                 perf_session__output_resort(session[i], session[i]->events_stats.total);
239         }
240
241         perf_session__match_hists(session[0], session[1]);
242         perf_session__fprintf_matched_hists(session[1], session[0], stdout);
243 out_delete:
244         for (i = 0; i < 2; ++i)
245                 perf_session__delete(session[i]);
246         return ret;
247 }
248
249 static const char *const diff_usage[] = {
250         "perf diff [<options>] [old_file] [new_file]",
251 };
252
253 static const struct option options[] = {
254         OPT_BOOLEAN('v', "verbose", &verbose,
255                     "be more verbose (show symbol address, etc)"),
256         OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace,
257                     "dump raw trace in ASCII"),
258         OPT_BOOLEAN('f', "force", &force, "don't complain, do it"),
259         OPT_BOOLEAN('m', "modules", &symbol_conf.use_modules,
260                     "load module symbols - WARNING: use only with -k and LIVE kernel"),
261         OPT_BOOLEAN('p', "percentages", &show_percent,
262                     "Don't shorten the pathnames taking into account the cwd"),
263         OPT_BOOLEAN('P', "full-paths", &event_ops.full_paths,
264                     "Don't shorten the pathnames taking into account the cwd"),
265         OPT_END()
266 };
267
268 int cmd_diff(int argc, const char **argv, const char *prefix __used)
269 {
270         if (symbol__init(&symbol_conf) < 0)
271                 return -1;
272
273         setup_sorting(diff_usage, options);
274
275         argc = parse_options(argc, argv, options, diff_usage, 0);
276         if (argc) {
277                 if (argc > 2)
278                         usage_with_options(diff_usage, options);
279                 if (argc == 2) {
280                         input_old = argv[0];
281                         input_new = argv[1];
282                 } else
283                         input_new = argv[0];
284         }
285
286         setup_pager();
287         return __cmd_diff();
288 }