[RFC PATCH] perf report: add sort by file lines

From: Lin Ming
Date: Tue Mar 29 2011 - 05:32:50 EST


Hi, all

This patch adds sort by file lines using dwarf debug info.

In order to add perf tool support for my load latency patches,
I asked a question about data variable symbols.
http://marc.info/?l=linux-kernel&m=129736540309559&w=2

Peter suggested to reverse map the reported IP (PEBS + fixup)
to a data access using dwarf info.
So I wrote this patch to see if the direction is right.

On Fri, Feb 11, 2011 at 3:17 AM, Peter Zijlstra <peterz@xxxxxxxxxxxxx> wrote:
> Another way is to reverse map the reported IP (PEBS + fixup) to a data
> access using the dwarf info. That would also work for dynamically
> allocated data structures.
>
> (clearly you'd loose variable things like which entry in an array, but
> you should still be able to identify the structure members)
>

$ ./perf report --stdio -k ~/vmlinux -s comm,dso,symbol,line

# Overhead Command Shared Object Symbol Line
# ........ ........... .................. ............... ..............................................

0.99% cc1 [kernel.kallsyms] [k] check_bytes /opt/linux-2.6/mm/slub.c:511
0.84% fixdep [kernel.kallsyms] [k] check_bytes /opt/linux-2.6/mm/slub.c:510
0.79% fixdep [kernel.kallsyms] [k] check_bytes /opt/linux-2.6/mm/slub.c:511
0.74% cc1 [kernel.kallsyms] [k] check_bytes /opt/linux-2.6/mm/slub.c:513
0.71% fixdep [kernel.kallsyms] [k] check_bytes /opt/linux-2.6/mm/slub.c:513
0.71% cc1 [kernel.kallsyms] [k] page_fault /opt/linux-2.6/arch/x86/kernel/entry_64.S:1336
0.69% cc1 cc1 [.] 0x5ec3a3 0x5ec3a3
0.67% cc1 [kernel.kallsyms] [k] clear_page_c /opt/linux-2.6/arch/x86/lib/clear_page_64.S:12


Signed-off-by: Lin Ming <ming.m.lin@xxxxxxxxx>
---
tools/perf/util/hist.c | 71 ++++++++++++++++++++++++++++++++++++++-------
tools/perf/util/hist.h | 4 ++
tools/perf/util/sort.c | 44 ++++++++++++++++++++++++++--
tools/perf/util/sort.h | 3 ++
tools/perf/util/symbol.c | 4 ++
tools/perf/util/symbol.h | 2 +
6 files changed, 114 insertions(+), 14 deletions(-)

diff --git a/tools/perf/util/hist.c b/tools/perf/util/hist.c
index 627a02e..c1e95e4 100644
--- a/tools/perf/util/hist.c
+++ b/tools/perf/util/hist.c
@@ -17,6 +17,38 @@ struct callchain_param callchain_param = {
.min_percent = 0.5
};

+static Dwarf_Line *hists__dwarf_line(struct map *map, u64 rip)
+{
+ Dwarf_Die cudie;
+ Dwarf_Line *line = NULL;
+ u64 ip;
+
+ if (!map || !map->dso || !map->dso->dwarf)
+ return NULL;
+
+ ip = map->unmap_ip(map, rip);
+ if (dwarf_addrdie(map->dso->dwarf, (Dwarf_Addr)ip, &cudie))
+ line = dwarf_getsrc_die(&cudie, (Dwarf_Addr)ip);
+
+ return line;
+}
+
+int hists__line(Dwarf_Line *line, char *buf, int len)
+{
+ int ret;
+ const char *file;
+ int lineno;
+
+ if (!line || !buf)
+ return 0;
+
+ file = dwarf_linesrc(line, NULL, NULL);
+ dwarf_lineno(line, &lineno);
+ ret = snprintf(buf, len, "%s:%d", file, lineno);
+
+ return ret;
+}
+
u16 hists__col_len(struct hists *self, enum hist_column col)
{
return self->col_len[col];
@@ -44,21 +76,25 @@ static void hists__reset_col_len(struct hists *self)
hists__set_col_len(self, col, 0);
}

+static void hists__set_unresolved_col_len(struct hists *self, enum hist_column col)
+{
+ const unsigned int unresolved_col_width = BITS_PER_LONG / 4 + 2;
+
+ if (hists__col_len(self, col) < unresolved_col_width &&
+ !symbol_conf.col_width_list_str && !symbol_conf.field_sep &&
+ !symbol_conf.dso_list)
+ hists__set_col_len(self, col,
+ unresolved_col_width);
+}
+
static void hists__calc_col_len(struct hists *self, struct hist_entry *h)
{
u16 len;

if (h->ms.sym)
- hists__new_col_len(self, HISTC_SYMBOL, h->ms.sym->namelen);
- else {
- const unsigned int unresolved_col_width = BITS_PER_LONG / 4;
-
- if (hists__col_len(self, HISTC_DSO) < unresolved_col_width &&
- !symbol_conf.col_width_list_str && !symbol_conf.field_sep &&
- !symbol_conf.dso_list)
- hists__set_col_len(self, HISTC_DSO,
- unresolved_col_width);
- }
+ hists__new_col_len(self, HISTC_SYMBOL, h->ms.sym->namelen + 4);
+ else
+ hists__set_unresolved_col_len(self, HISTC_DSO);

len = thread__comm_len(h->thread);
if (hists__new_col_len(self, HISTC_COMM, len))
@@ -68,6 +104,15 @@ static void hists__calc_col_len(struct hists *self, struct hist_entry *h)
len = dso__name_len(h->ms.map->dso);
hists__new_col_len(self, HISTC_DSO, len);
}
+
+ if (!h->line)
+ hists__set_unresolved_col_len(self, HISTC_LINE);
+ else {
+ char tmp[BUFSIZ];
+
+ len = hists__line(h->line, tmp, BUFSIZ);
+ hists__new_col_len(self, HISTC_LINE, len);
+ }
}

static void hist_entry__add_cpumode_period(struct hist_entry *self,
@@ -103,8 +148,11 @@ static struct hist_entry *hist_entry__new(struct hist_entry *template)
if (self != NULL) {
*self = *template;
self->nr_events = 1;
- if (self->ms.map)
+ if (self->ms.map) {
self->ms.map->referenced = true;
+
+ self->line = hists__dwarf_line(self->ms.map, self->ip);
+ }
if (symbol_conf.use_callchain)
callchain_init(self->callchain);
}
@@ -142,6 +190,7 @@ struct hist_entry *__hists__add_entry(struct hists *self,
},
.cpu = al->cpu,
.ip = al->addr,
+ .line = hists__dwarf_line(al->map, al->addr),
.level = al->level,
.period = period,
.parent = sym_parent,
diff --git a/tools/perf/util/hist.h b/tools/perf/util/hist.h
index 3beb97c..07e0f04 100644
--- a/tools/perf/util/hist.h
+++ b/tools/perf/util/hist.h
@@ -3,6 +3,7 @@

#include <linux/types.h>
#include "callchain.h"
+#include <elfutils/libdw.h>

extern struct callchain_param callchain_param;

@@ -39,6 +40,7 @@ enum hist_column {
HISTC_COMM,
HISTC_PARENT,
HISTC_CPU,
+ HISTC_LINE,
HISTC_NR_COLS, /* Last entry */
};

@@ -85,6 +87,8 @@ u16 hists__col_len(struct hists *self, enum hist_column col);
void hists__set_col_len(struct hists *self, enum hist_column col, u16 len);
bool hists__new_col_len(struct hists *self, enum hist_column col, u16 len);

+int hists__line(Dwarf_Line *line, char *buf, int len);
+
struct perf_evlist;

#ifdef NO_NEWT_SUPPORT
diff --git a/tools/perf/util/sort.c b/tools/perf/util/sort.c
index f44fa54..cfbdb6c 100644
--- a/tools/perf/util/sort.c
+++ b/tools/perf/util/sort.c
@@ -27,6 +27,9 @@ static int hist_entry__parent_snprintf(struct hist_entry *self, char *bf,
size_t size, unsigned int width);
static int hist_entry__cpu_snprintf(struct hist_entry *self, char *bf,
size_t size, unsigned int width);
+static int hist_entry__line_snprintf(struct hist_entry *self, char *bf,
+ size_t size, unsigned int width);
+

struct sort_entry sort_thread = {
.se_header = "Command: Pid",
@@ -71,6 +74,13 @@ struct sort_entry sort_cpu = {
.se_width_idx = HISTC_CPU,
};

+struct sort_entry sort_line = {
+ .se_header = "Line",
+ .se_cmp = sort__line_cmp,
+ .se_snprintf = hist_entry__line_snprintf,
+ .se_width_idx = HISTC_LINE,
+};
+
struct sort_dimension {
const char *name;
struct sort_entry *entry;
@@ -84,6 +94,7 @@ static struct sort_dimension sort_dimensions[] = {
{ .name = "symbol", .entry = &sort_sym, },
{ .name = "parent", .entry = &sort_parent, },
{ .name = "cpu", .entry = &sort_cpu, },
+ { .name = "line", .entry = &sort_line, },
};

int64_t cmp_null(void *l, void *r)
@@ -190,7 +201,7 @@ sort__sym_cmp(struct hist_entry *left, struct hist_entry *right)
}

static int hist_entry__sym_snprintf(struct hist_entry *self, char *bf,
- size_t size, unsigned int width __used)
+ size_t size, unsigned int width)
{
size_t ret = 0;

@@ -202,11 +213,11 @@ static int hist_entry__sym_snprintf(struct hist_entry *self, char *bf,

ret += repsep_snprintf(bf + ret, size - ret, "[%c] ", self->level);
if (self->ms.sym)
- ret += repsep_snprintf(bf + ret, size - ret, "%s",
+ ret += repsep_snprintf(bf + ret, size - ret, "%-*s", width - 4,
self->ms.sym->name);
else
ret += repsep_snprintf(bf + ret, size - ret, "%-#*llx",
- BITS_PER_LONG / 4, self->ip);
+ width - 4, self->ip);

return ret;
}
@@ -266,6 +277,31 @@ static int hist_entry__cpu_snprintf(struct hist_entry *self, char *bf,
return repsep_snprintf(bf, size, "%-*d", width, self->cpu);
}

+
+/* --sort line */
+
+int64_t
+sort__line_cmp(struct hist_entry *left, struct hist_entry *right)
+{
+ if (!left->line || !right->line)
+ return (int64_t)(left->ip - right->ip);
+
+ return (unsigned long)left->line - (unsigned long)right->line;
+}
+
+static int hist_entry__line_snprintf(struct hist_entry *self, char *bf,
+ size_t size, unsigned int width)
+{
+ char buf[BUFSIZ];
+
+ if (!self->line)
+ return repsep_snprintf(bf, size, "%-#*llx", width, self->ip);
+
+ hists__line(self->line, buf, BUFSIZ);
+
+ return repsep_snprintf(bf, size, "%-*s", width, buf);
+}
+
int sort_dimension__add(const char *tok)
{
unsigned int i;
@@ -307,6 +343,8 @@ 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, "line"))
+ sort__first_dimension = SORT_LINE;
}

list_add_tail(&sd->entry->list, &hist_entry__sort_list);
diff --git a/tools/perf/util/sort.h b/tools/perf/util/sort.h
index 0b91053..d2a4424 100644
--- a/tools/perf/util/sort.h
+++ b/tools/perf/util/sort.h
@@ -53,6 +53,7 @@ struct hist_entry {
u64 period_guest_us;
struct map_symbol ms;
struct thread *thread;
+ Dwarf_Line *line;
u64 ip;
s32 cpu;
u32 nr_events;
@@ -80,6 +81,7 @@ enum sort_type {
SORT_SYM,
SORT_PARENT,
SORT_CPU,
+ SORT_LINE,
};

/*
@@ -116,6 +118,7 @@ extern int64_t sort__dso_cmp(struct hist_entry *, struct hist_entry *);
extern int64_t sort__sym_cmp(struct hist_entry *, struct hist_entry *);
extern int64_t sort__parent_cmp(struct hist_entry *, struct hist_entry *);
int64_t sort__cpu_cmp(struct hist_entry *left, struct hist_entry *right);
+int64_t sort__line_cmp(struct hist_entry *left, struct hist_entry *right);
extern size_t sort__parent_print(FILE *, struct hist_entry *, unsigned int);
extern int sort_dimension__add(const char *);
void sort_entry__setup_elide(struct sort_entry *self, struct strlist *list,
diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c
index 17df793..ddaf396 100644
--- a/tools/perf/util/symbol.c
+++ b/tools/perf/util/symbol.c
@@ -240,6 +240,8 @@ void dso__delete(struct dso *self)
free((char *)self->short_name);
if (self->lname_alloc)
free(self->long_name);
+ if (self->dwarf)
+ dwarf_end(self->dwarf);
free(self);
}

@@ -1052,6 +1054,8 @@ static int dso__load_sym(struct dso *self, struct map *map, const char *name,
int nr = 0;
size_t opdidx = 0;

+ self->dwarf = dwarf_begin(fd, DWARF_C_READ);
+
elf = elf_begin(fd, PERF_ELF_C_READ_MMAP, NULL);
if (elf == NULL) {
pr_debug("%s: cannot read %s ELF file.\n", __func__, name);
diff --git a/tools/perf/util/symbol.h b/tools/perf/util/symbol.h
index 713b0b4..e07b907 100644
--- a/tools/perf/util/symbol.h
+++ b/tools/perf/util/symbol.h
@@ -8,6 +8,7 @@
#include <linux/list.h>
#include <linux/rbtree.h>
#include <stdio.h>
+#include <elfutils/libdw.h>

#ifdef HAVE_CPLUS_DEMANGLE
extern char *cplus_demangle(const char *, int);
@@ -135,6 +136,7 @@ struct dso {
struct list_head node;
struct rb_root symbols[MAP__NR_TYPES];
struct rb_root symbol_names[MAP__NR_TYPES];
+ Dwarf *dwarf;
enum dso_kernel_type kernel;
u8 adjust_symbols:1;
u8 has_build_id:1;


--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/