#include "hist.h"
#include "session.h"
#include "sort.h"
+#include "evsel.h"
#include <math.h>
+static bool hists__filter_entry_by_dso(struct hists *hists,
+ struct hist_entry *he);
+static bool hists__filter_entry_by_thread(struct hists *hists,
+ struct hist_entry *he);
+static bool hists__filter_entry_by_symbol(struct hists *hists,
+ struct hist_entry *he);
+
enum hist_filter {
HIST_FILTER__DSO,
HIST_FILTER__THREAD,
HIST_FILTER__PARENT,
+ HIST_FILTER__SYMBOL,
};
struct callchain_param callchain_param = {
return false;
}
-static void hists__reset_col_len(struct hists *hists)
+void hists__reset_col_len(struct hists *hists)
{
enum hist_column col;
hists__set_col_len(hists, col, 0);
}
-static void hists__calc_col_len(struct hists *hists, struct hist_entry *h)
+static void hists__set_unres_dso_col_len(struct hists *hists, int dso)
+{
+ const unsigned int unresolved_col_width = BITS_PER_LONG / 4;
+
+ if (hists__col_len(hists, dso) < unresolved_col_width &&
+ !symbol_conf.col_width_list_str && !symbol_conf.field_sep &&
+ !symbol_conf.dso_list)
+ hists__set_col_len(hists, dso, unresolved_col_width);
+}
+
+void hists__calc_col_len(struct hists *hists, struct hist_entry *h)
{
+ const unsigned int unresolved_col_width = BITS_PER_LONG / 4;
+ int symlen;
u16 len;
if (h->ms.sym)
- hists__new_col_len(hists, HISTC_SYMBOL, h->ms.sym->namelen);
+ hists__new_col_len(hists, HISTC_SYMBOL, h->ms.sym->namelen + 4);
else {
- const unsigned int unresolved_col_width = BITS_PER_LONG / 4;
-
- if (hists__col_len(hists, HISTC_DSO) < unresolved_col_width &&
- !symbol_conf.col_width_list_str && !symbol_conf.field_sep &&
- !symbol_conf.dso_list)
- hists__set_col_len(hists, HISTC_DSO,
- unresolved_col_width);
+ symlen = unresolved_col_width + 4 + 2;
+ hists__new_col_len(hists, HISTC_SYMBOL, symlen);
+ hists__set_unres_dso_col_len(hists, HISTC_DSO);
}
len = thread__comm_len(h->thread);
len = dso__name_len(h->ms.map->dso);
hists__new_col_len(hists, HISTC_DSO, len);
}
+
+ if (h->parent)
+ hists__new_col_len(hists, HISTC_PARENT, h->parent->namelen);
+
+ if (h->branch_info) {
+ /*
+ * +4 accounts for '[x] ' priv level info
+ * +2 account of 0x prefix on raw addresses
+ */
+ if (h->branch_info->from.sym) {
+ symlen = (int)h->branch_info->from.sym->namelen + 4;
+ hists__new_col_len(hists, HISTC_SYMBOL_FROM, symlen);
+
+ symlen = dso__name_len(h->branch_info->from.map->dso);
+ hists__new_col_len(hists, HISTC_DSO_FROM, symlen);
+ } else {
+ symlen = unresolved_col_width + 4 + 2;
+ hists__new_col_len(hists, HISTC_SYMBOL_FROM, symlen);
+ hists__set_unres_dso_col_len(hists, HISTC_DSO_FROM);
+ }
+
+ if (h->branch_info->to.sym) {
+ symlen = (int)h->branch_info->to.sym->namelen + 4;
+ hists__new_col_len(hists, HISTC_SYMBOL_TO, symlen);
+
+ symlen = dso__name_len(h->branch_info->to.map->dso);
+ hists__new_col_len(hists, HISTC_DSO_TO, symlen);
+ } else {
+ symlen = unresolved_col_width + 4 + 2;
+ hists__new_col_len(hists, HISTC_SYMBOL_TO, symlen);
+ hists__set_unres_dso_col_len(hists, HISTC_DSO_TO);
+ }
+ }
+
+ if (h->mem_info) {
+ /*
+ * +4 accounts for '[x] ' priv level info
+ * +2 account of 0x prefix on raw addresses
+ */
+ if (h->mem_info->daddr.sym) {
+ symlen = (int)h->mem_info->daddr.sym->namelen + 4
+ + unresolved_col_width + 2;
+ hists__new_col_len(hists, HISTC_MEM_DADDR_SYMBOL,
+ symlen);
+ } else {
+ symlen = unresolved_col_width + 4 + 2;
+ hists__new_col_len(hists, HISTC_MEM_DADDR_SYMBOL,
+ symlen);
+ }
+ if (h->mem_info->daddr.map) {
+ symlen = dso__name_len(h->mem_info->daddr.map->dso);
+ hists__new_col_len(hists, HISTC_MEM_DADDR_DSO,
+ symlen);
+ } else {
+ symlen = unresolved_col_width + 4 + 2;
+ hists__set_unres_dso_col_len(hists, HISTC_MEM_DADDR_DSO);
+ }
+ } else {
+ symlen = unresolved_col_width + 4 + 2;
+ hists__new_col_len(hists, HISTC_MEM_DADDR_SYMBOL, symlen);
+ hists__set_unres_dso_col_len(hists, HISTC_MEM_DADDR_DSO);
+ }
+
+ hists__new_col_len(hists, HISTC_MEM_LOCKED, 6);
+ hists__new_col_len(hists, HISTC_MEM_TLB, 22);
+ hists__new_col_len(hists, HISTC_MEM_SNOOP, 12);
+ hists__new_col_len(hists, HISTC_MEM_LVL, 21 + 3);
+ hists__new_col_len(hists, HISTC_LOCAL_WEIGHT, 12);
+ hists__new_col_len(hists, HISTC_GLOBAL_WEIGHT, 12);
}
-static void hist_entry__add_cpumode_period(struct hist_entry *self,
+void hists__output_recalc_col_len(struct hists *hists, int max_rows)
+{
+ struct rb_node *next = rb_first(&hists->entries);
+ struct hist_entry *n;
+ int row = 0;
+
+ hists__reset_col_len(hists);
+
+ while (next && row++ < max_rows) {
+ n = rb_entry(next, struct hist_entry, rb_node);
+ if (!n->filtered)
+ hists__calc_col_len(hists, n);
+ next = rb_next(&n->rb_node);
+ }
+}
+
+static void hist_entry__add_cpumode_period(struct hist_entry *he,
unsigned int cpumode, u64 period)
{
switch (cpumode) {
case PERF_RECORD_MISC_KERNEL:
- self->period_sys += period;
+ he->stat.period_sys += period;
break;
case PERF_RECORD_MISC_USER:
- self->period_us += period;
+ he->stat.period_us += period;
break;
case PERF_RECORD_MISC_GUEST_KERNEL:
- self->period_guest_sys += period;
+ he->stat.period_guest_sys += period;
break;
case PERF_RECORD_MISC_GUEST_USER:
- self->period_guest_us += period;
+ he->stat.period_guest_us += period;
break;
default:
break;
}
}
+static void he_stat__add_period(struct he_stat *he_stat, u64 period,
+ u64 weight)
+{
+
+ he_stat->period += period;
+ he_stat->weight += weight;
+ he_stat->nr_events += 1;
+}
+
+static void he_stat__add_stat(struct he_stat *dest, struct he_stat *src)
+{
+ dest->period += src->period;
+ dest->period_sys += src->period_sys;
+ dest->period_us += src->period_us;
+ dest->period_guest_sys += src->period_guest_sys;
+ dest->period_guest_us += src->period_guest_us;
+ dest->nr_events += src->nr_events;
+ dest->weight += src->weight;
+}
+
static void hist_entry__decay(struct hist_entry *he)
{
- he->period = (he->period * 7) / 8;
- he->nr_events = (he->nr_events * 7) / 8;
+ he->stat.period = (he->stat.period * 7) / 8;
+ he->stat.nr_events = (he->stat.nr_events * 7) / 8;
+ /* XXX need decay for weight too? */
}
static bool hists__decay_entry(struct hists *hists, struct hist_entry *he)
{
- hists->stats.total_period -= he->period;
+ u64 prev_period = he->stat.period;
+
+ if (prev_period == 0)
+ return true;
+
hist_entry__decay(he);
- hists->stats.total_period += he->period;
- return he->period == 0;
+
+ if (!he->filtered)
+ hists->stats.total_period -= prev_period - he->stat.period;
+
+ return he->stat.period == 0;
}
-void hists__decay_entries(struct hists *hists)
+static void __hists__decay_entries(struct hists *hists, bool zap_user,
+ bool zap_kernel, bool threaded)
{
struct rb_node *next = rb_first(&hists->entries);
struct hist_entry *n;
while (next) {
n = rb_entry(next, struct hist_entry, rb_node);
next = rb_next(&n->rb_node);
-
- if (hists__decay_entry(hists, n)) {
+ /*
+ * We may be annotating this, for instance, so keep it here in
+ * case some it gets new samples, we'll eventually free it when
+ * the user stops browsing and it agains gets fully decayed.
+ */
+ if (((zap_user && n->level == '.') ||
+ (zap_kernel && n->level != '.') ||
+ hists__decay_entry(hists, n)) &&
+ !n->used) {
rb_erase(&n->rb_node, &hists->entries);
- if (sort__need_collapse)
+ if (sort__need_collapse || threaded)
rb_erase(&n->rb_node_in, &hists->entries_collapsed);
hist_entry__free(n);
}
}
+void hists__decay_entries(struct hists *hists, bool zap_user, bool zap_kernel)
+{
+ return __hists__decay_entries(hists, zap_user, zap_kernel, false);
+}
+
+void hists__decay_entries_threaded(struct hists *hists,
+ bool zap_user, bool zap_kernel)
+{
+ return __hists__decay_entries(hists, zap_user, zap_kernel, true);
+}
+
/*
* histogram, sorted on item, collects periods
*/
static struct hist_entry *hist_entry__new(struct hist_entry *template)
{
size_t callchain_size = symbol_conf.use_callchain ? sizeof(struct callchain_root) : 0;
- struct hist_entry *self = malloc(sizeof(*self) + callchain_size);
+ struct hist_entry *he = zalloc(sizeof(*he) + callchain_size);
+
+ if (he != NULL) {
+ *he = *template;
+
+ if (he->ms.map)
+ he->ms.map->referenced = true;
+
+ if (he->branch_info) {
+ if (he->branch_info->from.map)
+ he->branch_info->from.map->referenced = true;
+ if (he->branch_info->to.map)
+ he->branch_info->to.map->referenced = true;
+ }
+
+ if (he->mem_info) {
+ if (he->mem_info->iaddr.map)
+ he->mem_info->iaddr.map->referenced = true;
+ if (he->mem_info->daddr.map)
+ he->mem_info->daddr.map->referenced = true;
+ }
- if (self != NULL) {
- *self = *template;
- self->nr_events = 1;
- if (self->ms.map)
- self->ms.map->referenced = true;
if (symbol_conf.use_callchain)
- callchain_init(self->callchain);
+ callchain_init(he->callchain);
+
+ INIT_LIST_HEAD(&he->pairs.node);
}
- return self;
+ return he;
}
-static void hists__inc_nr_entries(struct hists *hists, struct hist_entry *h)
+void hists__inc_nr_entries(struct hists *hists, struct hist_entry *h)
{
if (!h->filtered) {
hists__calc_col_len(hists, h);
++hists->nr_entries;
- hists->stats.total_period += h->period;
+ hists->stats.total_period += h->stat.period;
}
}
return 0;
}
-struct hist_entry *__hists__add_entry(struct hists *hists,
+static struct hist_entry *add_hist_entry(struct hists *hists,
+ struct hist_entry *entry,
struct addr_location *al,
- struct symbol *sym_parent, u64 period)
+ u64 period,
+ u64 weight)
{
struct rb_node **p;
struct rb_node *parent = NULL;
struct hist_entry *he;
- struct hist_entry entry = {
- .thread = al->thread,
- .ms = {
- .map = al->map,
- .sym = al->sym,
- },
- .cpu = al->cpu,
- .ip = al->addr,
- .level = al->level,
- .period = period,
- .parent = sym_parent,
- .filtered = symbol__parent_filter(sym_parent),
- };
int cmp;
pthread_mutex_lock(&hists->lock);
parent = *p;
he = rb_entry(parent, struct hist_entry, rb_node_in);
- cmp = hist_entry__cmp(&entry, he);
+ /*
+ * Make sure that it receives arguments in a same order as
+ * hist_entry__collapse() so that we can use an appropriate
+ * function when searching an entry regardless which sort
+ * keys were used.
+ */
+ cmp = hist_entry__cmp(he, entry);
if (!cmp) {
- he->period += period;
- ++he->nr_events;
+ he_stat__add_period(&he->stat, period, weight);
+
+ /* If the map of an existing hist_entry has
+ * become out-of-date due to an exec() or
+ * similar, update it. Otherwise we will
+ * mis-adjust symbol addresses when computing
+ * the history counter to increment.
+ */
+ if (he->ms.map != entry->ms.map) {
+ he->ms.map = entry->ms.map;
+ if (he->ms.map)
+ he->ms.map->referenced = true;
+ }
goto out;
}
p = &(*p)->rb_right;
}
- he = hist_entry__new(&entry);
+ he = hist_entry__new(entry);
if (!he)
goto out_unlock;
return he;
}
+struct hist_entry *__hists__add_mem_entry(struct hists *self,
+ struct addr_location *al,
+ struct symbol *sym_parent,
+ struct mem_info *mi,
+ u64 period,
+ u64 weight)
+{
+ struct hist_entry entry = {
+ .thread = al->thread,
+ .ms = {
+ .map = al->map,
+ .sym = al->sym,
+ },
+ .stat = {
+ .period = period,
+ .weight = weight,
+ .nr_events = 1,
+ },
+ .cpu = al->cpu,
+ .ip = al->addr,
+ .level = al->level,
+ .parent = sym_parent,
+ .filtered = symbol__parent_filter(sym_parent),
+ .hists = self,
+ .mem_info = mi,
+ .branch_info = NULL,
+ };
+ return add_hist_entry(self, &entry, al, period, weight);
+}
+
+struct hist_entry *__hists__add_branch_entry(struct hists *self,
+ struct addr_location *al,
+ struct symbol *sym_parent,
+ struct branch_info *bi,
+ u64 period,
+ u64 weight)
+{
+ struct hist_entry entry = {
+ .thread = al->thread,
+ .ms = {
+ .map = bi->to.map,
+ .sym = bi->to.sym,
+ },
+ .cpu = al->cpu,
+ .ip = bi->to.addr,
+ .level = al->level,
+ .stat = {
+ .period = period,
+ .nr_events = 1,
+ .weight = weight,
+ },
+ .parent = sym_parent,
+ .filtered = symbol__parent_filter(sym_parent),
+ .branch_info = bi,
+ .hists = self,
+ .mem_info = NULL,
+ };
+
+ return add_hist_entry(self, &entry, al, period, weight);
+}
+
+struct hist_entry *__hists__add_entry(struct hists *self,
+ struct addr_location *al,
+ struct symbol *sym_parent, u64 period,
+ u64 weight)
+{
+ struct hist_entry entry = {
+ .thread = al->thread,
+ .ms = {
+ .map = al->map,
+ .sym = al->sym,
+ },
+ .cpu = al->cpu,
+ .ip = al->addr,
+ .level = al->level,
+ .stat = {
+ .period = period,
+ .nr_events = 1,
+ .weight = weight,
+ },
+ .parent = sym_parent,
+ .filtered = symbol__parent_filter(sym_parent),
+ .hists = self,
+ .branch_info = NULL,
+ .mem_info = NULL,
+ };
+
+ return add_hist_entry(self, &entry, al, period, weight);
+}
+
int64_t
hist_entry__cmp(struct hist_entry *left, struct hist_entry *right)
{
void hist_entry__free(struct hist_entry *he)
{
+ free(he->branch_info);
+ free(he->mem_info);
free(he);
}
* collapse the histogram
*/
-static bool hists__collapse_insert_entry(struct hists *hists,
+static bool hists__collapse_insert_entry(struct hists *hists __maybe_unused,
struct rb_root *root,
struct hist_entry *he)
{
cmp = hist_entry__collapse(iter, he);
if (!cmp) {
- iter->period += he->period;
- iter->nr_events += he->nr_events;
+ he_stat__add_stat(&iter->stat, &he->stat);
+
if (symbol_conf.use_callchain) {
- callchain_cursor_reset(&hists->callchain_cursor);
- callchain_merge(&hists->callchain_cursor, iter->callchain,
+ callchain_cursor_reset(&callchain_cursor);
+ callchain_merge(&callchain_cursor,
+ iter->callchain,
he->callchain);
}
hist_entry__free(he);
return root;
}
+static void hists__apply_filters(struct hists *hists, struct hist_entry *he)
+{
+ hists__filter_entry_by_dso(hists, he);
+ hists__filter_entry_by_thread(hists, he);
+ hists__filter_entry_by_symbol(hists, he);
+}
+
static void __hists__collapse_resort(struct hists *hists, bool threaded)
{
struct rb_root *root;
root = hists__get_rotate_entries_in(hists);
next = rb_first(root);
- hists->stats.total_period = 0;
while (next) {
n = rb_entry(next, struct hist_entry, rb_node_in);
next = rb_next(&n->rb_node_in);
rb_erase(&n->rb_node_in, root);
- if (hists__collapse_insert_entry(hists, &hists->entries_collapsed, n))
- hists__inc_nr_entries(hists, n);
+ if (hists__collapse_insert_entry(hists, &hists->entries_collapsed, n)) {
+ /*
+ * If it wasn't combined with one of the entries already
+ * collapsed, we need to apply the filters that may have
+ * been set by, say, the hist_browser.
+ */
+ hists__apply_filters(hists, n);
+ }
}
}
* reverse the map, sort on period.
*/
+static int period_cmp(u64 period_a, u64 period_b)
+{
+ if (period_a > period_b)
+ return 1;
+ if (period_a < period_b)
+ return -1;
+ return 0;
+}
+
+static int hist_entry__sort_on_period(struct hist_entry *a,
+ struct hist_entry *b)
+{
+ int ret;
+ int i, nr_members;
+ struct perf_evsel *evsel;
+ struct hist_entry *pair;
+ u64 *periods_a, *periods_b;
+
+ ret = period_cmp(a->stat.period, b->stat.period);
+ if (ret || !symbol_conf.event_group)
+ return ret;
+
+ evsel = hists_to_evsel(a->hists);
+ nr_members = evsel->nr_members;
+ if (nr_members <= 1)
+ return ret;
+
+ periods_a = zalloc(sizeof(periods_a) * nr_members);
+ periods_b = zalloc(sizeof(periods_b) * nr_members);
+
+ if (!periods_a || !periods_b)
+ goto out;
+
+ list_for_each_entry(pair, &a->pairs.head, pairs.node) {
+ evsel = hists_to_evsel(pair->hists);
+ periods_a[perf_evsel__group_idx(evsel)] = pair->stat.period;
+ }
+
+ list_for_each_entry(pair, &b->pairs.head, pairs.node) {
+ evsel = hists_to_evsel(pair->hists);
+ periods_b[perf_evsel__group_idx(evsel)] = pair->stat.period;
+ }
+
+ for (i = 1; i < nr_members; i++) {
+ ret = period_cmp(periods_a[i], periods_b[i]);
+ if (ret)
+ break;
+ }
+
+out:
+ free(periods_a);
+ free(periods_b);
+
+ return ret;
+}
+
static void __hists__insert_output_entry(struct rb_root *entries,
struct hist_entry *he,
u64 min_callchain_hits)
parent = *p;
iter = rb_entry(parent, struct hist_entry, rb_node);
- if (he->period > iter->period)
+ if (hist_entry__sort_on_period(he, iter) > 0)
p = &(*p)->rb_left;
else
p = &(*p)->rb_right;
hists->entries = RB_ROOT;
hists->nr_entries = 0;
+ hists->stats.total_period = 0;
hists__reset_col_len(hists);
while (next) {
return __hists__output_resort(hists, true);
}
-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 period,
- u64 total_samples, u64 hits,
- int left_margin)
+static void hists__remove_entry_filter(struct hists *hists, struct hist_entry *h,
+ enum hist_filter filter)
{
- int i;
- size_t ret = 0;
+ h->filtered &= ~(1 << filter);
+ if (h->filtered)
+ return;
- 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 (!period && 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->ms.sym)
- ret += fprintf(fp, "%s\n", chain->ms.sym->name);
- else
- ret += fprintf(fp, "%p\n", (void *)(long)chain->ip);
+ ++hists->nr_entries;
+ if (h->ms.unfolded)
+ hists->nr_entries += h->nr_rows;
+ h->row_offset = 0;
+ hists->stats.total_period += h->stat.period;
+ hists->stats.nr_events[PERF_RECORD_SAMPLE] += h->stat.nr_events;
- return ret;
+ hists__calc_col_len(hists, h);
}
-static struct symbol *rem_sq_bracket;
-static struct callchain_list rem_hits;
-static void init_rem_hits(void)
+static bool hists__filter_entry_by_dso(struct hists *hists,
+ struct hist_entry *he)
{
- rem_sq_bracket = malloc(sizeof(*rem_sq_bracket) + 6);
- if (!rem_sq_bracket) {
- fprintf(stderr, "Not enough memory to display remaining hits\n");
- return;
+ if (hists->dso_filter != NULL &&
+ (he->ms.map == NULL || he->ms.map->dso != hists->dso_filter)) {
+ he->filtered |= (1 << HIST_FILTER__DSO);
+ return true;
}
- strcpy(rem_sq_bracket->name, "[...]");
- rem_hits.ms.sym = rem_sq_bracket;
+ return false;
}
-static size_t __callchain__fprintf_graph(FILE *fp, struct callchain_node *self,
- u64 total_samples, int depth,
- int depth_mask, int left_margin)
+void hists__filter_by_dso(struct hists *hists)
{
- 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;
- uint entries_printed = 0;
-
- 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 = callchain_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));
+ struct rb_node *nd;
- /*
- * But we keep the older depth mask for the line separator
- * 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) {
- 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 (++entries_printed == callchain_param.print_limit)
- break;
- }
+ hists->nr_entries = hists->stats.total_period = 0;
+ hists->stats.nr_events[PERF_RECORD_SAMPLE] = 0;
+ hists__reset_col_len(hists);
- if (callchain_param.mode == CHAIN_GRAPH_REL &&
- remaining && remaining != new_total) {
+ for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) {
+ struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
- if (!rem_sq_bracket)
- return ret;
+ if (symbol_conf.exclude_other && !h->parent)
+ continue;
- new_depth_mask &= ~(1 << (depth - 1));
+ if (hists__filter_entry_by_dso(hists, h))
+ continue;
- ret += ipchain__fprintf_graph(fp, &rem_hits, depth,
- new_depth_mask, 0, new_total,
- remaining, left_margin);
+ hists__remove_entry_filter(hists, h, HIST_FILTER__DSO);
}
-
- return ret;
}
-static size_t callchain__fprintf_graph(FILE *fp, struct callchain_node *self,
- u64 total_samples, int left_margin)
+static bool hists__filter_entry_by_thread(struct hists *hists,
+ struct hist_entry *he)
{
- struct callchain_list *chain;
- bool printed = false;
- int i = 0;
- int ret = 0;
- u32 entries_printed = 0;
-
- list_for_each_entry(chain, &self->val, list) {
- 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->ms.sym)
- ret += fprintf(fp, " %s\n", chain->ms.sym->name);
- else
- ret += fprintf(fp, " %p\n", (void *)(long)chain->ip);
-
- if (++entries_printed == callchain_param.print_limit)
- break;
+ if (hists->thread_filter != NULL &&
+ he->thread != hists->thread_filter) {
+ he->filtered |= (1 << HIST_FILTER__THREAD);
+ return true;
}
- ret += __callchain__fprintf_graph(fp, self, total_samples, 1, 1, left_margin);
-
- return ret;
+ return false;
}
-static size_t callchain__fprintf_flat(FILE *fp, struct callchain_node *self,
- u64 total_samples)
+void hists__filter_by_thread(struct hists *hists)
{
- struct callchain_list *chain;
- size_t ret = 0;
-
- if (!self)
- return 0;
+ struct rb_node *nd;
- ret += callchain__fprintf_flat(fp, self->parent, total_samples);
+ hists->nr_entries = hists->stats.total_period = 0;
+ hists->stats.nr_events[PERF_RECORD_SAMPLE] = 0;
+ hists__reset_col_len(hists);
+ for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) {
+ struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
- list_for_each_entry(chain, &self->val, list) {
- if (chain->ip >= PERF_CONTEXT_MAX)
+ if (hists__filter_entry_by_thread(hists, h))
continue;
- if (chain->ms.sym)
- ret += fprintf(fp, " %s\n", chain->ms.sym->name);
- else
- ret += fprintf(fp, " %p\n",
- (void *)(long)chain->ip);
- }
- return ret;
+ hists__remove_entry_filter(hists, h, HIST_FILTER__THREAD);
+ }
}
-static size_t hist_entry_callchain__fprintf(FILE *fp, struct hist_entry *self,
- u64 total_samples, int left_margin)
+static bool hists__filter_entry_by_symbol(struct hists *hists,
+ struct hist_entry *he)
{
- struct rb_node *rb_node;
- struct callchain_node *chain;
- size_t ret = 0;
- u32 entries_printed = 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");
- if (++entries_printed == callchain_param.print_limit)
- break;
- rb_node = rb_next(rb_node);
+ if (hists->symbol_filter_str != NULL &&
+ (!he->ms.sym || strstr(he->ms.sym->name,
+ hists->symbol_filter_str) == NULL)) {
+ he->filtered |= (1 << HIST_FILTER__SYMBOL);
+ return true;
}
- return ret;
+ return false;
}
-void hists__output_recalc_col_len(struct hists *hists, int max_rows)
+void hists__filter_by_symbol(struct hists *hists)
{
- struct rb_node *next = rb_first(&hists->entries);
- struct hist_entry *n;
- int row = 0;
+ struct rb_node *nd;
+ hists->nr_entries = hists->stats.total_period = 0;
+ hists->stats.nr_events[PERF_RECORD_SAMPLE] = 0;
hists__reset_col_len(hists);
- while (next && row++ < max_rows) {
- n = rb_entry(next, struct hist_entry, rb_node);
- hists__calc_col_len(hists, n);
- next = rb_next(&n->rb_node);
- }
-}
-
-int hist_entry__snprintf(struct hist_entry *self, char *s, size_t size,
- struct hists *hists, struct hists *pair_hists,
- bool show_displacement, long displacement,
- bool color, u64 session_total)
-{
- struct sort_entry *se;
- u64 period, total, period_sys, period_us, period_guest_sys, period_guest_us;
- u64 nr_events;
- const char *sep = symbol_conf.field_sep;
- int ret;
-
- if (symbol_conf.exclude_other && !self->parent)
- return 0;
-
- if (pair_hists) {
- period = self->pair ? self->pair->period : 0;
- nr_events = self->pair ? self->pair->nr_events : 0;
- total = pair_hists->stats.total_period;
- period_sys = self->pair ? self->pair->period_sys : 0;
- period_us = self->pair ? self->pair->period_us : 0;
- period_guest_sys = self->pair ? self->pair->period_guest_sys : 0;
- period_guest_us = self->pair ? self->pair->period_guest_us : 0;
- } else {
- period = self->period;
- nr_events = self->nr_events;
- total = session_total;
- period_sys = self->period_sys;
- period_us = self->period_us;
- period_guest_sys = self->period_guest_sys;
- period_guest_us = self->period_guest_us;
- }
-
- if (total) {
- if (color)
- ret = percent_color_snprintf(s, size,
- sep ? "%.2f" : " %6.2f%%",
- (period * 100.0) / total);
- else
- ret = snprintf(s, size, sep ? "%.2f" : " %6.2f%%",
- (period * 100.0) / total);
- if (symbol_conf.show_cpu_utilization) {
- ret += percent_color_snprintf(s + ret, size - ret,
- sep ? "%.2f" : " %6.2f%%",
- (period_sys * 100.0) / total);
- ret += percent_color_snprintf(s + ret, size - ret,
- sep ? "%.2f" : " %6.2f%%",
- (period_us * 100.0) / total);
- if (perf_guest) {
- ret += percent_color_snprintf(s + ret,
- size - ret,
- sep ? "%.2f" : " %6.2f%%",
- (period_guest_sys * 100.0) /
- total);
- ret += percent_color_snprintf(s + ret,
- size - ret,
- sep ? "%.2f" : " %6.2f%%",
- (period_guest_us * 100.0) /
- total);
- }
- }
- } else
- ret = snprintf(s, size, sep ? "%" PRIu64 : "%12" PRIu64 " ", period);
-
- if (symbol_conf.show_nr_samples) {
- if (sep)
- ret += snprintf(s + ret, size - ret, "%c%" PRIu64, *sep, nr_events);
- else
- ret += snprintf(s + ret, size - ret, "%11" PRIu64, nr_events);
- }
-
- if (symbol_conf.show_total_period) {
- if (sep)
- ret += snprintf(s + ret, size - ret, "%c%" PRIu64, *sep, period);
- else
- ret += snprintf(s + ret, size - ret, " %12" PRIu64, period);
- }
-
- if (pair_hists) {
- char bf[32];
- double old_percent = 0, new_percent = 0, diff;
-
- if (total > 0)
- old_percent = (period * 100.0) / total;
- if (session_total > 0)
- new_percent = (self->period * 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 += snprintf(s + ret, size - ret, "%c%s", *sep, bf);
- else
- ret += snprintf(s + ret, size - ret, "%11.11s", bf);
-
- if (show_displacement) {
- if (displacement)
- snprintf(bf, sizeof(bf), "%+4ld", displacement);
- else
- snprintf(bf, sizeof(bf), " ");
-
- if (sep)
- ret += snprintf(s + ret, size - ret, "%c%s", *sep, bf);
- else
- ret += snprintf(s + ret, size - ret, "%6.6s", bf);
- }
- }
+ for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) {
+ struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
- list_for_each_entry(se, &hist_entry__sort_list, list) {
- if (se->elide)
+ if (hists__filter_entry_by_symbol(hists, h))
continue;
- ret += snprintf(s + ret, size - ret, "%s", sep ?: " ");
- ret += se->se_snprintf(self, s + ret, size - ret,
- hists__col_len(hists, se->se_width_idx));
+ hists__remove_entry_filter(hists, h, HIST_FILTER__SYMBOL);
}
-
- return ret;
}
-int hist_entry__fprintf(struct hist_entry *he, size_t size, struct hists *hists,
- struct hists *pair_hists, bool show_displacement,
- long displacement, FILE *fp, u64 session_total)
+int hist_entry__inc_addr_samples(struct hist_entry *he, int evidx, u64 ip)
{
- char bf[512];
-
- if (size == 0 || size > sizeof(bf))
- size = sizeof(bf);
-
- hist_entry__snprintf(he, bf, size, hists, pair_hists,
- show_displacement, displacement,
- true, session_total);
- return fprintf(fp, "%s\n", bf);
+ return symbol__inc_addr_samples(he->ms.sym, he->ms.map, evidx, ip);
}
-static size_t hist_entry__fprintf_callchain(struct hist_entry *self,
- struct hists *hists, FILE *fp,
- u64 session_total)
+int hist_entry__annotate(struct hist_entry *he, size_t privsize)
{
- int left_margin = 0;
-
- if (sort__first_dimension == SORT_COMM) {
- struct sort_entry *se = list_first_entry(&hist_entry__sort_list,
- typeof(*se), list);
- left_margin = hists__col_len(hists, se->se_width_idx);
- left_margin -= thread__comm_len(self->thread);
- }
+ return symbol__annotate(he->ms.sym, he->ms.map, privsize);
+}
- return hist_entry_callchain__fprintf(fp, self, session_total,
- left_margin);
+void events_stats__inc(struct events_stats *stats, u32 type)
+{
+ ++stats->nr_events[0];
+ ++stats->nr_events[type];
}
-size_t hists__fprintf(struct hists *hists, struct hists *pair,
- bool show_displacement, bool show_header, int max_rows,
- int max_cols, FILE *fp)
+void hists__inc_nr_events(struct hists *hists, u32 type)
{
- 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;
- const char *col_width = symbol_conf.col_width_list_str;
- int nr_rows = 0;
+ events_stats__inc(&hists->stats, type);
+}
- init_rem_hits();
+static struct hist_entry *hists__add_dummy_entry(struct hists *hists,
+ struct hist_entry *pair)
+{
+ struct rb_root *root;
+ struct rb_node **p;
+ struct rb_node *parent = NULL;
+ struct hist_entry *he;
+ int cmp;
- if (!show_header)
- goto print_entries;
+ if (sort__need_collapse)
+ root = &hists->entries_collapsed;
+ else
+ root = hists->entries_in;
- fprintf(fp, "# %s", pair ? "Baseline" : "Overhead");
+ p = &root->rb_node;
- if (symbol_conf.show_nr_samples) {
- if (sep)
- fprintf(fp, "%cSamples", *sep);
- else
- fputs(" Samples ", fp);
- }
+ while (*p != NULL) {
+ parent = *p;
+ he = rb_entry(parent, struct hist_entry, rb_node_in);
- if (symbol_conf.show_total_period) {
- if (sep)
- ret += fprintf(fp, "%cPeriod", *sep);
- else
- ret += fprintf(fp, " Period ");
- }
+ cmp = hist_entry__collapse(he, pair);
- if (symbol_conf.show_cpu_utilization) {
- if (sep) {
- ret += fprintf(fp, "%csys", *sep);
- ret += fprintf(fp, "%cus", *sep);
- if (perf_guest) {
- ret += fprintf(fp, "%cguest sys", *sep);
- ret += fprintf(fp, "%cguest us", *sep);
- }
- } else {
- ret += fprintf(fp, " sys ");
- ret += fprintf(fp, " us ");
- if (perf_guest) {
- ret += fprintf(fp, " guest sys ");
- ret += fprintf(fp, " guest us ");
- }
- }
- }
+ if (!cmp)
+ goto out;
- if (pair) {
- if (sep)
- ret += fprintf(fp, "%cDelta", *sep);
+ if (cmp < 0)
+ p = &(*p)->rb_left;
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->se_header);
- continue;
- }
- width = strlen(se->se_header);
- if (symbol_conf.col_width_list_str) {
- if (col_width) {
- hists__set_col_len(hists, se->se_width_idx,
- atoi(col_width));
- col_width = strchr(col_width, ',');
- if (col_width)
- ++col_width;
- }
- }
- if (!hists__new_col_len(hists, se->se_width_idx, width))
- width = hists__col_len(hists, se->se_width_idx);
- fprintf(fp, " %*s", width, se->se_header);
+ p = &(*p)->rb_right;
}
- fprintf(fp, "\n");
- if (max_rows && ++nr_rows >= max_rows)
- goto out;
-
- if (sep)
- goto print_entries;
-
- fprintf(fp, "# ........");
- if (symbol_conf.show_nr_samples)
- fprintf(fp, " ..........");
- if (symbol_conf.show_total_period)
- 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, " ");
- width = hists__col_len(hists, se->se_width_idx);
- if (width == 0)
- width = strlen(se->se_header);
- for (i = 0; i < width; i++)
- fprintf(fp, ".");
- }
-
- fprintf(fp, "\n");
- if (max_rows && ++nr_rows >= max_rows)
- goto out;
-
- fprintf(fp, "#\n");
- if (max_rows && ++nr_rows >= max_rows)
- goto out;
-
-print_entries:
- for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) {
- struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
-
- if (h->filtered)
- continue;
-
- if (show_displacement) {
- if (h->pair != NULL)
- displacement = ((long)h->pair->position -
- (long)position);
- else
- displacement = 0;
- ++position;
- }
- ret += hist_entry__fprintf(h, max_cols, hists, pair, show_displacement,
- displacement, fp, hists->stats.total_period);
-
- if (symbol_conf.use_callchain)
- ret += hist_entry__fprintf_callchain(h, hists, fp,
- hists->stats.total_period);
- if (max_rows && ++nr_rows >= max_rows)
- goto out;
-
- if (h->ms.map == NULL && verbose > 1) {
- __map_groups__fprintf_maps(&h->thread->mg,
- MAP__FUNCTION, verbose, fp);
- fprintf(fp, "%.10s end\n", graph_dotted_line);
- }
+ he = hist_entry__new(pair);
+ if (he) {
+ memset(&he->stat, 0, sizeof(he->stat));
+ he->hists = hists;
+ rb_link_node(&he->rb_node_in, parent, p);
+ rb_insert_color(&he->rb_node_in, root);
+ hists__inc_nr_entries(hists, he);
}
out:
- free(rem_sq_bracket);
-
- return ret;
+ return he;
}
-/*
- * See hists__fprintf to match the column widths
- */
-unsigned int hists__sort_list_width(struct hists *hists)
+static struct hist_entry *hists__find_entry(struct hists *hists,
+ struct hist_entry *he)
{
- struct sort_entry *se;
- int ret = 9; /* total % */
-
- if (symbol_conf.show_cpu_utilization) {
- ret += 7; /* count_sys % */
- ret += 6; /* count_us % */
- if (perf_guest) {
- ret += 13; /* count_guest_sys % */
- ret += 12; /* count_guest_us % */
- }
- }
-
- if (symbol_conf.show_nr_samples)
- ret += 11;
-
- if (symbol_conf.show_total_period)
- ret += 13;
+ struct rb_node *n;
- list_for_each_entry(se, &hist_entry__sort_list, list)
- if (!se->elide)
- ret += 2 + hists__col_len(hists, se->se_width_idx);
-
- if (verbose) /* Addr + origin */
- ret += 3 + BITS_PER_LONG / 4;
-
- return ret;
-}
+ if (sort__need_collapse)
+ n = hists->entries_collapsed.rb_node;
+ else
+ n = hists->entries_in->rb_node;
-static void hists__remove_entry_filter(struct hists *hists, struct hist_entry *h,
- enum hist_filter filter)
-{
- h->filtered &= ~(1 << filter);
- if (h->filtered)
- return;
+ while (n) {
+ struct hist_entry *iter = rb_entry(n, struct hist_entry, rb_node_in);
+ int64_t cmp = hist_entry__collapse(iter, he);
- ++hists->nr_entries;
- if (h->ms.unfolded)
- hists->nr_entries += h->nr_rows;
- h->row_offset = 0;
- hists->stats.total_period += h->period;
- hists->stats.nr_events[PERF_RECORD_SAMPLE] += h->nr_events;
+ if (cmp < 0)
+ n = n->rb_left;
+ else if (cmp > 0)
+ n = n->rb_right;
+ else
+ return iter;
+ }
- hists__calc_col_len(hists, h);
+ return NULL;
}
-void hists__filter_by_dso(struct hists *hists, const struct dso *dso)
+/*
+ * Look for pairs to link to the leader buckets (hist_entries):
+ */
+void hists__match(struct hists *leader, struct hists *other)
{
+ struct rb_root *root;
struct rb_node *nd;
+ struct hist_entry *pos, *pair;
- hists->nr_entries = hists->stats.total_period = 0;
- hists->stats.nr_events[PERF_RECORD_SAMPLE] = 0;
- hists__reset_col_len(hists);
-
- for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) {
- struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
-
- if (symbol_conf.exclude_other && !h->parent)
- continue;
+ if (sort__need_collapse)
+ root = &leader->entries_collapsed;
+ else
+ root = leader->entries_in;
- if (dso != NULL && (h->ms.map == NULL || h->ms.map->dso != dso)) {
- h->filtered |= (1 << HIST_FILTER__DSO);
- continue;
- }
+ for (nd = rb_first(root); nd; nd = rb_next(nd)) {
+ pos = rb_entry(nd, struct hist_entry, rb_node_in);
+ pair = hists__find_entry(other, pos);
- hists__remove_entry_filter(hists, h, HIST_FILTER__DSO);
+ if (pair)
+ hist_entry__add_pair(pair, pos);
}
}
-void hists__filter_by_thread(struct hists *hists, const struct thread *thread)
+/*
+ * Look for entries in the other hists that are not present in the leader, if
+ * we find them, just add a dummy entry on the leader hists, with period=0,
+ * nr_events=0, to serve as the list header.
+ */
+int hists__link(struct hists *leader, struct hists *other)
{
+ struct rb_root *root;
struct rb_node *nd;
+ struct hist_entry *pos, *pair;
- hists->nr_entries = hists->stats.total_period = 0;
- hists->stats.nr_events[PERF_RECORD_SAMPLE] = 0;
- hists__reset_col_len(hists);
+ if (sort__need_collapse)
+ root = &other->entries_collapsed;
+ else
+ root = other->entries_in;
- for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) {
- struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
+ for (nd = rb_first(root); nd; nd = rb_next(nd)) {
+ pos = rb_entry(nd, struct hist_entry, rb_node_in);
- if (thread != NULL && h->thread != thread) {
- h->filtered |= (1 << HIST_FILTER__THREAD);
- continue;
+ if (!hist_entry__has_pairs(pos)) {
+ pair = hists__add_dummy_entry(leader, pos);
+ if (pair == NULL)
+ return -1;
+ hist_entry__add_pair(pos, pair);
}
-
- hists__remove_entry_filter(hists, h, HIST_FILTER__THREAD);
- }
-}
-
-int hist_entry__inc_addr_samples(struct hist_entry *he, int evidx, u64 ip)
-{
- return symbol__inc_addr_samples(he->ms.sym, he->ms.map, evidx, ip);
-}
-
-int hist_entry__annotate(struct hist_entry *he, size_t privsize)
-{
- return symbol__annotate(he->ms.sym, he->ms.map, privsize);
-}
-
-void hists__inc_nr_events(struct hists *hists, u32 type)
-{
- ++hists->stats.nr_events[0];
- ++hists->stats.nr_events[type];
-}
-
-size_t hists__fprintf_nr_events(struct hists *hists, FILE *fp)
-{
- int i;
- size_t ret = 0;
-
- for (i = 0; i < PERF_RECORD_HEADER_MAX; ++i) {
- const char *name;
-
- if (hists->stats.nr_events[i] == 0)
- continue;
-
- name = perf_event__name(i);
- if (!strcmp(name, "UNKNOWN"))
- continue;
-
- ret += fprintf(fp, "%16s events: %10d\n", name,
- hists->stats.nr_events[i]);
}
- return ret;
-}
-
-void hists__init(struct hists *hists)
-{
- memset(hists, 0, sizeof(*hists));
- hists->entries_in_array[0] = hists->entries_in_array[1] = RB_ROOT;
- hists->entries_in = &hists->entries_in_array[0];
- hists->entries_collapsed = RB_ROOT;
- hists->entries = RB_ROOT;
- pthread_mutex_init(&hists->lock, NULL);
+ return 0;
}