diff options
Diffstat (limited to 'tools/perf/builtin-diff.c')
| -rw-r--r-- | tools/perf/builtin-diff.c | 437 | 
1 files changed, 400 insertions, 37 deletions
| diff --git a/tools/perf/builtin-diff.c b/tools/perf/builtin-diff.c index a0b531c14b97..93b852f8a5d5 100644 --- a/tools/perf/builtin-diff.c +++ b/tools/perf/builtin-diff.c @@ -24,6 +24,228 @@ static char const *input_old = "perf.data.old",  static char	  diff__default_sort_order[] = "dso,symbol";  static bool  force;  static bool show_displacement; +static bool show_period; +static bool show_formula; +static bool show_baseline_only; +static bool sort_compute; + +static s64 compute_wdiff_w1; +static s64 compute_wdiff_w2; + +enum { +	COMPUTE_DELTA, +	COMPUTE_RATIO, +	COMPUTE_WEIGHTED_DIFF, +	COMPUTE_MAX, +}; + +const char *compute_names[COMPUTE_MAX] = { +	[COMPUTE_DELTA] = "delta", +	[COMPUTE_RATIO] = "ratio", +	[COMPUTE_WEIGHTED_DIFF] = "wdiff", +}; + +static int compute; + +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; +} + +static int setup_compute(const struct option *opt, const char *str, +			 int unset __maybe_unused) +{ +	int *cp = (int *) opt->value; +	char *cstr = (char *) str; +	char buf[50]; +	unsigned i; +	char *option; + +	if (!str) { +		*cp = COMPUTE_DELTA; +		return 0; +	} + +	if (*str == '+') { +		sort_compute = true; +		cstr = (char *) ++str; +		if (!*str) +			return 0; +	} + +	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; +	} + +	for (i = 0; i < COMPUTE_MAX; i++) +		if (!strcmp(cstr, compute_names[i])) { +			*cp = i; +			return setup_compute_opt(option); +		} + +	pr_err("Failed: '%s' is not computation method " +	       "(use 'delta','ratio' or 'wdiff')\n", str); +	return -EINVAL; +} + +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 = hist_entry__next_pair(he); +	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 = hist_entry__next_pair(he); +	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; +} + +s64 perf_diff__compute_wdiff(struct hist_entry *he) +{ +	struct hist_entry *pair = hist_entry__next_pair(he); +	u64 new_period = he->stat.period; +	u64 old_period = pair ? pair->stat.period : 0; + +	he->diff.computed = true; + +	if (!pair) +		he->diff.wdiff = 0; +	else +		he->diff.wdiff = new_period * compute_wdiff_w2 - +				 old_period * compute_wdiff_w1; + +	return he->diff.wdiff; +} + +static int formula_delta(struct hist_entry *he, char *buf, size_t size) +{ +	struct hist_entry *pair = hist_entry__next_pair(he); + +	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) +{ +	struct hist_entry *pair = hist_entry__next_pair(he); +	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) +{ +	struct hist_entry *pair = hist_entry__next_pair(he); +	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; +}  static int hists__add_entry(struct hists *self,  			    struct addr_location *al, u64 period) @@ -47,7 +269,7 @@ static int diff__process_sample_event(struct perf_tool *tool __maybe_unused,  		return -1;  	} -	if (al.filtered || al.sym == NULL) +	if (al.filtered)  		return 0;  	if (hists__add_entry(&evsel->hists, &al, sample->period)) { @@ -63,8 +285,8 @@ 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, +	.exit	= perf_event__process_exit, +	.fork	= perf_event__process_fork,  	.lost	= perf_event__process_lost,  	.ordered_samples = true,  	.ordering_requires_timestamps = true, @@ -112,36 +334,6 @@ static void hists__name_resort(struct hists *self, bool sort)  		self->entries = tmp;  } -static struct hist_entry *hists__find_entry(struct hists *self, -					    struct hist_entry *he) -{ -	struct rb_node *n = self->entries.rb_node; - -	while (n) { -		struct hist_entry *iter = rb_entry(n, struct hist_entry, rb_node); -		int64_t cmp = hist_entry__cmp(he, iter); - -		if (cmp < 0) -			n = n->rb_left; -		else if (cmp > 0) -			n = n->rb_right; -		else -			return iter; -	} - -	return NULL; -} - -static void hists__match(struct hists *older, struct hists *newer) -{ -	struct rb_node *nd; - -	for (nd = rb_first(&newer->entries); nd; nd = rb_next(nd)) { -		struct hist_entry *pos = rb_entry(nd, struct hist_entry, rb_node); -		pos->pair = hists__find_entry(older, pos); -	} -} -  static struct perf_evsel *evsel_match(struct perf_evsel *evsel,  				      struct perf_evlist *evlist)  { @@ -172,6 +364,144 @@ static void perf_evlist__resort_hists(struct perf_evlist *evlist, bool name)  	}  } +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 (!hist_entry__next_pair(he)) { +			rb_erase(&he->rb_node, &hists->entries); +			hist_entry__free(he); +		} +	} +} + +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; +		case COMPUTE_WEIGHTED_DIFF: +			perf_diff__compute_wdiff(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); +	} +	case COMPUTE_WEIGHTED_DIFF: +	{ +		s64 l = left->diff.wdiff; +		s64 r = right->diff.wdiff; + +		return r - l; +	} +	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; +} + +static void hists__process(struct hists *old, struct hists *new) +{ +	hists__match(new, old); + +	if (show_baseline_only) +		hists__baseline_only(new); +	else +		hists__link(new, old); + +	if (sort_compute) { +		hists__precompute(new); +		hists__compute_resort(new); +	} + +	hists__fprintf(new, true, 0, 0, stdout); +} +  static int __cmd_diff(void)  {  	int ret, i; @@ -213,8 +543,7 @@ static int __cmd_diff(void)  		first = false; -		hists__match(&evsel_old->hists, &evsel->hists); -		hists__fprintf(&evsel->hists, true, 0, 0, stdout); +		hists__process(&evsel_old->hists, &evsel->hists);  	}  out_delete: @@ -235,6 +564,16 @@ static const struct option options[] = {  		    "be more verbose (show symbol address, etc)"),  	OPT_BOOLEAN('M', "displacement", &show_displacement,  		    "Show position displacement relative to baseline"), +	OPT_BOOLEAN('b', "baseline-only", &show_baseline_only, +		    "Show only items with match in baseline"), +	OPT_CALLBACK('c', "compute", &compute, +		     "delta,ratio,wdiff:w1,w2 (default delta)", +		     "Entries differential computation selection", +		     setup_compute), +	OPT_BOOLEAN('p', "period", &show_period, +		    "Show period values."), +	OPT_BOOLEAN('F', "formula", &show_formula, +		    "Show formula."),  	OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace,  		    "dump raw trace in ASCII"),  	OPT_BOOLEAN('f', "force", &force, "don't complain, do it"), @@ -263,12 +602,36 @@ static void ui_init(void)  	/* No overhead column. */  	perf_hpp__column_enable(PERF_HPP__OVERHEAD, false); -	/* Display baseline/delta/displacement columns. */ +	/* +	 * Display baseline/delta/ratio/displacement/ +	 * formula/periods columns. +	 */  	perf_hpp__column_enable(PERF_HPP__BASELINE, true); -	perf_hpp__column_enable(PERF_HPP__DELTA, true); + +	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; +	case COMPUTE_WEIGHTED_DIFF: +		perf_hpp__column_enable(PERF_HPP__WEIGHTED_DIFF, true); +		break; +	default: +		BUG_ON(1); +	};  	if (show_displacement)  		perf_hpp__column_enable(PERF_HPP__DISPL, true); + +	if (show_formula) +		perf_hpp__column_enable(PERF_HPP__FORMULA, true); + +	if (show_period) { +		perf_hpp__column_enable(PERF_HPP__PERIOD, true); +		perf_hpp__column_enable(PERF_HPP__PERIOD_BASELINE, true); +	}  }  int cmd_diff(int argc, const char **argv, const char *prefix __maybe_unused) |