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 } 289