builtin-diff.c 14.7 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 new_percent = perf_diff__period_percent(he, he->stat.period);
	double old_percent = perf_diff__period_percent(pair, pair->stat.period);
172 173 174 175 176 177

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

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

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

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

	he->diff.computed = true;
194 195
	he->diff.wdiff = new_period * compute_wdiff_w2 -
			 old_period * compute_wdiff_w1;
196 197 198 199

	return he->diff.wdiff;
}

200 201
static int formula_delta(struct hist_entry *he, struct hist_entry *pair,
			 char *buf, size_t size)
202 203 204 205 206 207 208 209
{
	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);
}

210 211
static int formula_ratio(struct hist_entry *he, struct hist_entry *pair,
			 char *buf, size_t size)
212 213
{
	double new_period = he->stat.period;
214
	double old_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
{
	u64 new_period = he->stat.period;
223
	u64 old_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 466
static void hists__process(struct hists *old, struct hists *new)
{
467
	hists__match(new, old);
468 469 470

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

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

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

484
static void data_process(void)
485
{
486 487 488
	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;
489
	bool first = true;
490

491 492
	list_for_each_entry(evsel_old, &evlist_old->entries, node) {
		struct perf_evsel *evsel_new;
493

494 495 496
		evsel_new = evsel_match(evsel_old, evlist_new);
		if (!evsel_new)
			continue;
497

498 499
		fprintf(stdout, "%s# Event '%s'\n#\n", first ? "" : "\n",
			perf_evsel__name(evsel_old));
500

501
		first = false;
502

503 504 505
		hists__process(&evsel_old->hists, &evsel_new->hists);
	}
}
506

507 508 509 510 511 512 513 514 515 516 517 518 519
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;
		}
520

521 522 523 524 525
		ret = perf_session__process_events(d->session, &tool);
		if (ret) {
			pr_err("Failed to process %s\n", d->file);
			goto out_delete;
		}
526

527 528 529 530
		perf_evlist__collapse_resort(d->session->evlist);
	}

	data_process();
531

532 533 534 535
 out_delete:
	data__for_each_file(i, d) {
		if (d->session)
			perf_session__delete(d->session);
536
	}
537

538
	free(data__files);
539 540 541
	return ret;
}

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

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

581 582
static void ui_init(void)
{
583
	/*
584
	 * Display baseline/delta/ratio
585 586
	 * formula/periods columns.
	 */
587
	perf_hpp__column_enable(PERF_HPP__BASELINE);
588 589 590

	switch (compute) {
	case COMPUTE_DELTA:
591
		perf_hpp__column_enable(PERF_HPP__DELTA);
592 593
		break;
	case COMPUTE_RATIO:
594
		perf_hpp__column_enable(PERF_HPP__RATIO);
595 596
		break;
	case COMPUTE_WEIGHTED_DIFF:
597
		perf_hpp__column_enable(PERF_HPP__WEIGHTED_DIFF);
598 599 600 601
		break;
	default:
		BUG_ON(1);
	};
602

603
	if (show_formula)
604
		perf_hpp__column_enable(PERF_HPP__FORMULA);
605

606
	if (show_period) {
607 608
		perf_hpp__column_enable(PERF_HPP__PERIOD);
		perf_hpp__column_enable(PERF_HPP__PERIOD_BASELINE);
609
	}
610 611
}

612
static int data_init(int argc, const char **argv)
613
{
614 615 616 617 618 619 620 621 622
	struct data__file *d;
	static const char *defaults[] = {
		"perf.data.old",
		"perf.data",
	};
	int i;

	data__files_cnt = 2;

623 624 625 626
	if (argc) {
		if (argc > 2)
			usage_with_options(diff_usage, options);
		if (argc == 2) {
627 628
			defaults[0] = argv[0];
			defaults[1] = argv[1];
629
		} else
630
			defaults[1] = argv[0];
631 632
	} else if (symbol_conf.default_guest_vmlinux_name ||
		   symbol_conf.default_guest_kallsyms) {
633 634
		defaults[0] = "perf.data.host";
		defaults[1] = "perf.data.guest";
635 636
	}

637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653
	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);

654 655 656
	if (symbol__init() < 0)
		return -1;

657 658 659
	if (data_init(argc, argv) < 0)
		return -1;

660 661
	ui_init();

662 663 664
	if (setup_sorting() < 0)
		usage_with_options(diff_usage, options);

665
	setup_pager();
666

667
	sort__setup_elide(NULL);
668

669 670
	return __cmd_diff();
}