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 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
struct data__file {
	struct perf_session	*session;
	const char		*file;
	int			 idx;
};

static struct data__file *data__files;
static int data__files_cnt;

#define data__for_each_file_start(i, d, s)	\
	for (i = s, d = &data__files[s];	\
	     i < data__files_cnt;		\
	     i++, d = &data__files[i])

#define data__for_each_file(i, d) data__for_each_file_start(i, d, 0)

static char diff__default_sort_order[] = "dso,symbol";
static bool force;
40
static bool show_period;
41
static bool show_formula;
42
static bool show_baseline_only;
43
static bool sort_compute;
44

45 46 47
static s64 compute_wdiff_w1;
static s64 compute_wdiff_w2;

48 49 50
enum {
	COMPUTE_DELTA,
	COMPUTE_RATIO,
51
	COMPUTE_WEIGHTED_DIFF,
52 53 54 55 56 57
	COMPUTE_MAX,
};

const char *compute_names[COMPUTE_MAX] = {
	[COMPUTE_DELTA] = "delta",
	[COMPUTE_RATIO] = "ratio",
58
	[COMPUTE_WEIGHTED_DIFF] = "wdiff",
59 60 61 62
};

static int compute;

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 99 100 101 102 103 104 105 106 107 108 109 110 111
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;
}

112 113 114 115
static int setup_compute(const struct option *opt, const char *str,
			 int unset __maybe_unused)
{
	int *cp = (int *) opt->value;
116 117
	char *cstr = (char *) str;
	char buf[50];
118
	unsigned i;
119
	char *option;
120 121 122 123 124 125

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

126 127
	if (*str == '+') {
		sort_compute = true;
128
		cstr = (char *) ++str;
129 130 131 132
		if (!*str)
			return 0;
	}

133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
	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;
	}

151
	for (i = 0; i < COMPUTE_MAX; i++)
152
		if (!strcmp(cstr, compute_names[i])) {
153
			*cp = i;
154
			return setup_compute_opt(option);
155 156 157
		}

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

162
double perf_diff__period_percent(struct hist_entry *he, u64 period)
163 164 165 166 167
{
	u64 total = he->hists->stats.total_period;
	return (period * 100.0) / total;
}

168
double perf_diff__compute_delta(struct hist_entry *he, struct hist_entry *pair)
169
{
170 171
	double old_percent = perf_diff__period_percent(he, he->stat.period);
	double new_percent = perf_diff__period_percent(pair, pair->stat.period);
172

173 174 175
	pair->diff.period_ratio_delta = new_percent - old_percent;
	pair->diff.computed = true;
	return pair->diff.period_ratio_delta;
176 177
}

178
double perf_diff__compute_ratio(struct hist_entry *he, struct hist_entry *pair)
179
{
180 181
	double old_period = he->stat.period ?: 1;
	double new_period = pair->stat.period;
182

183 184 185
	pair->diff.computed = true;
	pair->diff.period_ratio = new_period / old_period;
	return pair->diff.period_ratio;
186 187
}

188
s64 perf_diff__compute_wdiff(struct hist_entry *he, struct hist_entry *pair)
189
{
190 191
	u64 old_period = he->stat.period;
	u64 new_period = pair->stat.period;
192

193 194 195
	pair->diff.computed = true;
	pair->diff.wdiff = new_period * compute_wdiff_w2 -
			   old_period * compute_wdiff_w1;
196

197
	return pair->diff.wdiff;
198 199
}

200 201
static int formula_delta(struct hist_entry *he, struct hist_entry *pair,
			 char *buf, size_t size)
202 203 204 205
{
	return scnprintf(buf, size,
			 "(%" PRIu64 " * 100 / %" PRIu64 ") - "
			 "(%" PRIu64 " * 100 / %" PRIu64 ")",
206 207
			  pair->stat.period, pair->hists->stats.total_period,
			  he->stat.period, he->hists->stats.total_period);
208 209
}

210 211
static int formula_ratio(struct hist_entry *he, struct hist_entry *pair,
			 char *buf, size_t size)
212
{
213 214
	double old_period = he->stat.period;
	double new_period = pair->stat.period;
215 216 217 218

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

219 220
static int formula_wdiff(struct hist_entry *he, struct hist_entry *pair,
			 char *buf, size_t size)
221
{
222 223
	u64 old_period = he->stat.period;
	u64 new_period = pair->stat.period;
224 225 226 227 228 229

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

230 231
int perf_diff__formula(struct hist_entry *he, struct hist_entry *pair,
		       char *buf, size_t size)
232 233 234
{
	switch (compute) {
	case COMPUTE_DELTA:
235
		return formula_delta(he, pair, buf, size);
236
	case COMPUTE_RATIO:
237
		return formula_ratio(he, pair, buf, size);
238
	case COMPUTE_WEIGHTED_DIFF:
239
		return formula_wdiff(he, pair, buf, size);
240 241 242 243 244 245 246
	default:
		BUG_ON(1);
	}

	return -1;
}

247
static int hists__add_entry(struct hists *self,
248 249
			    struct addr_location *al, u64 period,
			    u64 weight)
250
{
251
	if (__hists__add_entry(self, al, NULL, period, weight) != NULL)
252 253
		return 0;
	return -ENOMEM;
254 255
}

256
static int diff__process_sample_event(struct perf_tool *tool __maybe_unused,
257
				      union perf_event *event,
258
				      struct perf_sample *sample,
259
				      struct perf_evsel *evsel,
260
				      struct machine *machine)
261 262 263
{
	struct addr_location al;

264
	if (perf_event__preprocess_sample(event, machine, &al, sample, NULL) < 0) {
265 266 267 268 269
		pr_warning("problem processing %d event, skipping it.\n",
			   event->header.type);
		return -1;
	}

270
	if (al.filtered)
271 272
		return 0;

273
	if (hists__add_entry(&evsel->hists, &al, sample->period, sample->weight)) {
274
		pr_warning("problem incrementing symbol period, skipping event\n");
275 276 277
		return -1;
	}

278
	evsel->hists.stats.total_period += sample->period;
279 280 281
	return 0;
}

282 283 284 285
static struct perf_tool tool = {
	.sample	= diff__process_sample_event,
	.mmap	= perf_event__process_mmap,
	.comm	= perf_event__process_comm,
286 287
	.exit	= perf_event__process_exit,
	.fork	= perf_event__process_fork,
288 289 290
	.lost	= perf_event__process_lost,
	.ordered_samples = true,
	.ordering_requires_timestamps = true,
291 292
};

293 294 295 296 297 298 299 300 301 302 303 304
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;
}

305
static void perf_evlist__collapse_resort(struct perf_evlist *evlist)
306 307 308 309 310 311
{
	struct perf_evsel *evsel;

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

312
		hists__collapse_resort(hists);
313 314 315
	}
}

316 317
static void hists__baseline_only(struct hists *hists)
{
318 319 320 321 322 323 324
	struct rb_root *root;
	struct rb_node *next;

	if (sort__need_collapse)
		root = &hists->entries_collapsed;
	else
		root = hists->entries_in;
325

326
	next = rb_first(root);
327
	while (next != NULL) {
328
		struct hist_entry *he = rb_entry(next, struct hist_entry, rb_node_in);
329

330
		next = rb_next(&he->rb_node_in);
331
		if (!hist_entry__next_pair(he)) {
332
			rb_erase(&he->rb_node_in, root);
333 334 335 336 337
			hist_entry__free(he);
		}
	}
}

338 339
static void hists__precompute(struct hists *hists)
{
340 341 342 343 344 345 346
	struct rb_root *root;
	struct rb_node *next;

	if (sort__need_collapse)
		root = &hists->entries_collapsed;
	else
		root = hists->entries_in;
347

348
	next = rb_first(root);
349
	while (next != NULL) {
350
		struct hist_entry *he = rb_entry(next, struct hist_entry, rb_node_in);
351
		struct hist_entry *pair = hist_entry__next_pair(he);
352

353
		next = rb_next(&he->rb_node_in);
354 355
		if (!pair)
			continue;
356 357 358

		switch (compute) {
		case COMPUTE_DELTA:
359
			perf_diff__compute_delta(he, pair);
360 361
			break;
		case COMPUTE_RATIO:
362
			perf_diff__compute_ratio(he, pair);
363
			break;
364
		case COMPUTE_WEIGHTED_DIFF:
365
			perf_diff__compute_wdiff(he, pair);
366
			break;
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
		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);
	}
402 403 404 405 406 407 408
	case COMPUTE_WEIGHTED_DIFF:
	{
		s64 l = left->diff.wdiff;
		s64 r = right->diff.wdiff;

		return r - l;
	}
409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438
	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)
{
439 440 441 442 443 444 445 446 447 448 449 450 451 452
	struct rb_root *root;
	struct rb_node *next;

	if (sort__need_collapse)
		root = &hists->entries_collapsed;
	else
		root = hists->entries_in;

	hists->entries = RB_ROOT;
	next = rb_first(root);

	hists->nr_entries = 0;
	hists->stats.total_period = 0;
	hists__reset_col_len(hists);
453 454

	while (next != NULL) {
455
		struct hist_entry *he;
456

457 458
		he = rb_entry(next, struct hist_entry, rb_node_in);
		next = rb_next(&he->rb_node_in);
459

460 461
		insert_hist_entry_by_compute(&hists->entries, he, compute);
		hists__inc_nr_entries(hists, he);
462 463 464
	}
}

465
static void hists__process(struct hists *base, struct hists *new)
466
{
467
	hists__match(base, new);
468 469

	if (show_baseline_only)
470
		hists__baseline_only(base);
471
	else
472
		hists__link(base, new);
473

474
	if (sort_compute) {
475 476
		hists__precompute(base);
		hists__compute_resort(base);
477
	} else {
478
		hists__output_resort(base);
479 480
	}

481
	hists__fprintf(base, true, 0, 0, 0, stdout);
482 483
}

484 485 486 487 488 489 490 491 492 493 494 495 496 497 498
static void data__fprintf(void)
{
	struct data__file *d;
	int i;

	fprintf(stdout, "# Data files:\n");

	data__for_each_file(i, d)
		fprintf(stdout, "#  [%d] %s %s\n",
			d->idx, d->file,
			!d->idx ? "(Baseline)" : "");

	fprintf(stdout, "#\n");
}

499
static void data_process(void)
500
{
501 502 503
	struct perf_evlist *evlist_old = data__files[0].session->evlist;
	struct perf_evlist *evlist_new = data__files[1].session->evlist;
	struct perf_evsel *evsel_old;
504
	bool first = true;
505

506 507
	list_for_each_entry(evsel_old, &evlist_old->entries, node) {
		struct perf_evsel *evsel_new;
508

509 510 511
		evsel_new = evsel_match(evsel_old, evlist_new);
		if (!evsel_new)
			continue;
512

513 514
		fprintf(stdout, "%s# Event '%s'\n#\n", first ? "" : "\n",
			perf_evsel__name(evsel_old));
515

516
		first = false;
517

518 519 520
		if (verbose)
			data__fprintf();

521 522 523
		hists__process(&evsel_old->hists, &evsel_new->hists);
	}
}
524

525 526 527 528 529 530 531 532 533 534 535 536 537
static int __cmd_diff(void)
{
	struct data__file *d;
	int ret = -EINVAL, i;

	data__for_each_file(i, d) {
		d->session = perf_session__new(d->file, O_RDONLY, force,
					       false, &tool);
		if (!d->session) {
			pr_err("Failed to open %s\n", d->file);
			ret = -ENOMEM;
			goto out_delete;
		}
538

539 540 541 542 543
		ret = perf_session__process_events(d->session, &tool);
		if (ret) {
			pr_err("Failed to process %s\n", d->file);
			goto out_delete;
		}
544

545 546 547 548
		perf_evlist__collapse_resort(d->session->evlist);
	}

	data_process();
549

550 551 552 553
 out_delete:
	data__for_each_file(i, d) {
		if (d->session)
			perf_session__delete(d->session);
554
	}
555

556
	free(data__files);
557 558 559
	return ret;
}

560
static const char * const diff_usage[] = {
561
	"perf diff [<options>] [old_file] [new_file]",
562
	NULL,
563 564 565
};

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

599 600
static void ui_init(void)
{
601
	/*
602
	 * Display baseline/delta/ratio
603 604
	 * formula/periods columns.
	 */
605
	perf_hpp__column_enable(PERF_HPP__BASELINE);
606 607 608

	switch (compute) {
	case COMPUTE_DELTA:
609
		perf_hpp__column_enable(PERF_HPP__DELTA);
610 611
		break;
	case COMPUTE_RATIO:
612
		perf_hpp__column_enable(PERF_HPP__RATIO);
613 614
		break;
	case COMPUTE_WEIGHTED_DIFF:
615
		perf_hpp__column_enable(PERF_HPP__WEIGHTED_DIFF);
616 617 618 619
		break;
	default:
		BUG_ON(1);
	};
620

621
	if (show_formula)
622
		perf_hpp__column_enable(PERF_HPP__FORMULA);
623

624
	if (show_period) {
625 626
		perf_hpp__column_enable(PERF_HPP__PERIOD);
		perf_hpp__column_enable(PERF_HPP__PERIOD_BASELINE);
627
	}
628 629
}

630
static int data_init(int argc, const char **argv)
631
{
632 633 634 635 636 637 638 639 640
	struct data__file *d;
	static const char *defaults[] = {
		"perf.data.old",
		"perf.data",
	};
	int i;

	data__files_cnt = 2;

641 642 643 644
	if (argc) {
		if (argc > 2)
			usage_with_options(diff_usage, options);
		if (argc == 2) {
645 646
			defaults[0] = argv[0];
			defaults[1] = argv[1];
647
		} else
648
			defaults[1] = argv[0];
649 650
	} else if (symbol_conf.default_guest_vmlinux_name ||
		   symbol_conf.default_guest_kallsyms) {
651 652
		defaults[0] = "perf.data.host";
		defaults[1] = "perf.data.guest";
653 654
	}

655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671
	data__files = zalloc(sizeof(*data__files) * data__files_cnt);
	if (!data__files)
		return -ENOMEM;

	data__for_each_file(i, d) {
		d->file = defaults[i];
		d->idx  = i;
	}

	return 0;
}

int cmd_diff(int argc, const char **argv, const char *prefix __maybe_unused)
{
	sort_order = diff__default_sort_order;
	argc = parse_options(argc, argv, options, diff_usage, 0);

672 673 674
	if (symbol__init() < 0)
		return -1;

675 676 677
	if (data_init(argc, argv) < 0)
		return -1;

678 679
	ui_init();

680 681 682
	if (setup_sorting() < 0)
		usage_with_options(diff_usage, options);

683
	setup_pager();
684

685
	sort__setup_elide(NULL);
686

687 688
	return __cmd_diff();
}