builtin-diff.c 12.0 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11
/*
 * builtin-diff.c
 *
 * Builtin diff command: Analyze two perf.data input files, look up and read
 * DSOs and symbol information, sort them and produce a diff.
 */
#include "builtin.h"

#include "util/debug.h"
#include "util/event.h"
#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 17 18 19 20 21
#include "util/sort.h"
#include "util/symbol.h"
#include "util/util.h"

#include <stdlib.h>

22 23
static char const *input_old = "perf.data.old",
		  *input_new = "perf.data";
24
static char	  diff__default_sort_order[] = "dso,symbol";
25
static bool  force;
26
static bool show_displacement;
27
static bool show_baseline_only;
28
static bool sort_compute;
29

30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
enum {
	COMPUTE_DELTA,
	COMPUTE_RATIO,
	COMPUTE_MAX,
};

const char *compute_names[COMPUTE_MAX] = {
	[COMPUTE_DELTA] = "delta",
	[COMPUTE_RATIO] = "ratio",
};

static int compute;

static int setup_compute(const struct option *opt, const char *str,
			 int unset __maybe_unused)
{
	int *cp = (int *) opt->value;
	unsigned i;

	if (!str) {
		*cp = COMPUTE_DELTA;
		return 0;
	}

54 55 56 57 58 59 60
	if (*str == '+') {
		sort_compute = true;
		str++;
		if (!*str)
			return 0;
	}

61 62 63 64 65 66 67 68 69 70 71
	for (i = 0; i < COMPUTE_MAX; i++)
		if (!strcmp(str, compute_names[i])) {
			*cp = i;
			return 0;
		}

	pr_err("Failed: '%s' is not computation method "
	       "(use 'delta' or 'ratio').\n", str);
	return -EINVAL;
}

72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
static double get_period_percent(struct hist_entry *he, u64 period)
{
	u64 total = he->hists->stats.total_period;
	return (period * 100.0) / total;
}

double perf_diff__compute_delta(struct hist_entry *he)
{
	struct hist_entry *pair = he->pair;
	double new_percent = get_period_percent(he, he->stat.period);
	double old_percent = pair ? get_period_percent(pair, pair->stat.period) : 0.0;

	he->diff.period_ratio_delta = new_percent - old_percent;
	he->diff.computed = true;
	return he->diff.period_ratio_delta;
}

double perf_diff__compute_ratio(struct hist_entry *he)
{
	struct hist_entry *pair = he->pair;
	double new_period = he->stat.period;
	double old_period = pair ? pair->stat.period : 0;

	he->diff.computed = true;
	he->diff.period_ratio = pair ? (new_period / old_period) : 0;
	return he->diff.period_ratio;
}

100
static int hists__add_entry(struct hists *self,
101
			    struct addr_location *al, u64 period)
102
{
103
	if (__hists__add_entry(self, al, NULL, period) != NULL)
104 105
		return 0;
	return -ENOMEM;
106 107
}

108
static int diff__process_sample_event(struct perf_tool *tool __maybe_unused,
109
				      union perf_event *event,
110
				      struct perf_sample *sample,
111
				      struct perf_evsel *evsel,
112
				      struct machine *machine)
113 114 115
{
	struct addr_location al;

116
	if (perf_event__preprocess_sample(event, machine, &al, sample, NULL) < 0) {
117 118 119 120 121
		pr_warning("problem processing %d event, skipping it.\n",
			   event->header.type);
		return -1;
	}

122
	if (al.filtered || al.sym == NULL)
123 124
		return 0;

125
	if (hists__add_entry(&evsel->hists, &al, sample->period)) {
126
		pr_warning("problem incrementing symbol period, skipping event\n");
127 128 129
		return -1;
	}

130
	evsel->hists.stats.total_period += sample->period;
131 132 133
	return 0;
}

134 135 136 137 138 139 140 141 142
static struct perf_tool tool = {
	.sample	= diff__process_sample_event,
	.mmap	= perf_event__process_mmap,
	.comm	= perf_event__process_comm,
	.exit	= perf_event__process_task,
	.fork	= perf_event__process_task,
	.lost	= perf_event__process_lost,
	.ordered_samples = true,
	.ordering_requires_timestamps = true,
143 144
};

145 146
static void insert_hist_entry_by_name(struct rb_root *root,
				      struct hist_entry *he)
147 148 149 150 151 152 153 154
{
	struct rb_node **p = &root->rb_node;
	struct rb_node *parent = NULL;
	struct hist_entry *iter;

	while (*p != NULL) {
		parent = *p;
		iter = rb_entry(parent, struct hist_entry, rb_node);
155
		if (hist_entry__cmp(he, iter) < 0)
156
			p = &(*p)->rb_left;
157
		else
158 159 160 161 162 163 164
			p = &(*p)->rb_right;
	}

	rb_link_node(&he->rb_node, parent, p);
	rb_insert_color(&he->rb_node, root);
}

165
static void hists__name_resort(struct hists *self, bool sort)
166 167 168
{
	unsigned long position = 1;
	struct rb_root tmp = RB_ROOT;
169
	struct rb_node *next = rb_first(&self->entries);
170 171 172 173 174 175

	while (next != NULL) {
		struct hist_entry *n = rb_entry(next, struct hist_entry, rb_node);

		next = rb_next(&n->rb_node);
		n->position = position++;
176 177 178 179 180

		if (sort) {
			rb_erase(&n->rb_node, &self->entries);
			insert_hist_entry_by_name(&tmp, n);
		}
181 182
	}

183 184
	if (sort)
		self->entries = tmp;
185 186
}

187 188
static struct hist_entry *hists__find_entry(struct hists *self,
					    struct hist_entry *he)
189
{
190
	struct rb_node *n = self->entries.rb_node;
191 192 193

	while (n) {
		struct hist_entry *iter = rb_entry(n, struct hist_entry, rb_node);
194
		int64_t cmp = hist_entry__cmp(he, iter);
195

196
		if (cmp < 0)
197
			n = n->rb_left;
198
		else if (cmp > 0)
199
			n = n->rb_right;
200
		else
201
			return iter;
202 203 204 205 206
	}

	return NULL;
}

207
static void hists__match(struct hists *older, struct hists *newer)
208 209 210
{
	struct rb_node *nd;

211
	for (nd = rb_first(&newer->entries); nd; nd = rb_next(nd)) {
212
		struct hist_entry *pos = rb_entry(nd, struct hist_entry, rb_node);
213
		pos->pair = hists__find_entry(older, pos);
214 215 216
	}
}

217 218 219 220 221 222 223 224 225 226 227 228
static struct perf_evsel *evsel_match(struct perf_evsel *evsel,
				      struct perf_evlist *evlist)
{
	struct perf_evsel *e;

	list_for_each_entry(e, &evlist->entries, node)
		if (perf_evsel__match2(evsel, e))
			return e;

	return NULL;
}

229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
static void perf_evlist__resort_hists(struct perf_evlist *evlist, bool name)
{
	struct perf_evsel *evsel;

	list_for_each_entry(evsel, &evlist->entries, node) {
		struct hists *hists = &evsel->hists;

		hists__output_resort(hists);

		/*
		 * The hists__name_resort only sets possition
		 * if name is false.
		 */
		if (name || ((!name) && show_displacement))
			hists__name_resort(hists, name);
	}
}

247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
static void hists__baseline_only(struct hists *hists)
{
	struct rb_node *next = rb_first(&hists->entries);

	while (next != NULL) {
		struct hist_entry *he = rb_entry(next, struct hist_entry, rb_node);

		next = rb_next(&he->rb_node);
		if (!he->pair) {
			rb_erase(&he->rb_node, &hists->entries);
			hist_entry__free(he);
		}
	}
}

262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
static void hists__precompute(struct hists *hists)
{
	struct rb_node *next = rb_first(&hists->entries);

	while (next != NULL) {
		struct hist_entry *he = rb_entry(next, struct hist_entry, rb_node);

		next = rb_next(&he->rb_node);

		switch (compute) {
		case COMPUTE_DELTA:
			perf_diff__compute_delta(he);
			break;
		case COMPUTE_RATIO:
			perf_diff__compute_ratio(he);
			break;
		default:
			BUG_ON(1);
		}
	}
}

static int64_t cmp_doubles(double l, double r)
{
	if (l > r)
		return -1;
	else if (l < r)
		return 1;
	else
		return 0;
}

static int64_t
hist_entry__cmp_compute(struct hist_entry *left, struct hist_entry *right,
			int c)
{
	switch (c) {
	case COMPUTE_DELTA:
	{
		double l = left->diff.period_ratio_delta;
		double r = right->diff.period_ratio_delta;

		return cmp_doubles(l, r);
	}
	case COMPUTE_RATIO:
	{
		double l = left->diff.period_ratio;
		double r = right->diff.period_ratio;

		return cmp_doubles(l, r);
	}
	default:
		BUG_ON(1);
	}

	return 0;
}

static void insert_hist_entry_by_compute(struct rb_root *root,
					 struct hist_entry *he,
					 int c)
{
	struct rb_node **p = &root->rb_node;
	struct rb_node *parent = NULL;
	struct hist_entry *iter;

	while (*p != NULL) {
		parent = *p;
		iter = rb_entry(parent, struct hist_entry, rb_node);
		if (hist_entry__cmp_compute(he, iter, c) < 0)
			p = &(*p)->rb_left;
		else
			p = &(*p)->rb_right;
	}

	rb_link_node(&he->rb_node, parent, p);
	rb_insert_color(&he->rb_node, root);
}

static void hists__compute_resort(struct hists *hists)
{
	struct rb_root tmp = RB_ROOT;
	struct rb_node *next = rb_first(&hists->entries);

	while (next != NULL) {
		struct hist_entry *he = rb_entry(next, struct hist_entry, rb_node);

		next = rb_next(&he->rb_node);

		rb_erase(&he->rb_node, &hists->entries);
		insert_hist_entry_by_compute(&tmp, he, compute);
	}

	hists->entries = tmp;
}

358 359 360 361 362 363 364
static void hists__process(struct hists *old, struct hists *new)
{
	hists__match(old, new);

	if (show_baseline_only)
		hists__baseline_only(new);

365 366 367 368 369
	if (sort_compute) {
		hists__precompute(new);
		hists__compute_resort(new);
	}

370 371 372
	hists__fprintf(new, true, 0, 0, stdout);
}

373 374 375
static int __cmd_diff(void)
{
	int ret, i;
376 377
#define older (session[0])
#define newer (session[1])
378
	struct perf_session *session[2];
379 380 381
	struct perf_evlist *evlist_new, *evlist_old;
	struct perf_evsel *evsel;
	bool first = true;
382

383
	older = perf_session__new(input_old, O_RDONLY, force, false,
384
				  &tool);
385
	newer = perf_session__new(input_new, O_RDONLY, force, false,
386
				  &tool);
387 388 389 390
	if (session[0] == NULL || session[1] == NULL)
		return -ENOMEM;

	for (i = 0; i < 2; ++i) {
391
		ret = perf_session__process_events(session[i], &tool);
392 393 394 395
		if (ret)
			goto out_delete;
	}

396 397 398
	evlist_old = older->evlist;
	evlist_new = newer->evlist;

399 400
	perf_evlist__resort_hists(evlist_old, true);
	perf_evlist__resort_hists(evlist_new, false);
401 402 403 404 405 406 407 408 409 410 411 412 413

	list_for_each_entry(evsel, &evlist_new->entries, node) {
		struct perf_evsel *evsel_old;

		evsel_old = evsel_match(evsel, evlist_old);
		if (!evsel_old)
			continue;

		fprintf(stdout, "%s# Event '%s'\n#\n", first ? "" : "\n",
			perf_evsel__name(evsel));

		first = false;

414
		hists__process(&evsel_old->hists, &evsel->hists);
415
	}
416

417 418 419 420
out_delete:
	for (i = 0; i < 2; ++i)
		perf_session__delete(session[i]);
	return ret;
421 422
#undef older
#undef newer
423 424
}

425
static const char * const diff_usage[] = {
426
	"perf diff [<options>] [old_file] [new_file]",
427
	NULL,
428 429 430
};

static const struct option options[] = {
431
	OPT_INCR('v', "verbose", &verbose,
432
		    "be more verbose (show symbol address, etc)"),
433
	OPT_BOOLEAN('M', "displacement", &show_displacement,
434
		    "Show position displacement relative to baseline"),
435 436
	OPT_BOOLEAN('b', "baseline-only", &show_baseline_only,
		    "Show only items with match in baseline"),
437 438 439
	OPT_CALLBACK('c', "compute", &compute, "delta,ratio (default delta)",
		     "Entries differential computation selection",
		     setup_compute),
440 441 442 443 444
	OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace,
		    "dump raw trace in ASCII"),
	OPT_BOOLEAN('f', "force", &force, "don't complain, do it"),
	OPT_BOOLEAN('m', "modules", &symbol_conf.use_modules,
		    "load module symbols - WARNING: use only with -k and LIVE kernel"),
445 446 447 448 449 450
	OPT_STRING('d', "dsos", &symbol_conf.dso_list_str, "dso[,dso...]",
		   "only consider symbols in these dsos"),
	OPT_STRING('C', "comms", &symbol_conf.comm_list_str, "comm[,comm...]",
		   "only consider symbols in these comms"),
	OPT_STRING('S', "symbols", &symbol_conf.sym_list_str, "symbol[,symbol...]",
		   "only consider these symbols"),
451 452 453 454 455
	OPT_STRING('s', "sort", &sort_order, "key[,key2...]",
		   "sort by key(s): pid, comm, dso, symbol, parent"),
	OPT_STRING('t', "field-separator", &symbol_conf.field_sep, "separator",
		   "separator for columns, no spaces will be added between "
		   "columns '.' is reserved."),
456 457
	OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory",
		    "Look for files with symbols relative to this directory"),
458 459 460
	OPT_END()
};

461 462 463 464 465 466 467
static void ui_init(void)
{
	perf_hpp__init();

	/* No overhead column. */
	perf_hpp__column_enable(PERF_HPP__OVERHEAD, false);

468
	/* Display baseline/delta/ratio/displacement columns. */
469
	perf_hpp__column_enable(PERF_HPP__BASELINE, true);
470 471 472 473 474 475 476 477 478 479 480

	switch (compute) {
	case COMPUTE_DELTA:
		perf_hpp__column_enable(PERF_HPP__DELTA, true);
		break;
	case COMPUTE_RATIO:
		perf_hpp__column_enable(PERF_HPP__RATIO, true);
		break;
	default:
		BUG_ON(1);
	};
481 482 483 484 485

	if (show_displacement)
		perf_hpp__column_enable(PERF_HPP__DISPL, true);
}

486
int cmd_diff(int argc, const char **argv, const char *prefix __maybe_unused)
487
{
488
	sort_order = diff__default_sort_order;
489 490 491 492 493 494 495 496 497
	argc = parse_options(argc, argv, options, diff_usage, 0);
	if (argc) {
		if (argc > 2)
			usage_with_options(diff_usage, options);
		if (argc == 2) {
			input_old = argv[0];
			input_new = argv[1];
		} else
			input_new = argv[0];
498 499 500 501
	} else if (symbol_conf.default_guest_vmlinux_name ||
		   symbol_conf.default_guest_kallsyms) {
		input_old = "perf.data.host";
		input_new = "perf.data.guest";
502 503
	}

504
	symbol_conf.exclude_other = false;
505 506 507
	if (symbol__init() < 0)
		return -1;

508 509
	ui_init();

510
	setup_sorting(diff_usage, options);
511
	setup_pager();
512 513 514 515 516

	sort_entry__setup_elide(&sort_dso, symbol_conf.dso_list, "dso", NULL);
	sort_entry__setup_elide(&sort_comm, symbol_conf.comm_list, "comm", NULL);
	sort_entry__setup_elide(&sort_sym, symbol_conf.sym_list, "symbol", NULL);

517 518
	return __cmd_diff();
}