2
0
mirror of https://github.com/edk2-porting/linux-next.git synced 2024-12-28 07:04:00 +08:00
linux-next/tools/perf/util/hist.c
Eric B Munson eefc465cdd perf session: Change perf_session post processing functions to take histogram tree
Now that report can store historgrams for multiple events we
need to be able to do the post processing work for each
histogram. This patch changes the post processing functions so
that they can be called individually for each event's histogram.

Signed-off-by: Eric B Munson <ebmunson@us.ibm.com>
[ Guarantee bisectabilty by fixing up builtin-report.c ]
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Paul Mackerras <paulus@samba.org>
LKML-Reference: <1267804269-22660-5-git-send-email-acme@infradead.org>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
2010-03-10 13:53:49 +01:00

664 lines
14 KiB
C

#include "hist.h"
#include "session.h"
#include "sort.h"
#include <math.h>
struct callchain_param callchain_param = {
.mode = CHAIN_GRAPH_REL,
.min_percent = 0.5
};
/*
* histogram, sorted on item, collects counts
*/
struct hist_entry *__perf_session__add_hist_entry(struct rb_root *hists,
struct addr_location *al,
struct symbol *sym_parent,
u64 count, bool *hit)
{
struct rb_node **p = &hists->rb_node;
struct rb_node *parent = NULL;
struct hist_entry *he;
struct hist_entry entry = {
.thread = al->thread,
.map = al->map,
.sym = al->sym,
.ip = al->addr,
.level = al->level,
.count = count,
.parent = sym_parent,
};
int cmp;
while (*p != NULL) {
parent = *p;
he = rb_entry(parent, struct hist_entry, rb_node);
cmp = hist_entry__cmp(&entry, he);
if (!cmp) {
*hit = true;
return he;
}
if (cmp < 0)
p = &(*p)->rb_left;
else
p = &(*p)->rb_right;
}
he = malloc(sizeof(*he));
if (!he)
return NULL;
*he = entry;
rb_link_node(&he->rb_node, parent, p);
rb_insert_color(&he->rb_node, hists);
*hit = false;
return he;
}
int64_t
hist_entry__cmp(struct hist_entry *left, struct hist_entry *right)
{
struct sort_entry *se;
int64_t cmp = 0;
list_for_each_entry(se, &hist_entry__sort_list, list) {
cmp = se->cmp(left, right);
if (cmp)
break;
}
return cmp;
}
int64_t
hist_entry__collapse(struct hist_entry *left, struct hist_entry *right)
{
struct sort_entry *se;
int64_t cmp = 0;
list_for_each_entry(se, &hist_entry__sort_list, list) {
int64_t (*f)(struct hist_entry *, struct hist_entry *);
f = se->collapse ?: se->cmp;
cmp = f(left, right);
if (cmp)
break;
}
return cmp;
}
void hist_entry__free(struct hist_entry *he)
{
free(he);
}
/*
* collapse the histogram
*/
static void collapse__insert_entry(struct rb_root *root, struct hist_entry *he)
{
struct rb_node **p = &root->rb_node;
struct rb_node *parent = NULL;
struct hist_entry *iter;
int64_t cmp;
while (*p != NULL) {
parent = *p;
iter = rb_entry(parent, struct hist_entry, rb_node);
cmp = hist_entry__collapse(iter, he);
if (!cmp) {
iter->count += he->count;
hist_entry__free(he);
return;
}
if (cmp < 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);
}
void perf_session__collapse_resort(struct rb_root *hists)
{
struct rb_root tmp;
struct rb_node *next;
struct hist_entry *n;
if (!sort__need_collapse)
return;
tmp = RB_ROOT;
next = rb_first(hists);
while (next) {
n = rb_entry(next, struct hist_entry, rb_node);
next = rb_next(&n->rb_node);
rb_erase(&n->rb_node, hists);
collapse__insert_entry(&tmp, n);
}
*hists = tmp;
}
/*
* reverse the map, sort on count.
*/
static void perf_session__insert_output_hist_entry(struct rb_root *root,
struct hist_entry *he,
u64 min_callchain_hits)
{
struct rb_node **p = &root->rb_node;
struct rb_node *parent = NULL;
struct hist_entry *iter;
if (symbol_conf.use_callchain)
callchain_param.sort(&he->sorted_chain, &he->callchain,
min_callchain_hits, &callchain_param);
while (*p != NULL) {
parent = *p;
iter = rb_entry(parent, struct hist_entry, rb_node);
if (he->count > iter->count)
p = &(*p)->rb_left;
else
p = &(*p)->rb_right;
}
rb_link_node(&he->rb_node, parent, p);
rb_insert_color(&he->rb_node, root);
}
void perf_session__output_resort(struct rb_root *hists, u64 total_samples)
{
struct rb_root tmp;
struct rb_node *next;
struct hist_entry *n;
u64 min_callchain_hits;
min_callchain_hits =
total_samples * (callchain_param.min_percent / 100);
tmp = RB_ROOT;
next = rb_first(hists);
while (next) {
n = rb_entry(next, struct hist_entry, rb_node);
next = rb_next(&n->rb_node);
rb_erase(&n->rb_node, hists);
perf_session__insert_output_hist_entry(&tmp, n,
min_callchain_hits);
}
*hists = tmp;
}
static size_t callchain__fprintf_left_margin(FILE *fp, int left_margin)
{
int i;
int ret = fprintf(fp, " ");
for (i = 0; i < left_margin; i++)
ret += fprintf(fp, " ");
return ret;
}
static size_t ipchain__fprintf_graph_line(FILE *fp, int depth, int depth_mask,
int left_margin)
{
int i;
size_t ret = callchain__fprintf_left_margin(fp, left_margin);
for (i = 0; i < depth; i++)
if (depth_mask & (1 << i))
ret += fprintf(fp, "| ");
else
ret += fprintf(fp, " ");
ret += fprintf(fp, "\n");
return ret;
}
static size_t ipchain__fprintf_graph(FILE *fp, struct callchain_list *chain,
int depth, int depth_mask, int count,
u64 total_samples, int hits,
int left_margin)
{
int i;
size_t ret = 0;
ret += callchain__fprintf_left_margin(fp, left_margin);
for (i = 0; i < depth; i++) {
if (depth_mask & (1 << i))
ret += fprintf(fp, "|");
else
ret += fprintf(fp, " ");
if (!count && i == depth - 1) {
double percent;
percent = hits * 100.0 / total_samples;
ret += percent_color_fprintf(fp, "--%2.2f%%-- ", percent);
} else
ret += fprintf(fp, "%s", " ");
}
if (chain->sym)
ret += fprintf(fp, "%s\n", chain->sym->name);
else
ret += fprintf(fp, "%p\n", (void *)(long)chain->ip);
return ret;
}
static struct symbol *rem_sq_bracket;
static struct callchain_list rem_hits;
static void init_rem_hits(void)
{
rem_sq_bracket = malloc(sizeof(*rem_sq_bracket) + 6);
if (!rem_sq_bracket) {
fprintf(stderr, "Not enough memory to display remaining hits\n");
return;
}
strcpy(rem_sq_bracket->name, "[...]");
rem_hits.sym = rem_sq_bracket;
}
static size_t __callchain__fprintf_graph(FILE *fp, struct callchain_node *self,
u64 total_samples, int depth,
int depth_mask, int left_margin)
{
struct rb_node *node, *next;
struct callchain_node *child;
struct callchain_list *chain;
int new_depth_mask = depth_mask;
u64 new_total;
u64 remaining;
size_t ret = 0;
int i;
if (callchain_param.mode == CHAIN_GRAPH_REL)
new_total = self->children_hit;
else
new_total = total_samples;
remaining = new_total;
node = rb_first(&self->rb_root);
while (node) {
u64 cumul;
child = rb_entry(node, struct callchain_node, rb_node);
cumul = cumul_hits(child);
remaining -= cumul;
/*
* The depth mask manages the output of pipes that show
* the depth. We don't want to keep the pipes of the current
* level for the last child of this depth.
* Except if we have remaining filtered hits. They will
* supersede the last child
*/
next = rb_next(node);
if (!next && (callchain_param.mode != CHAIN_GRAPH_REL || !remaining))
new_depth_mask &= ~(1 << (depth - 1));
/*
* But we keep the older depth mask for the line seperator
* to keep the level link until we reach the last child
*/
ret += ipchain__fprintf_graph_line(fp, depth, depth_mask,
left_margin);
i = 0;
list_for_each_entry(chain, &child->val, list) {
if (chain->ip >= PERF_CONTEXT_MAX)
continue;
ret += ipchain__fprintf_graph(fp, chain, depth,
new_depth_mask, i++,
new_total,
cumul,
left_margin);
}
ret += __callchain__fprintf_graph(fp, child, new_total,
depth + 1,
new_depth_mask | (1 << depth),
left_margin);
node = next;
}
if (callchain_param.mode == CHAIN_GRAPH_REL &&
remaining && remaining != new_total) {
if (!rem_sq_bracket)
return ret;
new_depth_mask &= ~(1 << (depth - 1));
ret += ipchain__fprintf_graph(fp, &rem_hits, depth,
new_depth_mask, 0, new_total,
remaining, left_margin);
}
return ret;
}
static size_t callchain__fprintf_graph(FILE *fp, struct callchain_node *self,
u64 total_samples, int left_margin)
{
struct callchain_list *chain;
bool printed = false;
int i = 0;
int ret = 0;
list_for_each_entry(chain, &self->val, list) {
if (chain->ip >= PERF_CONTEXT_MAX)
continue;
if (!i++ && sort__first_dimension == SORT_SYM)
continue;
if (!printed) {
ret += callchain__fprintf_left_margin(fp, left_margin);
ret += fprintf(fp, "|\n");
ret += callchain__fprintf_left_margin(fp, left_margin);
ret += fprintf(fp, "---");
left_margin += 3;
printed = true;
} else
ret += callchain__fprintf_left_margin(fp, left_margin);
if (chain->sym)
ret += fprintf(fp, " %s\n", chain->sym->name);
else
ret += fprintf(fp, " %p\n", (void *)(long)chain->ip);
}
ret += __callchain__fprintf_graph(fp, self, total_samples, 1, 1, left_margin);
return ret;
}
static size_t callchain__fprintf_flat(FILE *fp, struct callchain_node *self,
u64 total_samples)
{
struct callchain_list *chain;
size_t ret = 0;
if (!self)
return 0;
ret += callchain__fprintf_flat(fp, self->parent, total_samples);
list_for_each_entry(chain, &self->val, list) {
if (chain->ip >= PERF_CONTEXT_MAX)
continue;
if (chain->sym)
ret += fprintf(fp, " %s\n", chain->sym->name);
else
ret += fprintf(fp, " %p\n",
(void *)(long)chain->ip);
}
return ret;
}
static size_t hist_entry_callchain__fprintf(FILE *fp, struct hist_entry *self,
u64 total_samples, int left_margin)
{
struct rb_node *rb_node;
struct callchain_node *chain;
size_t ret = 0;
rb_node = rb_first(&self->sorted_chain);
while (rb_node) {
double percent;
chain = rb_entry(rb_node, struct callchain_node, rb_node);
percent = chain->hit * 100.0 / total_samples;
switch (callchain_param.mode) {
case CHAIN_FLAT:
ret += percent_color_fprintf(fp, " %6.2f%%\n",
percent);
ret += callchain__fprintf_flat(fp, chain, total_samples);
break;
case CHAIN_GRAPH_ABS: /* Falldown */
case CHAIN_GRAPH_REL:
ret += callchain__fprintf_graph(fp, chain, total_samples,
left_margin);
case CHAIN_NONE:
default:
break;
}
ret += fprintf(fp, "\n");
rb_node = rb_next(rb_node);
}
return ret;
}
static size_t hist_entry__fprintf(struct hist_entry *self,
struct perf_session *pair_session,
bool show_displacement,
long displacement, FILE *fp,
u64 session_total)
{
struct sort_entry *se;
u64 count, total;
const char *sep = symbol_conf.field_sep;
size_t ret;
if (symbol_conf.exclude_other && !self->parent)
return 0;
if (pair_session) {
count = self->pair ? self->pair->count : 0;
total = pair_session->events_stats.total;
} else {
count = self->count;
total = session_total;
}
if (total)
ret = percent_color_fprintf(fp, sep ? "%.2f" : " %6.2f%%",
(count * 100.0) / total);
else
ret = fprintf(fp, sep ? "%lld" : "%12lld ", count);
if (symbol_conf.show_nr_samples) {
if (sep)
fprintf(fp, "%c%lld", *sep, count);
else
fprintf(fp, "%11lld", count);
}
if (pair_session) {
char bf[32];
double old_percent = 0, new_percent = 0, diff;
if (total > 0)
old_percent = (count * 100.0) / total;
if (session_total > 0)
new_percent = (self->count * 100.0) / session_total;
diff = new_percent - old_percent;
if (fabs(diff) >= 0.01)
snprintf(bf, sizeof(bf), "%+4.2F%%", diff);
else
snprintf(bf, sizeof(bf), " ");
if (sep)
ret += fprintf(fp, "%c%s", *sep, bf);
else
ret += fprintf(fp, "%11.11s", bf);
if (show_displacement) {
if (displacement)
snprintf(bf, sizeof(bf), "%+4ld", displacement);
else
snprintf(bf, sizeof(bf), " ");
if (sep)
fprintf(fp, "%c%s", *sep, bf);
else
fprintf(fp, "%6.6s", bf);
}
}
list_for_each_entry(se, &hist_entry__sort_list, list) {
if (se->elide)
continue;
fprintf(fp, "%s", sep ?: " ");
ret += se->print(fp, self, se->width ? *se->width : 0);
}
ret += fprintf(fp, "\n");
if (symbol_conf.use_callchain) {
int left_margin = 0;
if (sort__first_dimension == SORT_COMM) {
se = list_first_entry(&hist_entry__sort_list, typeof(*se),
list);
left_margin = se->width ? *se->width : 0;
left_margin -= thread__comm_len(self->thread);
}
hist_entry_callchain__fprintf(fp, self, session_total,
left_margin);
}
return ret;
}
size_t perf_session__fprintf_hists(struct rb_root *hists,
struct perf_session *pair,
bool show_displacement, FILE *fp,
u64 session_total)
{
struct sort_entry *se;
struct rb_node *nd;
size_t ret = 0;
unsigned long position = 1;
long displacement = 0;
unsigned int width;
const char *sep = symbol_conf.field_sep;
char *col_width = symbol_conf.col_width_list_str;
init_rem_hits();
fprintf(fp, "# %s", pair ? "Baseline" : "Overhead");
if (symbol_conf.show_nr_samples) {
if (sep)
fprintf(fp, "%cSamples", *sep);
else
fputs(" Samples ", fp);
}
if (pair) {
if (sep)
ret += fprintf(fp, "%cDelta", *sep);
else
ret += fprintf(fp, " Delta ");
if (show_displacement) {
if (sep)
ret += fprintf(fp, "%cDisplacement", *sep);
else
ret += fprintf(fp, " Displ");
}
}
list_for_each_entry(se, &hist_entry__sort_list, list) {
if (se->elide)
continue;
if (sep) {
fprintf(fp, "%c%s", *sep, se->header);
continue;
}
width = strlen(se->header);
if (se->width) {
if (symbol_conf.col_width_list_str) {
if (col_width) {
*se->width = atoi(col_width);
col_width = strchr(col_width, ',');
if (col_width)
++col_width;
}
}
width = *se->width = max(*se->width, width);
}
fprintf(fp, " %*s", width, se->header);
}
fprintf(fp, "\n");
if (sep)
goto print_entries;
fprintf(fp, "# ........");
if (symbol_conf.show_nr_samples)
fprintf(fp, " ..........");
if (pair) {
fprintf(fp, " ..........");
if (show_displacement)
fprintf(fp, " .....");
}
list_for_each_entry(se, &hist_entry__sort_list, list) {
unsigned int i;
if (se->elide)
continue;
fprintf(fp, " ");
if (se->width)
width = *se->width;
else
width = strlen(se->header);
for (i = 0; i < width; i++)
fprintf(fp, ".");
}
fprintf(fp, "\n#\n");
print_entries:
for (nd = rb_first(hists); nd; nd = rb_next(nd)) {
struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
if (show_displacement) {
if (h->pair != NULL)
displacement = ((long)h->pair->position -
(long)position);
else
displacement = 0;
++position;
}
ret += hist_entry__fprintf(h, pair, show_displacement,
displacement, fp, session_total);
}
free(rem_sq_bracket);
return ret;
}