builtin-diff.c 14.9 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_period;
28
static bool show_formula;
29
static bool show_baseline_only;
30
static bool sort_compute;
31

32 33 34
static s64 compute_wdiff_w1;
static s64 compute_wdiff_w2;

35 36 37
enum {
	COMPUTE_DELTA,
	COMPUTE_RATIO,
38
	COMPUTE_WEIGHTED_DIFF,
39 40 41 42 43 44
	COMPUTE_MAX,
};

const char *compute_names[COMPUTE_MAX] = {
	[COMPUTE_DELTA] = "delta",
	[COMPUTE_RATIO] = "ratio",
45
	[COMPUTE_WEIGHTED_DIFF] = "wdiff",
46 47 48 49
};

static int compute;

50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 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
static int setup_compute_opt_wdiff(char *opt)
{
	char *w1_str = opt;
	char *w2_str;

	int ret = -EINVAL;

	if (!opt)
		goto out;

	w2_str = strchr(opt, ',');
	if (!w2_str)
		goto out;

	*w2_str++ = 0x0;
	if (!*w2_str)
		goto out;

	compute_wdiff_w1 = strtol(w1_str, NULL, 10);
	compute_wdiff_w2 = strtol(w2_str, NULL, 10);

	if (!compute_wdiff_w1 || !compute_wdiff_w2)
		goto out;

	pr_debug("compute wdiff w1(%" PRId64 ") w2(%" PRId64 ")\n",
		  compute_wdiff_w1, compute_wdiff_w2);

	ret = 0;

 out:
	if (ret)
		pr_err("Failed: wrong weight data, use 'wdiff:w1,w2'\n");

	return ret;
}

static int setup_compute_opt(char *opt)
{
	if (compute == COMPUTE_WEIGHTED_DIFF)
		return setup_compute_opt_wdiff(opt);

	if (opt) {
		pr_err("Failed: extra option specified '%s'", opt);
		return -EINVAL;
	}

	return 0;
}

99 100 101 102
static int setup_compute(const struct option *opt, const char *str,
			 int unset __maybe_unused)
{
	int *cp = (int *) opt->value;
103 104
	char *cstr = (char *) str;
	char buf[50];
105
	unsigned i;
106
	char *option;
107 108 109 110 111 112

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

113 114
	if (*str == '+') {
		sort_compute = true;
115
		cstr = (char *) ++str;
116 117 118 119
		if (!*str)
			return 0;
	}

120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
	option = strchr(str, ':');
	if (option) {
		unsigned len = option++ - str;

		/*
		 * The str data are not writeable, so we need
		 * to use another buffer.
		 */

		/* No option value is longer. */
		if (len >= sizeof(buf))
			return -EINVAL;

		strncpy(buf, str, len);
		buf[len] = 0x0;
		cstr = buf;
	}

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 147 148
	return -EINVAL;
}

149
double perf_diff__period_percent(struct hist_entry *he, u64 period)
150 151 152 153 154
{
	u64 total = he->hists->stats.total_period;
	return (period * 100.0) / total;
}

155
double perf_diff__compute_delta(struct hist_entry *he, struct hist_entry *pair)
156
{
157 158
	double new_percent = perf_diff__period_percent(he, he->stat.period);
	double old_percent = perf_diff__period_percent(pair, pair->stat.period);
159 160 161 162 163 164

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

165
double perf_diff__compute_ratio(struct hist_entry *he, struct hist_entry *pair)
166 167
{
	double new_period = he->stat.period;
168
	double old_period = pair->stat.period;
169 170

	he->diff.computed = true;
171
	he->diff.period_ratio = new_period / old_period;
172 173 174
	return he->diff.period_ratio;
}

175
s64 perf_diff__compute_wdiff(struct hist_entry *he, struct hist_entry *pair)
176 177
{
	u64 new_period = he->stat.period;
178
	u64 old_period = pair->stat.period;
179 180

	he->diff.computed = true;
181 182
	he->diff.wdiff = new_period * compute_wdiff_w2 -
			 old_period * compute_wdiff_w1;
183 184 185 186

	return he->diff.wdiff;
}

187 188
static int formula_delta(struct hist_entry *he, struct hist_entry *pair,
			 char *buf, size_t size)
189 190 191 192 193 194 195 196
{
	return scnprintf(buf, size,
			 "(%" PRIu64 " * 100 / %" PRIu64 ") - "
			 "(%" PRIu64 " * 100 / %" PRIu64 ")",
			  he->stat.period, he->hists->stats.total_period,
			  pair->stat.period, pair->hists->stats.total_period);
}

197 198
static int formula_ratio(struct hist_entry *he, struct hist_entry *pair,
			 char *buf, size_t size)
199 200
{
	double new_period = he->stat.period;
201
	double old_period = pair->stat.period;
202 203 204 205

	return scnprintf(buf, size, "%.0F / %.0F", new_period, old_period);
}

206 207
static int formula_wdiff(struct hist_entry *he, struct hist_entry *pair,
			 char *buf, size_t size)
208 209
{
	u64 new_period = he->stat.period;
210
	u64 old_period = pair->stat.period;
211 212 213 214 215 216

	return scnprintf(buf, size,
		  "(%" PRIu64 " * " "%" PRId64 ") - (%" PRIu64 " * " "%" PRId64 ")",
		  new_period, compute_wdiff_w2, old_period, compute_wdiff_w1);
}

217 218
int perf_diff__formula(struct hist_entry *he, struct hist_entry *pair,
		       char *buf, size_t size)
219 220 221
{
	switch (compute) {
	case COMPUTE_DELTA:
222
		return formula_delta(he, pair, buf, size);
223
	case COMPUTE_RATIO:
224
		return formula_ratio(he, pair, buf, size);
225
	case COMPUTE_WEIGHTED_DIFF:
226
		return formula_wdiff(he, pair, buf, size);
227 228 229 230 231 232 233
	default:
		BUG_ON(1);
	}

	return -1;
}

234
static int hists__add_entry(struct hists *self,
235
			    struct addr_location *al, u64 period)
236
{
237
	if (__hists__add_entry(self, al, NULL, period) != NULL)
238 239
		return 0;
	return -ENOMEM;
240 241
}

242
static int diff__process_sample_event(struct perf_tool *tool __maybe_unused,
243
				      union perf_event *event,
244
				      struct perf_sample *sample,
245
				      struct perf_evsel *evsel,
246
				      struct machine *machine)
247 248 249
{
	struct addr_location al;

250
	if (perf_event__preprocess_sample(event, machine, &al, sample, NULL) < 0) {
251 252 253 254 255
		pr_warning("problem processing %d event, skipping it.\n",
			   event->header.type);
		return -1;
	}

256
	if (al.filtered)
257 258
		return 0;

259
	if (hists__add_entry(&evsel->hists, &al, sample->period)) {
260
		pr_warning("problem incrementing symbol period, skipping event\n");
261 262 263
		return -1;
	}

264
	evsel->hists.stats.total_period += sample->period;
265 266 267
	return 0;
}

268 269 270 271
static struct perf_tool tool = {
	.sample	= diff__process_sample_event,
	.mmap	= perf_event__process_mmap,
	.comm	= perf_event__process_comm,
272 273
	.exit	= perf_event__process_exit,
	.fork	= perf_event__process_fork,
274 275 276
	.lost	= perf_event__process_lost,
	.ordered_samples = true,
	.ordering_requires_timestamps = true,
277 278
};

279 280
static void insert_hist_entry_by_name(struct rb_root *root,
				      struct hist_entry *he)
281 282 283 284 285 286 287 288
{
	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);
289
		if (hist_entry__cmp(he, iter) < 0)
290
			p = &(*p)->rb_left;
291
		else
292 293 294 295 296 297 298
			p = &(*p)->rb_right;
	}

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

299
static void hists__name_resort(struct hists *self, bool sort)
300 301 302
{
	unsigned long position = 1;
	struct rb_root tmp = RB_ROOT;
303
	struct rb_node *next = rb_first(&self->entries);
304 305 306 307 308 309

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

		next = rb_next(&n->rb_node);
		n->position = position++;
310 311 312 313 314

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

317 318
	if (sort)
		self->entries = tmp;
319 320
}

321 322 323 324 325 326 327 328 329 330 331 332
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;
}

333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
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);
	}
}

351 352 353 354 355 356 357 358
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);
359
		if (!hist_entry__next_pair(he)) {
360 361 362 363 364 365
			rb_erase(&he->rb_node, &hists->entries);
			hist_entry__free(he);
		}
	}
}

366 367 368 369 370 371
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);
372
		struct hist_entry *pair = hist_entry__next_pair(he);
373 374

		next = rb_next(&he->rb_node);
375 376
		if (!pair)
			continue;
377 378 379

		switch (compute) {
		case COMPUTE_DELTA:
380
			perf_diff__compute_delta(he, pair);
381 382
			break;
		case COMPUTE_RATIO:
383
			perf_diff__compute_ratio(he, pair);
384
			break;
385
		case COMPUTE_WEIGHTED_DIFF:
386
			perf_diff__compute_wdiff(he, pair);
387
			break;
388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422
		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);
	}
423 424 425 426 427 428 429
	case COMPUTE_WEIGHTED_DIFF:
	{
		s64 l = left->diff.wdiff;
		s64 r = right->diff.wdiff;

		return r - l;
	}
430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474
	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;
}

475 476
static void hists__process(struct hists *old, struct hists *new)
{
477
	hists__match(new, old);
478 479 480

	if (show_baseline_only)
		hists__baseline_only(new);
481 482
	else
		hists__link(new, old);
483

484 485 486 487 488
	if (sort_compute) {
		hists__precompute(new);
		hists__compute_resort(new);
	}

489 490 491
	hists__fprintf(new, true, 0, 0, stdout);
}

492 493 494
static int __cmd_diff(void)
{
	int ret, i;
495 496
#define older (session[0])
#define newer (session[1])
497
	struct perf_session *session[2];
498 499 500
	struct perf_evlist *evlist_new, *evlist_old;
	struct perf_evsel *evsel;
	bool first = true;
501

502
	older = perf_session__new(input_old, O_RDONLY, force, false,
503
				  &tool);
504
	newer = perf_session__new(input_new, O_RDONLY, force, false,
505
				  &tool);
506 507 508 509
	if (session[0] == NULL || session[1] == NULL)
		return -ENOMEM;

	for (i = 0; i < 2; ++i) {
510
		ret = perf_session__process_events(session[i], &tool);
511 512 513 514
		if (ret)
			goto out_delete;
	}

515 516 517
	evlist_old = older->evlist;
	evlist_new = newer->evlist;

518 519
	perf_evlist__resort_hists(evlist_old, true);
	perf_evlist__resort_hists(evlist_new, false);
520 521 522 523 524 525 526 527 528 529 530 531 532

	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;

533
		hists__process(&evsel_old->hists, &evsel->hists);
534
	}
535

536 537 538 539
out_delete:
	for (i = 0; i < 2; ++i)
		perf_session__delete(session[i]);
	return ret;
540 541
#undef older
#undef newer
542 543
}

544
static const char * const diff_usage[] = {
545
	"perf diff [<options>] [old_file] [new_file]",
546
	NULL,
547 548 549
};

static const struct option options[] = {
550
	OPT_INCR('v', "verbose", &verbose,
551
		    "be more verbose (show symbol address, etc)"),
552
	OPT_BOOLEAN('M', "displacement", &show_displacement,
553
		    "Show position displacement relative to baseline"),
554 555
	OPT_BOOLEAN('b', "baseline-only", &show_baseline_only,
		    "Show only items with match in baseline"),
556 557
	OPT_CALLBACK('c', "compute", &compute,
		     "delta,ratio,wdiff:w1,w2 (default delta)",
558 559
		     "Entries differential computation selection",
		     setup_compute),
560 561
	OPT_BOOLEAN('p', "period", &show_period,
		    "Show period values."),
562 563
	OPT_BOOLEAN('F', "formula", &show_formula,
		    "Show formula."),
564 565 566 567 568
	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"),
569 570 571 572 573 574
	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"),
575 576 577 578 579
	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."),
580 581
	OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory",
		    "Look for files with symbols relative to this directory"),
582 583 584
	OPT_END()
};

585 586
static void ui_init(void)
{
587 588 589 590
	/*
	 * Display baseline/delta/ratio/displacement/
	 * formula/periods columns.
	 */
591
	perf_hpp__column_enable(PERF_HPP__BASELINE);
592 593 594

	switch (compute) {
	case COMPUTE_DELTA:
595
		perf_hpp__column_enable(PERF_HPP__DELTA);
596 597
		break;
	case COMPUTE_RATIO:
598
		perf_hpp__column_enable(PERF_HPP__RATIO);
599 600
		break;
	case COMPUTE_WEIGHTED_DIFF:
601
		perf_hpp__column_enable(PERF_HPP__WEIGHTED_DIFF);
602 603 604 605
		break;
	default:
		BUG_ON(1);
	};
606 607

	if (show_displacement)
608
		perf_hpp__column_enable(PERF_HPP__DISPL);
609

610
	if (show_formula)
611
		perf_hpp__column_enable(PERF_HPP__FORMULA);
612

613
	if (show_period) {
614 615
		perf_hpp__column_enable(PERF_HPP__PERIOD);
		perf_hpp__column_enable(PERF_HPP__PERIOD_BASELINE);
616
	}
617 618
}

619
int cmd_diff(int argc, const char **argv, const char *prefix __maybe_unused)
620
{
621
	sort_order = diff__default_sort_order;
622 623 624 625 626 627 628 629 630
	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];
631 632 633 634
	} else if (symbol_conf.default_guest_vmlinux_name ||
		   symbol_conf.default_guest_kallsyms) {
		input_old = "perf.data.host";
		input_new = "perf.data.guest";
635 636
	}

637
	symbol_conf.exclude_other = false;
638 639 640
	if (symbol__init() < 0)
		return -1;

641 642
	ui_init();

643
	setup_sorting(diff_usage, options);
644
	setup_pager();
645 646 647 648 649

	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);

650 651
	return __cmd_diff();
}