builtin-diff.c 15.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_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, char *buf, size_t size)
{
189
	struct hist_entry *pair = hist_entry__next_pair(he);
190 191 192 193 194 195 196 197 198 199 200 201 202

	if (!pair)
		return -1;

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

static int formula_ratio(struct hist_entry *he, char *buf, size_t size)
{
203
	struct hist_entry *pair = hist_entry__next_pair(he);
204 205 206 207 208 209 210 211 212 213 214
	double new_period = he->stat.period;
	double old_period = pair ? pair->stat.period : 0;

	if (!pair)
		return -1;

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

static int formula_wdiff(struct hist_entry *he, char *buf, size_t size)
{
215
	struct hist_entry *pair = hist_entry__next_pair(he);
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
	u64 new_period = he->stat.period;
	u64 old_period = pair ? pair->stat.period : 0;

	if (!pair)
		return -1;

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

int perf_diff__formula(char *buf, size_t size, struct hist_entry *he)
{
	switch (compute) {
	case COMPUTE_DELTA:
		return formula_delta(he, buf, size);
	case COMPUTE_RATIO:
		return formula_ratio(he, buf, size);
	case COMPUTE_WEIGHTED_DIFF:
		return formula_wdiff(he, buf, size);
	default:
		BUG_ON(1);
	}

	return -1;
}

243
static int hists__add_entry(struct hists *self,
244
			    struct addr_location *al, u64 period)
245
{
246
	if (__hists__add_entry(self, al, NULL, period) != NULL)
247 248
		return 0;
	return -ENOMEM;
249 250
}

251
static int diff__process_sample_event(struct perf_tool *tool __maybe_unused,
252
				      union perf_event *event,
253
				      struct perf_sample *sample,
254
				      struct perf_evsel *evsel,
255
				      struct machine *machine)
256 257 258
{
	struct addr_location al;

259
	if (perf_event__preprocess_sample(event, machine, &al, sample, NULL) < 0) {
260 261 262 263 264
		pr_warning("problem processing %d event, skipping it.\n",
			   event->header.type);
		return -1;
	}

265
	if (al.filtered)
266 267
		return 0;

268
	if (hists__add_entry(&evsel->hists, &al, sample->period)) {
269
		pr_warning("problem incrementing symbol period, skipping event\n");
270 271 272
		return -1;
	}

273
	evsel->hists.stats.total_period += sample->period;
274 275 276
	return 0;
}

277 278 279 280
static struct perf_tool tool = {
	.sample	= diff__process_sample_event,
	.mmap	= perf_event__process_mmap,
	.comm	= perf_event__process_comm,
281 282
	.exit	= perf_event__process_exit,
	.fork	= perf_event__process_fork,
283 284 285
	.lost	= perf_event__process_lost,
	.ordered_samples = true,
	.ordering_requires_timestamps = true,
286 287
};

288 289
static void insert_hist_entry_by_name(struct rb_root *root,
				      struct hist_entry *he)
290 291 292 293 294 295 296 297
{
	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);
298
		if (hist_entry__cmp(he, iter) < 0)
299
			p = &(*p)->rb_left;
300
		else
301 302 303 304 305 306 307
			p = &(*p)->rb_right;
	}

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

308
static void hists__name_resort(struct hists *self, bool sort)
309 310 311
{
	unsigned long position = 1;
	struct rb_root tmp = RB_ROOT;
312
	struct rb_node *next = rb_first(&self->entries);
313 314 315 316 317 318

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

		next = rb_next(&n->rb_node);
		n->position = position++;
319 320 321 322 323

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

326 327
	if (sort)
		self->entries = tmp;
328 329
}

330 331 332 333 334 335 336 337 338 339 340 341
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;
}

342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359
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);
	}
}

360 361 362 363 364 365 366 367
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);
368
		if (!hist_entry__next_pair(he)) {
369 370 371 372 373 374
			rb_erase(&he->rb_node, &hists->entries);
			hist_entry__free(he);
		}
	}
}

375 376 377 378 379 380
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);
381
		struct hist_entry *pair = hist_entry__next_pair(he);
382 383

		next = rb_next(&he->rb_node);
384 385
		if (!pair)
			continue;
386 387 388

		switch (compute) {
		case COMPUTE_DELTA:
389
			perf_diff__compute_delta(he, pair);
390 391
			break;
		case COMPUTE_RATIO:
392
			perf_diff__compute_ratio(he, pair);
393
			break;
394
		case COMPUTE_WEIGHTED_DIFF:
395
			perf_diff__compute_wdiff(he, pair);
396
			break;
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 423 424 425 426 427 428 429 430 431
		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);
	}
432 433 434 435 436 437 438
	case COMPUTE_WEIGHTED_DIFF:
	{
		s64 l = left->diff.wdiff;
		s64 r = right->diff.wdiff;

		return r - l;
	}
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 475 476 477 478 479 480 481 482 483
	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;
}

484 485
static void hists__process(struct hists *old, struct hists *new)
{
486
	hists__match(new, old);
487 488 489

	if (show_baseline_only)
		hists__baseline_only(new);
490 491
	else
		hists__link(new, old);
492

493 494 495 496 497
	if (sort_compute) {
		hists__precompute(new);
		hists__compute_resort(new);
	}

498 499 500
	hists__fprintf(new, true, 0, 0, stdout);
}

501 502 503
static int __cmd_diff(void)
{
	int ret, i;
504 505
#define older (session[0])
#define newer (session[1])
506
	struct perf_session *session[2];
507 508 509
	struct perf_evlist *evlist_new, *evlist_old;
	struct perf_evsel *evsel;
	bool first = true;
510

511
	older = perf_session__new(input_old, O_RDONLY, force, false,
512
				  &tool);
513
	newer = perf_session__new(input_new, O_RDONLY, force, false,
514
				  &tool);
515 516 517 518
	if (session[0] == NULL || session[1] == NULL)
		return -ENOMEM;

	for (i = 0; i < 2; ++i) {
519
		ret = perf_session__process_events(session[i], &tool);
520 521 522 523
		if (ret)
			goto out_delete;
	}

524 525 526
	evlist_old = older->evlist;
	evlist_new = newer->evlist;

527 528
	perf_evlist__resort_hists(evlist_old, true);
	perf_evlist__resort_hists(evlist_new, false);
529 530 531 532 533 534 535 536 537 538 539 540 541

	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;

542
		hists__process(&evsel_old->hists, &evsel->hists);
543
	}
544

545 546 547 548
out_delete:
	for (i = 0; i < 2; ++i)
		perf_session__delete(session[i]);
	return ret;
549 550
#undef older
#undef newer
551 552
}

553
static const char * const diff_usage[] = {
554
	"perf diff [<options>] [old_file] [new_file]",
555
	NULL,
556 557 558
};

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

594 595
static void ui_init(void)
{
596 597 598 599
	/*
	 * Display baseline/delta/ratio/displacement/
	 * formula/periods columns.
	 */
600
	perf_hpp__column_enable(PERF_HPP__BASELINE);
601 602 603

	switch (compute) {
	case COMPUTE_DELTA:
604
		perf_hpp__column_enable(PERF_HPP__DELTA);
605 606
		break;
	case COMPUTE_RATIO:
607
		perf_hpp__column_enable(PERF_HPP__RATIO);
608 609
		break;
	case COMPUTE_WEIGHTED_DIFF:
610
		perf_hpp__column_enable(PERF_HPP__WEIGHTED_DIFF);
611 612 613 614
		break;
	default:
		BUG_ON(1);
	};
615 616

	if (show_displacement)
617
		perf_hpp__column_enable(PERF_HPP__DISPL);
618

619
	if (show_formula)
620
		perf_hpp__column_enable(PERF_HPP__FORMULA);
621

622
	if (show_period) {
623 624
		perf_hpp__column_enable(PERF_HPP__PERIOD);
		perf_hpp__column_enable(PERF_HPP__PERIOD_BASELINE);
625
	}
626 627
}

628
int cmd_diff(int argc, const char **argv, const char *prefix __maybe_unused)
629
{
630
	sort_order = diff__default_sort_order;
631 632 633 634 635 636 637 638 639
	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];
640 641 642 643
	} else if (symbol_conf.default_guest_vmlinux_name ||
		   symbol_conf.default_guest_kallsyms) {
		input_old = "perf.data.host";
		input_new = "perf.data.guest";
644 645
	}

646
	symbol_conf.exclude_other = false;
647 648 649
	if (symbol__init() < 0)
		return -1;

650 651
	ui_init();

652
	setup_sorting(diff_usage, options);
653
	setup_pager();
654 655 656 657 658

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

659 660
	return __cmd_diff();
}