forked from Minki/linux
perf tools: Add code to support PERF_SAMPLE_BRANCH_STACK
This patch adds: - ability to parse samples with PERF_SAMPLE_BRANCH_STACK - sort on branches (dso_from, symbol_from, dso_to, symbol_to, mispredict) - build histograms on branches Signed-off-by: Roberto Agostino Vitillo <ravitillo@lbl.gov> Signed-off-by: Stephane Eranian <eranian@google.com> Cc: peterz@infradead.org Cc: acme@redhat.com Cc: robert.richter@amd.com Cc: ming.m.lin@intel.com Cc: andi@firstfloor.org Cc: asharma@fb.com Cc: vweaver1@eecs.utk.edu Cc: khandual@linux.vnet.ibm.com Cc: dsahern@gmail.com Link: http://lkml.kernel.org/r/1328826068-11713-12-git-send-email-eranian@google.com Signed-off-by: Ingo Molnar <mingo@elte.hu>
This commit is contained in:
parent
d010b3326c
commit
b5387528f3
@ -179,6 +179,23 @@ struct ip_callchain {
|
||||
u64 ips[0];
|
||||
};
|
||||
|
||||
struct branch_flags {
|
||||
u64 mispred:1;
|
||||
u64 predicted:1;
|
||||
u64 reserved:62;
|
||||
};
|
||||
|
||||
struct branch_entry {
|
||||
u64 from;
|
||||
u64 to;
|
||||
struct branch_flags flags;
|
||||
};
|
||||
|
||||
struct branch_stack {
|
||||
u64 nr;
|
||||
struct branch_entry entries[0];
|
||||
};
|
||||
|
||||
extern bool perf_host, perf_guest;
|
||||
extern const char perf_version_string[];
|
||||
|
||||
|
@ -81,6 +81,7 @@ struct perf_sample {
|
||||
u32 raw_size;
|
||||
void *raw_data;
|
||||
struct ip_callchain *callchain;
|
||||
struct branch_stack *branch_stack;
|
||||
};
|
||||
|
||||
#define BUILD_ID_SIZE 20
|
||||
|
@ -576,6 +576,16 @@ int perf_event__parse_sample(const union perf_event *event, u64 type,
|
||||
data->raw_data = (void *) pdata;
|
||||
}
|
||||
|
||||
if (type & PERF_SAMPLE_BRANCH_STACK) {
|
||||
u64 sz;
|
||||
|
||||
data->branch_stack = (struct branch_stack *)array;
|
||||
array++; /* nr */
|
||||
|
||||
sz = data->branch_stack->nr * sizeof(struct branch_entry);
|
||||
sz /= sizeof(u64);
|
||||
array += sz;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -50,21 +50,25 @@ static void hists__reset_col_len(struct hists *hists)
|
||||
hists__set_col_len(hists, col, 0);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static void hists__calc_col_len(struct hists *hists, struct hist_entry *h)
|
||||
{
|
||||
const unsigned int unresolved_col_width = BITS_PER_LONG / 4;
|
||||
u16 len;
|
||||
|
||||
if (h->ms.sym)
|
||||
hists__new_col_len(hists, HISTC_SYMBOL, h->ms.sym->namelen);
|
||||
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);
|
||||
}
|
||||
hists__new_col_len(hists, HISTC_SYMBOL, h->ms.sym->namelen + 4);
|
||||
else
|
||||
hists__set_unres_dso_col_len(hists, HISTC_DSO);
|
||||
|
||||
len = thread__comm_len(h->thread);
|
||||
if (hists__new_col_len(hists, HISTC_COMM, len))
|
||||
@ -74,6 +78,37 @@ static void hists__calc_col_len(struct hists *hists, struct hist_entry *h)
|
||||
len = dso__name_len(h->ms.map->dso);
|
||||
hists__new_col_len(hists, HISTC_DSO, len);
|
||||
}
|
||||
|
||||
if (h->branch_info) {
|
||||
int symlen;
|
||||
/*
|
||||
* +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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void hist_entry__add_cpumode_period(struct hist_entry *he,
|
||||
@ -195,26 +230,14 @@ static u8 symbol__parent_filter(const struct symbol *parent)
|
||||
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)
|
||||
{
|
||||
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);
|
||||
@ -225,7 +248,7 @@ struct hist_entry *__hists__add_entry(struct hists *hists,
|
||||
parent = *p;
|
||||
he = rb_entry(parent, struct hist_entry, rb_node_in);
|
||||
|
||||
cmp = hist_entry__cmp(&entry, he);
|
||||
cmp = hist_entry__cmp(entry, he);
|
||||
|
||||
if (!cmp) {
|
||||
he->period += period;
|
||||
@ -239,7 +262,7 @@ struct hist_entry *__hists__add_entry(struct hists *hists,
|
||||
p = &(*p)->rb_right;
|
||||
}
|
||||
|
||||
he = hist_entry__new(&entry);
|
||||
he = hist_entry__new(entry);
|
||||
if (!he)
|
||||
goto out_unlock;
|
||||
|
||||
@ -252,6 +275,51 @@ out_unlock:
|
||||
return he;
|
||||
}
|
||||
|
||||
struct hist_entry *__hists__add_branch_entry(struct hists *self,
|
||||
struct addr_location *al,
|
||||
struct symbol *sym_parent,
|
||||
struct branch_info *bi,
|
||||
u64 period)
|
||||
{
|
||||
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,
|
||||
.period = period,
|
||||
.parent = sym_parent,
|
||||
.filtered = symbol__parent_filter(sym_parent),
|
||||
.branch_info = bi,
|
||||
};
|
||||
|
||||
return add_hist_entry(self, &entry, al, period);
|
||||
}
|
||||
|
||||
struct hist_entry *__hists__add_entry(struct hists *self,
|
||||
struct addr_location *al,
|
||||
struct symbol *sym_parent, u64 period)
|
||||
{
|
||||
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),
|
||||
};
|
||||
|
||||
return add_hist_entry(self, &entry, al, period);
|
||||
}
|
||||
|
||||
int64_t
|
||||
hist_entry__cmp(struct hist_entry *left, struct hist_entry *right)
|
||||
{
|
||||
|
@ -42,6 +42,11 @@ enum hist_column {
|
||||
HISTC_COMM,
|
||||
HISTC_PARENT,
|
||||
HISTC_CPU,
|
||||
HISTC_MISPREDICT,
|
||||
HISTC_SYMBOL_FROM,
|
||||
HISTC_SYMBOL_TO,
|
||||
HISTC_DSO_FROM,
|
||||
HISTC_DSO_TO,
|
||||
HISTC_NR_COLS, /* Last entry */
|
||||
};
|
||||
|
||||
@ -74,6 +79,12 @@ int hist_entry__snprintf(struct hist_entry *self, char *bf, size_t size,
|
||||
struct hists *hists);
|
||||
void hist_entry__free(struct hist_entry *);
|
||||
|
||||
struct hist_entry *__hists__add_branch_entry(struct hists *self,
|
||||
struct addr_location *al,
|
||||
struct symbol *sym_parent,
|
||||
struct branch_info *bi,
|
||||
u64 period);
|
||||
|
||||
void hists__output_resort(struct hists *self);
|
||||
void hists__output_resort_threaded(struct hists *hists);
|
||||
void hists__collapse_resort(struct hists *self);
|
||||
|
@ -229,6 +229,63 @@ static bool symbol__match_parent_regex(struct symbol *sym)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const u8 cpumodes[] = {
|
||||
PERF_RECORD_MISC_USER,
|
||||
PERF_RECORD_MISC_KERNEL,
|
||||
PERF_RECORD_MISC_GUEST_USER,
|
||||
PERF_RECORD_MISC_GUEST_KERNEL
|
||||
};
|
||||
#define NCPUMODES (sizeof(cpumodes)/sizeof(u8))
|
||||
|
||||
static void ip__resolve_ams(struct machine *self, struct thread *thread,
|
||||
struct addr_map_symbol *ams,
|
||||
u64 ip)
|
||||
{
|
||||
struct addr_location al;
|
||||
size_t i;
|
||||
u8 m;
|
||||
|
||||
memset(&al, 0, sizeof(al));
|
||||
|
||||
for (i = 0; i < NCPUMODES; i++) {
|
||||
m = cpumodes[i];
|
||||
/*
|
||||
* We cannot use the header.misc hint to determine whether a
|
||||
* branch stack address is user, kernel, guest, hypervisor.
|
||||
* Branches may straddle the kernel/user/hypervisor boundaries.
|
||||
* Thus, we have to try consecutively until we find a match
|
||||
* or else, the symbol is unknown
|
||||
*/
|
||||
thread__find_addr_location(thread, self, m, MAP__FUNCTION,
|
||||
ip, &al, NULL);
|
||||
if (al.sym)
|
||||
goto found;
|
||||
}
|
||||
found:
|
||||
ams->addr = ip;
|
||||
ams->sym = al.sym;
|
||||
ams->map = al.map;
|
||||
}
|
||||
|
||||
struct branch_info *machine__resolve_bstack(struct machine *self,
|
||||
struct thread *thr,
|
||||
struct branch_stack *bs)
|
||||
{
|
||||
struct branch_info *bi;
|
||||
unsigned int i;
|
||||
|
||||
bi = calloc(bs->nr, sizeof(struct branch_info));
|
||||
if (!bi)
|
||||
return NULL;
|
||||
|
||||
for (i = 0; i < bs->nr; i++) {
|
||||
ip__resolve_ams(self, thr, &bi[i].to, bs->entries[i].to);
|
||||
ip__resolve_ams(self, thr, &bi[i].from, bs->entries[i].from);
|
||||
bi[i].flags = bs->entries[i].flags;
|
||||
}
|
||||
return bi;
|
||||
}
|
||||
|
||||
int machine__resolve_callchain(struct machine *self, struct perf_evsel *evsel,
|
||||
struct thread *thread,
|
||||
struct ip_callchain *chain,
|
||||
@ -697,6 +754,18 @@ static void callchain__printf(struct perf_sample *sample)
|
||||
i, sample->callchain->ips[i]);
|
||||
}
|
||||
|
||||
static void branch_stack__printf(struct perf_sample *sample)
|
||||
{
|
||||
uint64_t i;
|
||||
|
||||
printf("... branch stack: nr:%" PRIu64 "\n", sample->branch_stack->nr);
|
||||
|
||||
for (i = 0; i < sample->branch_stack->nr; i++)
|
||||
printf("..... %2"PRIu64": %016" PRIx64 " -> %016" PRIx64 "\n",
|
||||
i, sample->branch_stack->entries[i].from,
|
||||
sample->branch_stack->entries[i].to);
|
||||
}
|
||||
|
||||
static void perf_session__print_tstamp(struct perf_session *session,
|
||||
union perf_event *event,
|
||||
struct perf_sample *sample)
|
||||
@ -744,6 +813,9 @@ static void dump_sample(struct perf_session *session, union perf_event *event,
|
||||
|
||||
if (session->sample_type & PERF_SAMPLE_CALLCHAIN)
|
||||
callchain__printf(sample);
|
||||
|
||||
if (session->sample_type & PERF_SAMPLE_BRANCH_STACK)
|
||||
branch_stack__printf(sample);
|
||||
}
|
||||
|
||||
static struct machine *
|
||||
|
@ -73,6 +73,10 @@ int perf_session__resolve_callchain(struct perf_session *self, struct perf_evsel
|
||||
struct ip_callchain *chain,
|
||||
struct symbol **parent);
|
||||
|
||||
struct branch_info *machine__resolve_bstack(struct machine *self,
|
||||
struct thread *thread,
|
||||
struct branch_stack *bs);
|
||||
|
||||
bool perf_session__has_traces(struct perf_session *self, const char *msg);
|
||||
|
||||
void mem_bswap_64(void *src, int byte_size);
|
||||
|
@ -8,6 +8,7 @@ const char default_sort_order[] = "comm,dso,symbol";
|
||||
const char *sort_order = default_sort_order;
|
||||
int sort__need_collapse = 0;
|
||||
int sort__has_parent = 0;
|
||||
bool sort__branch_mode;
|
||||
|
||||
enum sort_type sort__first_dimension;
|
||||
|
||||
@ -94,21 +95,10 @@ static int hist_entry__comm_snprintf(struct hist_entry *self, char *bf,
|
||||
return repsep_snprintf(bf, size, "%*s", width, self->thread->comm);
|
||||
}
|
||||
|
||||
struct sort_entry sort_comm = {
|
||||
.se_header = "Command",
|
||||
.se_cmp = sort__comm_cmp,
|
||||
.se_collapse = sort__comm_collapse,
|
||||
.se_snprintf = hist_entry__comm_snprintf,
|
||||
.se_width_idx = HISTC_COMM,
|
||||
};
|
||||
|
||||
/* --sort dso */
|
||||
|
||||
static int64_t
|
||||
sort__dso_cmp(struct hist_entry *left, struct hist_entry *right)
|
||||
static int64_t _sort__dso_cmp(struct map *map_l, struct map *map_r)
|
||||
{
|
||||
struct dso *dso_l = left->ms.map ? left->ms.map->dso : NULL;
|
||||
struct dso *dso_r = right->ms.map ? right->ms.map->dso : NULL;
|
||||
struct dso *dso_l = map_l ? map_l->dso : NULL;
|
||||
struct dso *dso_r = map_r ? map_r->dso : NULL;
|
||||
const char *dso_name_l, *dso_name_r;
|
||||
|
||||
if (!dso_l || !dso_r)
|
||||
@ -125,18 +115,87 @@ sort__dso_cmp(struct hist_entry *left, struct hist_entry *right)
|
||||
return strcmp(dso_name_l, dso_name_r);
|
||||
}
|
||||
|
||||
static int hist_entry__dso_snprintf(struct hist_entry *self, char *bf,
|
||||
size_t size, unsigned int width)
|
||||
struct sort_entry sort_comm = {
|
||||
.se_header = "Command",
|
||||
.se_cmp = sort__comm_cmp,
|
||||
.se_collapse = sort__comm_collapse,
|
||||
.se_snprintf = hist_entry__comm_snprintf,
|
||||
.se_width_idx = HISTC_COMM,
|
||||
};
|
||||
|
||||
/* --sort dso */
|
||||
|
||||
static int64_t
|
||||
sort__dso_cmp(struct hist_entry *left, struct hist_entry *right)
|
||||
{
|
||||
if (self->ms.map && self->ms.map->dso) {
|
||||
const char *dso_name = !verbose ? self->ms.map->dso->short_name :
|
||||
self->ms.map->dso->long_name;
|
||||
return _sort__dso_cmp(left->ms.map, right->ms.map);
|
||||
}
|
||||
|
||||
|
||||
static int64_t _sort__sym_cmp(struct symbol *sym_l, struct symbol *sym_r,
|
||||
u64 ip_l, u64 ip_r)
|
||||
{
|
||||
if (!sym_l || !sym_r)
|
||||
return cmp_null(sym_l, sym_r);
|
||||
|
||||
if (sym_l == sym_r)
|
||||
return 0;
|
||||
|
||||
if (sym_l)
|
||||
ip_l = sym_l->start;
|
||||
if (sym_r)
|
||||
ip_r = sym_r->start;
|
||||
|
||||
return (int64_t)(ip_r - ip_l);
|
||||
}
|
||||
|
||||
static int _hist_entry__dso_snprintf(struct map *map, char *bf,
|
||||
size_t size, unsigned int width)
|
||||
{
|
||||
if (map && map->dso) {
|
||||
const char *dso_name = !verbose ? map->dso->short_name :
|
||||
map->dso->long_name;
|
||||
return repsep_snprintf(bf, size, "%-*s", width, dso_name);
|
||||
}
|
||||
|
||||
return repsep_snprintf(bf, size, "%-*s", width, "[unknown]");
|
||||
}
|
||||
|
||||
static int hist_entry__dso_snprintf(struct hist_entry *self, char *bf,
|
||||
size_t size, unsigned int width)
|
||||
{
|
||||
return _hist_entry__dso_snprintf(self->ms.map, bf, size, width);
|
||||
}
|
||||
|
||||
static int _hist_entry__sym_snprintf(struct map *map, struct symbol *sym,
|
||||
u64 ip, char level, char *bf, size_t size,
|
||||
unsigned int width __used)
|
||||
{
|
||||
size_t ret = 0;
|
||||
|
||||
if (verbose) {
|
||||
char o = map ? dso__symtab_origin(map->dso) : '!';
|
||||
ret += repsep_snprintf(bf, size, "%-#*llx %c ",
|
||||
BITS_PER_LONG / 4, ip, o);
|
||||
}
|
||||
|
||||
ret += repsep_snprintf(bf + ret, size - ret, "[%c] ", level);
|
||||
if (sym)
|
||||
ret += repsep_snprintf(bf + ret, size - ret, "%-*s",
|
||||
width - ret,
|
||||
sym->name);
|
||||
else {
|
||||
size_t len = BITS_PER_LONG / 4;
|
||||
ret += repsep_snprintf(bf + ret, size - ret, "%-#.*llx",
|
||||
len, ip);
|
||||
ret += repsep_snprintf(bf + ret, size - ret, "%-*s",
|
||||
width - ret, "");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
struct sort_entry sort_dso = {
|
||||
.se_header = "Shared Object",
|
||||
.se_cmp = sort__dso_cmp,
|
||||
@ -144,8 +203,14 @@ struct sort_entry sort_dso = {
|
||||
.se_width_idx = HISTC_DSO,
|
||||
};
|
||||
|
||||
/* --sort symbol */
|
||||
static int hist_entry__sym_snprintf(struct hist_entry *self, char *bf,
|
||||
size_t size, unsigned int width __used)
|
||||
{
|
||||
return _hist_entry__sym_snprintf(self->ms.map, self->ms.sym, self->ip,
|
||||
self->level, bf, size, width);
|
||||
}
|
||||
|
||||
/* --sort symbol */
|
||||
static int64_t
|
||||
sort__sym_cmp(struct hist_entry *left, struct hist_entry *right)
|
||||
{
|
||||
@ -163,31 +228,7 @@ sort__sym_cmp(struct hist_entry *left, struct hist_entry *right)
|
||||
ip_l = left->ms.sym->start;
|
||||
ip_r = right->ms.sym->start;
|
||||
|
||||
return (int64_t)(ip_r - ip_l);
|
||||
}
|
||||
|
||||
static int hist_entry__sym_snprintf(struct hist_entry *self, char *bf,
|
||||
size_t size, unsigned int width __used)
|
||||
{
|
||||
size_t ret = 0;
|
||||
|
||||
if (verbose) {
|
||||
char o = self->ms.map ? dso__symtab_origin(self->ms.map->dso) : '!';
|
||||
ret += repsep_snprintf(bf, size, "%-#*llx %c ",
|
||||
BITS_PER_LONG / 4, self->ip, o);
|
||||
}
|
||||
|
||||
if (!sort_dso.elide)
|
||||
ret += repsep_snprintf(bf + ret, size - ret, "[%c] ", self->level);
|
||||
|
||||
if (self->ms.sym)
|
||||
ret += repsep_snprintf(bf + ret, size - ret, "%s",
|
||||
self->ms.sym->name);
|
||||
else
|
||||
ret += repsep_snprintf(bf + ret, size - ret, "%-#*llx",
|
||||
BITS_PER_LONG / 4, self->ip);
|
||||
|
||||
return ret;
|
||||
return _sort__sym_cmp(left->ms.sym, right->ms.sym, ip_l, ip_r);
|
||||
}
|
||||
|
||||
struct sort_entry sort_sym = {
|
||||
@ -246,19 +287,155 @@ struct sort_entry sort_cpu = {
|
||||
.se_width_idx = HISTC_CPU,
|
||||
};
|
||||
|
||||
static int64_t
|
||||
sort__dso_from_cmp(struct hist_entry *left, struct hist_entry *right)
|
||||
{
|
||||
return _sort__dso_cmp(left->branch_info->from.map,
|
||||
right->branch_info->from.map);
|
||||
}
|
||||
|
||||
static int hist_entry__dso_from_snprintf(struct hist_entry *self, char *bf,
|
||||
size_t size, unsigned int width)
|
||||
{
|
||||
return _hist_entry__dso_snprintf(self->branch_info->from.map,
|
||||
bf, size, width);
|
||||
}
|
||||
|
||||
struct sort_entry sort_dso_from = {
|
||||
.se_header = "Source Shared Object",
|
||||
.se_cmp = sort__dso_from_cmp,
|
||||
.se_snprintf = hist_entry__dso_from_snprintf,
|
||||
.se_width_idx = HISTC_DSO_FROM,
|
||||
};
|
||||
|
||||
static int64_t
|
||||
sort__dso_to_cmp(struct hist_entry *left, struct hist_entry *right)
|
||||
{
|
||||
return _sort__dso_cmp(left->branch_info->to.map,
|
||||
right->branch_info->to.map);
|
||||
}
|
||||
|
||||
static int hist_entry__dso_to_snprintf(struct hist_entry *self, char *bf,
|
||||
size_t size, unsigned int width)
|
||||
{
|
||||
return _hist_entry__dso_snprintf(self->branch_info->to.map,
|
||||
bf, size, width);
|
||||
}
|
||||
|
||||
static int64_t
|
||||
sort__sym_from_cmp(struct hist_entry *left, struct hist_entry *right)
|
||||
{
|
||||
struct addr_map_symbol *from_l = &left->branch_info->from;
|
||||
struct addr_map_symbol *from_r = &right->branch_info->from;
|
||||
|
||||
if (!from_l->sym && !from_r->sym)
|
||||
return right->level - left->level;
|
||||
|
||||
return _sort__sym_cmp(from_l->sym, from_r->sym, from_l->addr,
|
||||
from_r->addr);
|
||||
}
|
||||
|
||||
static int64_t
|
||||
sort__sym_to_cmp(struct hist_entry *left, struct hist_entry *right)
|
||||
{
|
||||
struct addr_map_symbol *to_l = &left->branch_info->to;
|
||||
struct addr_map_symbol *to_r = &right->branch_info->to;
|
||||
|
||||
if (!to_l->sym && !to_r->sym)
|
||||
return right->level - left->level;
|
||||
|
||||
return _sort__sym_cmp(to_l->sym, to_r->sym, to_l->addr, to_r->addr);
|
||||
}
|
||||
|
||||
static int hist_entry__sym_from_snprintf(struct hist_entry *self, char *bf,
|
||||
size_t size, unsigned int width __used)
|
||||
{
|
||||
struct addr_map_symbol *from = &self->branch_info->from;
|
||||
return _hist_entry__sym_snprintf(from->map, from->sym, from->addr,
|
||||
self->level, bf, size, width);
|
||||
|
||||
}
|
||||
|
||||
static int hist_entry__sym_to_snprintf(struct hist_entry *self, char *bf,
|
||||
size_t size, unsigned int width __used)
|
||||
{
|
||||
struct addr_map_symbol *to = &self->branch_info->to;
|
||||
return _hist_entry__sym_snprintf(to->map, to->sym, to->addr,
|
||||
self->level, bf, size, width);
|
||||
|
||||
}
|
||||
|
||||
struct sort_entry sort_dso_to = {
|
||||
.se_header = "Target Shared Object",
|
||||
.se_cmp = sort__dso_to_cmp,
|
||||
.se_snprintf = hist_entry__dso_to_snprintf,
|
||||
.se_width_idx = HISTC_DSO_TO,
|
||||
};
|
||||
|
||||
struct sort_entry sort_sym_from = {
|
||||
.se_header = "Source Symbol",
|
||||
.se_cmp = sort__sym_from_cmp,
|
||||
.se_snprintf = hist_entry__sym_from_snprintf,
|
||||
.se_width_idx = HISTC_SYMBOL_FROM,
|
||||
};
|
||||
|
||||
struct sort_entry sort_sym_to = {
|
||||
.se_header = "Target Symbol",
|
||||
.se_cmp = sort__sym_to_cmp,
|
||||
.se_snprintf = hist_entry__sym_to_snprintf,
|
||||
.se_width_idx = HISTC_SYMBOL_TO,
|
||||
};
|
||||
|
||||
static int64_t
|
||||
sort__mispredict_cmp(struct hist_entry *left, struct hist_entry *right)
|
||||
{
|
||||
const unsigned char mp = left->branch_info->flags.mispred !=
|
||||
right->branch_info->flags.mispred;
|
||||
const unsigned char p = left->branch_info->flags.predicted !=
|
||||
right->branch_info->flags.predicted;
|
||||
|
||||
return mp || p;
|
||||
}
|
||||
|
||||
static int hist_entry__mispredict_snprintf(struct hist_entry *self, char *bf,
|
||||
size_t size, unsigned int width){
|
||||
static const char *out = "N/A";
|
||||
|
||||
if (self->branch_info->flags.predicted)
|
||||
out = "N";
|
||||
else if (self->branch_info->flags.mispred)
|
||||
out = "Y";
|
||||
|
||||
return repsep_snprintf(bf, size, "%-*s", width, out);
|
||||
}
|
||||
|
||||
struct sort_entry sort_mispredict = {
|
||||
.se_header = "Branch Mispredicted",
|
||||
.se_cmp = sort__mispredict_cmp,
|
||||
.se_snprintf = hist_entry__mispredict_snprintf,
|
||||
.se_width_idx = HISTC_MISPREDICT,
|
||||
};
|
||||
|
||||
struct sort_dimension {
|
||||
const char *name;
|
||||
struct sort_entry *entry;
|
||||
int taken;
|
||||
};
|
||||
|
||||
#define DIM(d, n, func) [d] = { .name = n, .entry = &(func) }
|
||||
|
||||
static struct sort_dimension sort_dimensions[] = {
|
||||
{ .name = "pid", .entry = &sort_thread, },
|
||||
{ .name = "comm", .entry = &sort_comm, },
|
||||
{ .name = "dso", .entry = &sort_dso, },
|
||||
{ .name = "symbol", .entry = &sort_sym, },
|
||||
{ .name = "parent", .entry = &sort_parent, },
|
||||
{ .name = "cpu", .entry = &sort_cpu, },
|
||||
DIM(SORT_PID, "pid", sort_thread),
|
||||
DIM(SORT_COMM, "comm", sort_comm),
|
||||
DIM(SORT_DSO, "dso", sort_dso),
|
||||
DIM(SORT_DSO_FROM, "dso_from", sort_dso_from),
|
||||
DIM(SORT_DSO_TO, "dso_to", sort_dso_to),
|
||||
DIM(SORT_SYM, "symbol", sort_sym),
|
||||
DIM(SORT_SYM_TO, "symbol_from", sort_sym_from),
|
||||
DIM(SORT_SYM_FROM, "symbol_to", sort_sym_to),
|
||||
DIM(SORT_PARENT, "parent", sort_parent),
|
||||
DIM(SORT_CPU, "cpu", sort_cpu),
|
||||
DIM(SORT_MISPREDICT, "mispredict", sort_mispredict),
|
||||
};
|
||||
|
||||
int sort_dimension__add(const char *tok)
|
||||
@ -270,7 +447,6 @@ int sort_dimension__add(const char *tok)
|
||||
|
||||
if (strncasecmp(tok, sd->name, strlen(tok)))
|
||||
continue;
|
||||
|
||||
if (sd->entry == &sort_parent) {
|
||||
int ret = regcomp(&parent_regex, parent_pattern, REG_EXTENDED);
|
||||
if (ret) {
|
||||
@ -302,6 +478,16 @@ int sort_dimension__add(const char *tok)
|
||||
sort__first_dimension = SORT_PARENT;
|
||||
else if (!strcmp(sd->name, "cpu"))
|
||||
sort__first_dimension = SORT_CPU;
|
||||
else if (!strcmp(sd->name, "symbol_from"))
|
||||
sort__first_dimension = SORT_SYM_FROM;
|
||||
else if (!strcmp(sd->name, "symbol_to"))
|
||||
sort__first_dimension = SORT_SYM_TO;
|
||||
else if (!strcmp(sd->name, "dso_from"))
|
||||
sort__first_dimension = SORT_DSO_FROM;
|
||||
else if (!strcmp(sd->name, "dso_to"))
|
||||
sort__first_dimension = SORT_DSO_TO;
|
||||
else if (!strcmp(sd->name, "mispredict"))
|
||||
sort__first_dimension = SORT_MISPREDICT;
|
||||
}
|
||||
|
||||
list_add_tail(&sd->entry->list, &hist_entry__sort_list);
|
||||
@ -309,7 +495,6 @@ int sort_dimension__add(const char *tok)
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -ESRCH;
|
||||
}
|
||||
|
||||
|
@ -31,11 +31,14 @@ extern const char *parent_pattern;
|
||||
extern const char default_sort_order[];
|
||||
extern int sort__need_collapse;
|
||||
extern int sort__has_parent;
|
||||
extern bool sort__branch_mode;
|
||||
extern char *field_sep;
|
||||
extern struct sort_entry sort_comm;
|
||||
extern struct sort_entry sort_dso;
|
||||
extern struct sort_entry sort_sym;
|
||||
extern struct sort_entry sort_parent;
|
||||
extern struct sort_entry sort_lbr_dso;
|
||||
extern struct sort_entry sort_lbr_sym;
|
||||
extern enum sort_type sort__first_dimension;
|
||||
|
||||
/**
|
||||
@ -72,6 +75,7 @@ struct hist_entry {
|
||||
struct hist_entry *pair;
|
||||
struct rb_root sorted_chain;
|
||||
};
|
||||
struct branch_info *branch_info;
|
||||
struct callchain_root callchain[0];
|
||||
};
|
||||
|
||||
@ -82,6 +86,11 @@ enum sort_type {
|
||||
SORT_SYM,
|
||||
SORT_PARENT,
|
||||
SORT_CPU,
|
||||
SORT_DSO_FROM,
|
||||
SORT_DSO_TO,
|
||||
SORT_SYM_FROM,
|
||||
SORT_SYM_TO,
|
||||
SORT_MISPREDICT,
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "map.h"
|
||||
#include "../perf.h"
|
||||
#include <linux/list.h>
|
||||
#include <linux/rbtree.h>
|
||||
#include <stdio.h>
|
||||
@ -120,6 +121,18 @@ struct map_symbol {
|
||||
bool has_children;
|
||||
};
|
||||
|
||||
struct addr_map_symbol {
|
||||
struct map *map;
|
||||
struct symbol *sym;
|
||||
u64 addr;
|
||||
};
|
||||
|
||||
struct branch_info {
|
||||
struct addr_map_symbol from;
|
||||
struct addr_map_symbol to;
|
||||
struct branch_flags flags;
|
||||
};
|
||||
|
||||
struct addr_location {
|
||||
struct thread *thread;
|
||||
struct map *map;
|
||||
|
Loading…
Reference in New Issue
Block a user