2013-03-26 19:15:05

by Roberto Vitillo

[permalink] [raw]
Subject: [PATCH v2] perf: add callgrind conversion tool

The proposed patch adds the convert tool to perf which allows to convert a
perf.data file to a set of callgrind data files which can subsequently be
displayed with kcachegrind.

Note that the code may trigger the following bug in libbfd:
http://sourceware.org/bugzilla/show_bug.cgi?id=15106

Signed-off-by: Roberto Agostino Vitillo <[email protected]>
---
tools/perf/Documentation/perf-convert.txt | 74 ++++
tools/perf/Makefile | 12 +
tools/perf/builtin-convert.c | 652 ++++++++++++++++++++++++++++++
tools/perf/builtin.h | 1 +
tools/perf/command-list.txt | 1 +
tools/perf/perf.c | 3 +
tools/perf/util/a2l.c | 147 +++++++
tools/perf/util/a2l.h | 9 +
8 files changed, 899 insertions(+)
create mode 100644 tools/perf/Documentation/perf-convert.txt
create mode 100644 tools/perf/builtin-convert.c
create mode 100644 tools/perf/util/a2l.c
create mode 100644 tools/perf/util/a2l.h

diff --git a/tools/perf/Documentation/perf-convert.txt
b/tools/perf/Documentation/perf-convert.txt
new file mode 100644
index 0000000..fa75933
--- /dev/null
+++ b/tools/perf/Documentation/perf-convert.txt
@@ -0,0 +1,74 @@
+perf-convert(1)
+================
+
+NAME
+----
+perf-convert - Convert perf.data (created by perf record) to a set of
callgrind
+data files.
+
+SYNOPSIS
+--------
+[verse]
+'perf convert' [-i <file> | --input=file] [-p <name> | --prefix=name]
+
+DESCRIPTION
+-----------
+This command converts the input file to a set of callgrind data files
which can
+be subsequently displayed with kcachegrind. A distinct callgrind data file is
+generated for each recorded event.
+
+OPTIONS
+-------
+-i::
+--input=::
+ Input file name. (default: perf.data unless stdin is a fifo)
+
+-p::
+--prefix=::
+ Filename prefix of the generated callgrind files. (default: callgrind_)
+
+-d::
+--dsos=<dso[,dso...]>::
+ Only consider symbols in these dsos.
+
+-f::
+--force::
+ Don't complain, do it.
+
+-k::
+--vmlinux=<file>::
+ vmlinux pathname.
+
+-m::
+--modules::
+ Load module symbols. WARNING: use only with -k and LIVE kernel.
+
+-C::
+--cpu:: Only report samples for the list of CPUs provided. Multiple CPUs can
+ be provided as a comma-separated list with no space: 0,1. Ranges of
+ CPUs are specified with -: 0-2. Default is to report samples on all
+ CPUs.
+
+--symfs=<directory>::
+ Look for files with symbols relative to this directory.
+
+EXAMPLE
+-------
+ Given a perf.data file with a certain set of events, that has
+ been previously generated by perf record, e.g.:
+
+ perf record -g -e cycles -e instructions ...
+
+ The convert tool generates a callgrind file for each of the collected
+ events, i.e. cycles and instructions:
+
+ perf convert
+
+ The generated callgrind files are named callgrind_cycles and
+ callgrind_instructions. To display callgrind_instructions:
+
+ kcachegrind callgrind_instructions
+
+SEE ALSO
+--------
+linkperf:perf-record[1], linkperf:perf-report[1]
diff --git a/tools/perf/Makefile b/tools/perf/Makefile
index bb74c79..5c47df3 100644
--- a/tools/perf/Makefile
+++ b/tools/perf/Makefile
@@ -808,16 +808,19 @@ else
has_bfd := $(call try-cc,$(SOURCE_BFD),$(FLAGS_BFD),libbfd)
ifeq ($(has_bfd),y)
EXTLIBS += -lbfd
+ bfd_available = y
else
FLAGS_BFD_IBERTY=$(FLAGS_BFD) -liberty
has_bfd_iberty := $(call try-cc,$(SOURCE_BFD),$(FLAGS_BFD_IBERTY),liberty)
ifeq ($(has_bfd_iberty),y)
EXTLIBS += -lbfd -liberty
+ bfd_available = y
else
FLAGS_BFD_IBERTY_Z=$(FLAGS_BFD_IBERTY) -lz
has_bfd_iberty_z := $(call try-cc,$(SOURCE_BFD),$(FLAGS_BFD_IBERTY_Z),libz)
ifeq ($(has_bfd_iberty_z),y)
EXTLIBS += -lbfd -liberty -lz
+ bfd_available = y
else
FLAGS_CPLUS_DEMANGLE=$(ALL_CFLAGS) $(ALL_LDFLAGS) $(EXTLIBS) -liberty
has_cplus_demangle := $(call
try-cc,$(SOURCE_CPLUS_DEMANGLE),$(FLAGS_CPLUS_DEMANGLE),demangle)
@@ -834,6 +837,15 @@ else
endif
endif

+ifeq ($(bfd_available),y)
+ LIB_H += util/a2l.h
+ LIB_OBJS += $(OUTPUT)util/a2l.o
+ BUILTIN_OBJS += $(OUTPUT)builtin-convert.o
+ BASIC_CFLAGS += -DLIBBFD_SUPPORT
+else
+ msg := $(warning No bfd.h/libbfd found, install
binutils-dev[el]/zlib-static to enable the convert tool)
+endif
+
ifeq ($(NO_PERF_REGS),0)
ifeq ($(ARCH),x86)
LIB_H += arch/x86/include/perf_regs.h
diff --git a/tools/perf/builtin-convert.c b/tools/perf/builtin-convert.c
new file mode 100644
index 0000000..2582221
--- /dev/null
+++ b/tools/perf/builtin-convert.c
@@ -0,0 +1,652 @@
+/*
+ * builtin-convert.c
+ *
+ * Builtin convert command: Convert a perf.data input file
+ * to a set of callgrind profile data files.
+ */
+
+#include <linux/list.h>
+#include <linux/rbtree.h>
+#include <linux/bitmap.h>
+
+#include "util/util.h"
+#include "util/cache.h"
+#include "util/symbol.h"
+#include "util/evlist.h"
+#include "util/evsel.h"
+#include "util/annotate.h"
+#include "util/event.h"
+#include "util/parse-options.h"
+#include "util/parse-events.h"
+#include "util/thread.h"
+#include "util/session.h"
+#include "util/tool.h"
+#include "util/a2l.h"
+
+#include "builtin.h"
+#include "perf.h"
+
+struct perf_convert {
+ struct perf_tool tool;
+ char const *input_name;
+ char const *output_prefix;
+ bool force;
+ const char *cpu_list;
+ DECLARE_BITMAP(cpu_bitmap, MAX_NR_CPUS);
+};
+
+struct stats {
+ u64 hits;
+ bool has_callees;
+};
+
+struct graph_node {
+ const char *filename;
+ struct rb_node rb_node;
+ struct map *map;
+ struct symbol *sym;
+ struct rb_root callees;
+ struct stats stats[0];
+};
+
+struct callee {
+ struct rb_node rb_node;
+ struct map *map;
+ struct symbol *sym;
+ u64 hits[0];
+};
+
+static const char *last_source_name;
+static unsigned nr_events;
+static unsigned last_line;
+static u64 last_off;
+static FILE **output_files;
+static struct rb_root graph_root;
+
+static inline int64_t cmp_null(void *l, void *r)
+{
+ if (!l && !r)
+ return 0;
+ else if (!l)
+ return -1;
+ else
+ return 1;
+}
+
+static int64_t map_cmp(struct map *map_l, struct map *map_r)
+{
+ 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)
+ return cmp_null(dso_l, dso_r);
+
+ if (verbose) {
+ dso_name_l = dso_l->long_name;
+ dso_name_r = dso_r->long_name;
+ } else {
+ dso_name_l = dso_l->short_name;
+ dso_name_r = dso_r->short_name;
+ }
+
+ return strcmp(dso_name_l, dso_name_r);
+}
+
+static int64_t sym_cmp(struct symbol *sym_l, struct symbol *sym_r)
+{
+ u64 ip_l, ip_r;
+
+ if (!sym_l || !sym_r)
+ return cmp_null(sym_l, sym_r);
+
+ if (sym_l == sym_r)
+ return 0;
+
+ ip_l = sym_l->start;
+ ip_r = sym_r->start;
+
+ return (int64_t)(ip_r - ip_l);
+}
+
+static inline int64_t map_sym_cmp(struct map *map_l, struct symbol *sym_l,
+ struct map *map_r, struct symbol *sym_r)
+{
+ int64_t cmp = map_cmp(map_l, map_r);
+
+ if (!cmp)
+ return sym_cmp(sym_l, sym_r);
+ else
+ return cmp;
+}
+
+static struct graph_node *add_graph_node(struct map *map, struct symbol *sym)
+{
+ struct rb_node **rb_node = &(&graph_root)->rb_node, *parent = NULL;
+ struct graph_node *node;
+ int64_t cmp;
+
+ while (*rb_node) {
+ parent = *rb_node;
+ node = rb_entry(parent, struct graph_node, rb_node);
+ cmp = map_sym_cmp(map, sym, node->map, node->sym);
+
+ if (cmp < 0)
+ rb_node = &(*rb_node)->rb_left;
+ else if (cmp > 0)
+ rb_node = &(*rb_node)->rb_right;
+ else {
+ if (map != node->map)
+ node->map = map;
+
+ if (map)
+ map->referenced = true;
+
+ return node;
+ }
+ }
+
+ node = zalloc(sizeof(*node) + nr_events * sizeof(node->stats[0]));
+ if (node) {
+ node->map = map;
+ node->sym = sym;
+ node->filename = "";
+ node->callees = RB_ROOT;
+
+ if (map)
+ map->referenced = true;
+
+ rb_link_node(&node->rb_node, parent, rb_node);
+ rb_insert_color(&node->rb_node, &graph_root);
+ }
+
+ return node;
+}
+
+static struct graph_node *get_graph_node(struct map *map, struct symbol *sym)
+{
+ struct rb_node *rb_node = (&graph_root)->rb_node;
+ struct graph_node *node;
+ int64_t cmp;
+
+ while (rb_node) {
+ node = rb_entry(rb_node, struct graph_node, rb_node);
+ cmp = map_sym_cmp(map, sym, node->map, node->sym);
+
+ if (cmp < 0)
+ rb_node = rb_node->rb_left;
+ else if (cmp > 0)
+ rb_node = rb_node->rb_right;
+ else
+ return node;
+ }
+
+ return NULL;
+}
+
+static int graph_node__add_callee(struct graph_node *caller, struct map *map,
+ struct symbol *sym, int idx)
+{
+ struct rb_node **rb_node = &caller->callees.rb_node, *parent = NULL;
+ struct callee *callee;
+ int64_t cmp;
+
+ while (*rb_node) {
+ parent = *rb_node;
+ callee = rb_entry(parent, struct callee, rb_node);
+ cmp = map_sym_cmp(map, sym, callee->map, callee->sym);
+
+ if (cmp < 0)
+ rb_node = &(*rb_node)->rb_left;
+ else if (cmp > 0)
+ rb_node = &(*rb_node)->rb_right;
+ else{
+ callee->hits[idx]++;
+ caller->stats[idx].has_callees = true;
+
+ if (map != callee->map)
+ callee->map = map;
+
+ if (map)
+ map->referenced = true;
+
+ return 0;
+ }
+ }
+
+ callee = zalloc(sizeof(*callee) + nr_events * sizeof(callee->hits[0]));
+ if (callee) {
+ callee->map = map;
+ callee->sym = sym;
+ callee->hits[idx] = 1;
+ caller->stats[idx].has_callees = true;
+
+ if (map)
+ map->referenced = true;
+
+ rb_link_node(&callee->rb_node, parent, rb_node);
+ rb_insert_color(&callee->rb_node, &caller->callees);
+
+ return 0;
+ } else
+ return -ENOMEM;
+}
+
+static int add_callchain_to_callgraph(int idx)
+{
+ struct callchain_cursor_node *caller, *callee;
+ struct graph_node *node;
+ int err;
+
+ callchain_cursor_commit(&callchain_cursor);
+ callee = callchain_cursor_current(&callchain_cursor);
+
+ if (!callee)
+ return 0;
+
+ while (true) {
+ callchain_cursor_advance(&callchain_cursor);
+ caller = callchain_cursor_current(&callchain_cursor);
+
+ if (!caller)
+ break;
+
+ node = add_graph_node(caller->map, caller->sym);
+ if (!node)
+ return -ENOMEM;
+
+ err = graph_node__add_callee(node, callee->map, callee->sym, idx);
+ if (err)
+ return err;
+
+ callee = caller;
+ }
+
+ return 0;
+}
+
+static int accumulate_sample(struct perf_evsel *evsel, struct
perf_sample *sample,
+ struct addr_location *al, struct machine *machine)
+{
+ struct symbol *parent = NULL;
+ struct graph_node *node;
+ int err;
+
+ if (sample->callchain) {
+ err = machine__resolve_callchain(machine, evsel, al->thread,
+ sample, &parent);
+
+ if (err)
+ return err;
+ }
+
+ node = add_graph_node(al->map, al->sym);
+ if (!node)
+ return -ENOMEM;
+
+ err = add_callchain_to_callgraph(evsel->idx);
+ if (err)
+ return err;
+
+ node->stats[evsel->idx].hits++;
+
+ if (node->sym != NULL) {
+ struct annotation *notes = symbol__annotation(node->sym);
+ if (notes->src == NULL && symbol__alloc_hist(node->sym) < 0)
+ return -ENOMEM;
+
+ err = symbol__inc_addr_samples(node->sym, node->map, evsel->idx,
+ al->addr);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int process_sample_event(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct perf_evsel *evsel,
+ struct machine *machine)
+{
+ struct perf_convert *cnv = container_of(tool, struct perf_convert, tool);
+ struct addr_location al;
+ int err;
+
+ if (perf_event__preprocess_sample(event, machine, &al, sample,
+ symbol__annotate_init) < 0) {
+ pr_warning("problem processing %d event, skipping it.\n",
+ event->header.type);
+ return -1;
+ }
+
+ if (cnv->cpu_list && !test_bit(sample->cpu, cnv->cpu_bitmap))
+ return 0;
+
+ if (!al.filtered) {
+ err = accumulate_sample(evsel, sample, &al, machine);
+ if (err) {
+ pr_warning("problem incrementing symbol count, skipping event\n");
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static inline void print_header(const char *evname, int idx)
+{
+ fprintf(output_files[idx], "positions: instr line\nevents: %s\n", evname);
+}
+
+static void print_function_header(struct graph_node *node, u64 offset, int idx)
+{
+ FILE *output = output_files[idx];
+ const char *filename;
+ struct map *map = node->map;
+ struct symbol *sym = node->sym;
+ struct annotation *notes = symbol__annotation(sym);
+ u64 function_start = map__rip_2objdump(map, sym->start);
+ u64 address = function_start + offset;
+ int ret;
+
+ filename = "";
+ addr2line(function_start, &filename, &last_line);
+
+ /* Cache filename to speedup the callgraph generation */
+ node->filename = strdup(filename);
+
+ fprintf(output, "ob=%s\n", map->dso->long_name);
+ fprintf(output, "fl=%s\n", filename);
+ fprintf(output, "fn=%s\n", sym->name);
+ fprintf(output, "0 0\n");
+
+ ret = addr2line(address, &last_source_name, &last_line);
+ if (ret && strcmp(filename, last_source_name))
+ fprintf(output, "fl=%s\n", last_source_name);
+
+ fprintf(output, "%#" PRIx64 " %u %" PRIu64 "\n", address, last_line,
+ annotation__histogram(notes, idx)->addr[offset]);
+
+ last_off = offset;
+}
+
+static inline bool event_has_samples(struct annotation *notes, u64
offset, int idx)
+{
+ return annotation__histogram(notes, idx)->addr[offset];
+}
+
+static void print_function_tail(struct graph_node *node, u64 offset, int idx)
+{
+ int ret;
+ unsigned line;
+ const char *filename = NULL;
+ FILE *output = output_files[idx];
+ struct map *map = node->map;
+ struct symbol *sym = node->sym;
+ struct annotation *notes = symbol__annotation(sym);
+
+ ret = addr2line(map__rip_2objdump(map, sym->start) + offset,
+ &filename, &line);
+ if (ret && strcmp(filename, last_source_name)) {
+ fprintf(output, "fl=%s\n", filename);
+ last_source_name = filename;
+ }
+
+ if (ret)
+ fprintf(output, "+%" PRIu64 " %+d", offset - last_off,
+ (int)(line - last_line));
+ else{
+ fprintf(output, "+%" PRIu64 " 0", offset - last_off);
+ line = 0;
+ }
+
+ fprintf(output, " %" PRIu64 "\n",
+ annotation__histogram(notes, idx)->addr[offset]);
+
+ last_off = offset;
+ last_line = line;
+}
+
+static void print_function_summary(struct graph_node *node, int idx)
+{
+ FILE *output = output_files[idx];
+
+ fprintf(output, "ob=%s\n", node->map && node->map->dso ?
+ node->map->dso->long_name : "");
+
+ /* Without the empty fl declaration kcachegrind would apply the last
+ * valid fl declaration in the file*/
+ fprintf(output, "fl=\n");
+ fprintf(output, "fn=%s\n", node->sym ? node->sym->name : "");
+ fprintf(output, "0 0 %" PRIu64, node->stats[idx].hits);
+ fprintf(output, "\n");
+}
+
+static void print_function(struct graph_node *node, int idx)
+{
+ struct annotation *notes;
+ struct map *map;
+ struct symbol *sym;
+ u64 sym_len, i;
+
+ if (!node->stats[idx].hits)
+ return;
+
+ map = node->map;
+ sym = node->sym;
+
+ if (!map || !sym || addr2line_init(map->dso->long_name)) {
+ print_function_summary(node, idx);
+ return;
+ }
+
+ notes = symbol__annotation(sym);
+ sym_len = sym->end - sym->start;
+
+ for (i = 0; i < sym_len; i++) {
+ if (event_has_samples(notes, i, idx)) {
+ print_function_header(node, i, idx);
+ break;
+ }
+ }
+
+ for (++i; i < sym_len; i++) {
+ if (event_has_samples(notes, i, idx))
+ print_function_tail(node, i, idx);
+ }
+}
+
+static void print_functions(void){
+ struct rb_node *rb_node;
+ struct graph_node *node;
+ u64 i = 0;
+
+ for (rb_node = rb_first(&graph_root); rb_node; rb_node = rb_next(rb_node)) {
+ node = rb_entry(rb_node, struct graph_node, rb_node);
+
+ for (i = 0; i < nr_events; i++)
+ print_function(node, i);
+ }
+}
+
+static void print_callee(struct callee *callee, int idx)
+{
+ FILE *output = output_files[idx];
+ struct graph_node *callee_node;
+
+ if (!callee->hits[idx])
+ return;
+
+ if (callee->sym) {
+ callee_node = get_graph_node(callee->map, callee->sym);
+ fprintf(output, "cob=%s\ncfl=%s\ncfn=%s\n",
+ callee->map->dso->long_name, callee_node->filename,
+ callee->sym->name);
+ } else
+ fprintf(output, "cob=%s\ncfl=\ncfn=\n", callee->map ?
+ callee->map->dso->long_name : "");
+
+ fprintf(output, "calls=%" PRIu64 "\n0 0 %" PRIu64 "\n",
+ callee->hits[idx], callee->hits[idx]);
+
+}
+
+static void print_caller(struct graph_node *node, int idx)
+{
+ FILE *output = output_files[idx];
+ struct callee *callee;
+ struct rb_node *rb_node;
+
+ if (!node->stats[idx].has_callees)
+ return;
+
+ if (node->sym)
+ fprintf(output, "ob=%s\nfl=%s\nfn=%s\n",
+ node->map->dso->long_name,
+ node->filename, node->sym->name);
+ else
+ fprintf(output, "ob=%s\nfl=\nfn=\n",
+ node->map ? node->map->dso->long_name : "");
+
+ for (rb_node = rb_first(&node->callees); rb_node; rb_node =
rb_next(rb_node)) {
+ callee = rb_entry(rb_node, struct callee, rb_node);
+ print_callee(callee, idx);
+ }
+}
+
+static void print_calls(void)
+{
+ struct rb_node *rb_node;
+ struct graph_node *node;
+ u64 i = 0;
+
+ for (rb_node = rb_first(&graph_root); rb_node; rb_node = rb_next(rb_node)) {
+ node = rb_entry(rb_node, struct graph_node, rb_node);
+
+ for (i = 0; i < nr_events; i++)
+ print_caller(node, i);
+ }
+}
+
+static int __cmd_convert(struct perf_convert *cnv)
+{
+ int ret;
+ unsigned i = 0;
+ struct perf_session *session;
+ struct perf_evsel *pos;
+ char output_filename[100];
+
+ session = perf_session__new(cnv->input_name, O_RDONLY,
+ cnv->force, false, &cnv->tool);
+ if (session == NULL)
+ return -ENOMEM;
+
+ nr_events = session->evlist->nr_entries;
+
+ if (cnv->cpu_list) {
+ ret = perf_session__cpu_bitmap(session, cnv->cpu_list,
+ cnv->cpu_bitmap);
+ if (ret)
+ goto out_delete;
+ }
+
+ ret = perf_session__process_events(session, &cnv->tool);
+ if (ret)
+ goto out_delete;
+
+ output_files = malloc(sizeof(*output_files)*nr_events);
+ list_for_each_entry(pos, &session->evlist->entries, node) {
+ const char *evname = perf_evsel__name(pos);
+
+ snprintf(output_filename, sizeof(output_filename), "%s%s",
+ cnv->output_prefix, evname);
+ output_files[i] = fopen(output_filename, "w");
+
+ if (!output_files[i]) {
+ fprintf(stderr, "Cannot open %s for output\n",
+ output_filename);
+ return -1;
+ }
+
+ print_header(evname, i++);
+ }
+
+ print_functions();
+ print_calls();
+
+ for (i = 0; i < nr_events; i++)
+ fclose(output_files[i]);
+
+out_delete:
+ /*
+ * Speed up the exit process, for large files this can
+ * take quite a while.
+ *
+ * XXX Enable this when using valgrind or if we ever
+ * librarize this command.
+ *
+ * Also experiment with obstacks to see how much speed
+ * up we'll get here.
+ *
+ * perf_session__delete(session);
+ */
+ return ret;
+}
+
+static const char * const convert_usage[] = {
+ "perf convert [<options>]",
+ NULL
+};
+
+int cmd_convert(int argc, const char **argv, const char *prefix __maybe_unused)
+{
+ struct perf_convert convert = {
+ .tool = {
+ .sample = process_sample_event,
+ .mmap = perf_event__process_mmap,
+ .comm = perf_event__process_comm,
+ .exit = perf_event__process_exit,
+ .fork = perf_event__process_fork,
+ .ordered_samples = true,
+ .ordering_requires_timestamps = true,
+ },
+ .output_prefix = "callgrind_"
+ };
+ const struct option options[] = {
+ OPT_STRING('i', "input", &convert.input_name, "file",
+ "input file name"),
+ OPT_STRING('p', "prefix", &convert.output_prefix, "prefix", "filename "
+ "prefix of the generated callgrind files, default is 'callgrind_'"),
+ OPT_STRING('d', "dsos", &symbol_conf.dso_list_str, "dso[,dso...]",
+ "only consider symbols in these dsos"),
+ OPT_BOOLEAN('f', "force", &convert.force, "don't complain, do it"),
+ OPT_STRING('k', "vmlinux", &symbol_conf.vmlinux_name,
+ "file", "vmlinux pathname"),
+ OPT_BOOLEAN('m', "modules", &symbol_conf.use_modules,
+ "load module symbols - WARNING: use only with -k and LIVE kernel"),
+ OPT_STRING('C', "cpu", &convert.cpu_list, "cpu", "list of cpus to profile"),
+ OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory",
+ "Look for files with symbols relative to this directory"),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, options, convert_usage, 0);
+
+ symbol_conf.priv_size = sizeof(struct annotation);
+ symbol_conf.try_vmlinux_path = true;
+ symbol_conf.use_callchain = true;
+
+ if (callchain_register_param(&callchain_param) < 0) {
+ fprintf(stderr, "Can't register callchain params\n");
+ return -1;
+ }
+
+ if (symbol__init() < 0)
+ return -1;
+
+ graph_root = RB_ROOT;
+
+ return __cmd_convert(&convert);
+}
diff --git a/tools/perf/builtin.h b/tools/perf/builtin.h
index 08143bd..e4f7d5a 100644
--- a/tools/perf/builtin.h
+++ b/tools/perf/builtin.h
@@ -36,6 +36,7 @@ extern int cmd_kvm(int argc, const char **argv,
const char *prefix);
extern int cmd_test(int argc, const char **argv, const char *prefix);
extern int cmd_trace(int argc, const char **argv, const char *prefix);
extern int cmd_inject(int argc, const char **argv, const char *prefix);
+extern int cmd_convert(int argc, const char **argv, const char *prefix);

extern int find_scripts(char **scripts_array, char **scripts_path_array);
#endif
diff --git a/tools/perf/command-list.txt b/tools/perf/command-list.txt
index 3e86bbd..74e792c 100644
--- a/tools/perf/command-list.txt
+++ b/tools/perf/command-list.txt
@@ -7,6 +7,7 @@ perf-archive mainporcelain common
perf-bench mainporcelain common
perf-buildid-cache mainporcelain common
perf-buildid-list mainporcelain common
+perf-convert mainporcelain common
perf-diff mainporcelain common
perf-evlist mainporcelain common
perf-inject mainporcelain common
diff --git a/tools/perf/perf.c b/tools/perf/perf.c
index 095b882..01104f1 100644
--- a/tools/perf/perf.c
+++ b/tools/perf/perf.c
@@ -60,6 +60,9 @@ static struct cmd_struct commands[] = {
{ "trace", cmd_trace, 0 },
#endif
{ "inject", cmd_inject, 0 },
+#ifdef LIBBFD_SUPPORT
+ { "convert", cmd_convert, 0 },
+#endif
};

struct pager_config {
diff --git a/tools/perf/util/a2l.c b/tools/perf/util/a2l.c
new file mode 100644
index 0000000..3df6b7e
--- /dev/null
+++ b/tools/perf/util/a2l.c
@@ -0,0 +1,147 @@
+/* based on addr2line */
+
+#define PACKAGE "perf"
+
+#include <linux/kernel.h>
+
+#include <bfd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "a2l.h"
+
+static const char *filename;
+static const char *functionname;
+static const char *last_opened_file;
+static unsigned int line;
+static asymbol **syms;
+static bfd_vma pc;
+static bfd_boolean found;
+static bfd *abfd;
+
+static void bfd_nonfatal(const char *string)
+{
+ const char *errmsg;
+
+ errmsg = bfd_errmsg(bfd_get_error());
+ fflush(stdout);
+ if (string)
+ pr_warning("%s: %s\n", string, errmsg);
+ else
+ pr_warning("%s\n", errmsg);
+}
+
+static int bfd_fatal(const char *string)
+{
+ bfd_nonfatal(string);
+ return -1;
+}
+
+static int slurp_symtab(void)
+{
+ long storage;
+ long symcount;
+ bfd_boolean dynamic = FALSE;
+
+ if ((bfd_get_file_flags(abfd) & HAS_SYMS) == 0)
+ return bfd_fatal(bfd_get_filename(abfd));
+
+ storage = bfd_get_symtab_upper_bound(abfd);
+ if (storage == 0) {
+ storage = bfd_get_dynamic_symtab_upper_bound(abfd);
+ dynamic = TRUE;
+ }
+ if (storage < 0)
+ return bfd_fatal(bfd_get_filename(abfd));
+
+ syms = (asymbol **) malloc(storage);
+ if (dynamic)
+ symcount = bfd_canonicalize_dynamic_symtab(abfd, syms);
+ else
+ symcount = bfd_canonicalize_symtab(abfd, syms);
+
+ if (symcount < 0)
+ return bfd_fatal(bfd_get_filename(abfd));
+
+ return 0;
+}
+
+static void find_address_in_section(bfd *mybfd, asection *section,
+ void *data ATTRIBUTE_UNUSED)
+{
+ bfd_vma vma;
+ bfd_size_type size;
+ (void)mybfd;
+
+ if (found)
+ return;
+
+ if ((bfd_get_section_flags(abfd, section) & SEC_ALLOC) == 0)
+ return;
+
+ vma = bfd_get_section_vma(abfd, section);
+ if (pc < vma)
+ return;
+
+ size = bfd_get_section_size(section);
+ if (pc >= vma + size)
+ return;
+
+ found = bfd_find_nearest_line(abfd, section, syms, pc - vma,
+ &filename, &functionname, &line);
+}
+
+int addr2line_init(const char *file_name)
+{
+ if (last_opened_file && !strcmp(last_opened_file, file_name))
+ return 0;
+ else
+ addr2line_cleanup();
+
+ abfd = bfd_openr(file_name, NULL);
+ if (abfd == NULL)
+ return -1;
+
+ if (!bfd_check_format(abfd, bfd_object))
+ return bfd_fatal(bfd_get_filename(abfd));
+
+ last_opened_file = file_name;
+ return slurp_symtab();
+
+}
+
+void addr2line_cleanup(void)
+{
+ if (syms != NULL) {
+ free(syms);
+ syms = NULL;
+ }
+
+ if (abfd)
+ bfd_close(abfd);
+
+ line = found = 0;
+ last_opened_file = NULL;
+ abfd = 0;
+}
+
+int addr2line_inline(const char **file, unsigned *line_nr)
+{
+ return bfd_find_inliner_info(abfd, file, &functionname, line_nr);
+}
+
+int addr2line(unsigned long addr, const char **file, unsigned *line_nr)
+{
+ found = 0;
+ pc = addr;
+ bfd_map_over_sections(abfd, find_address_in_section, NULL);
+
+ if (found) {
+ *file = filename ? filename : "";
+ *line_nr = line;
+ return found;
+ }
+
+ return 0;
+}
diff --git a/tools/perf/util/a2l.h b/tools/perf/util/a2l.h
new file mode 100644
index 0000000..b248429
--- /dev/null
+++ b/tools/perf/util/a2l.h
@@ -0,0 +1,9 @@
+#ifndef __A2L_H_
+#define __A2L_H_
+
+int addr2line_init(const char *file_name);
+int addr2line(unsigned long addr, const char **file, unsigned *line_nr);
+int addr2line_inline(const char **file, unsigned *line_nr);
+void addr2line_cleanup(void);
+
+#endif


2013-03-27 07:23:31

by Namhyung Kim

[permalink] [raw]
Subject: Re: [PATCH v2] perf: add callgrind conversion tool

Hi Roberto,

On Tue, 26 Mar 2013 12:14:49 -0700, Roberto Vitillo wrote:
> The proposed patch adds the convert tool to perf which allows to convert a
> perf.data file to a set of callgrind data files which can subsequently be
> displayed with kcachegrind.
>
> Note that the code may trigger the following bug in libbfd:
> http://sourceware.org/bugzilla/show_bug.cgi?id=15106

Does it now support call graph too? Great if so!

Anyway, I suggest that splitting patch to two - one for addr2line and
another for actual 'convert' tool. I think we *can* use the tool
without libbfd (but with external addr2line binary or even with no line
number information).

And it seems there's some linewraps.

Thanks,
Namhyung

2013-03-27 14:27:01

by Peter Zijlstra

[permalink] [raw]
Subject: Re: [PATCH v2] perf: add callgrind conversion tool


This sort of reminds me of another little proglet I have lying about
that might need a home..


---
/*
* Library to hook into code compiled with -finstrument-functions it will
* record function arcs (call_fn, this_fn) as well as the sum of whatever event
* is being measured over that function.
*
* Copyright (C) 2011 Red Hat, Inc., Peter Zijlstra <[email protected]>
*
* This file is copyrighted under the GPLv2 License (and not any later version).
*
* SuperFastHash under LGPLv2.1 (http://www.azillionmonkeys.com/qed/hash.html)
*
* Compilation example:
* gcc -shared -fPIC profviz.c -o profviz.so -lpthread -ldl -lelf
*
* Usage example:
* LD_PRELOAD=./profviz.so your_program
*/

#define _GNU_SOURCE

#include "util/util.h"
#include "perf.h"
#include "util/parse-events.h"

#include <sys/time.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <pthread.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <libelf.h>
#include <gelf.h>
#include <link.h>
#include <math.h>

#define barrier() asm volatile("" ::: "memory")

static u64 rdpmc(unsigned int counter)
{
unsigned int low, high;

asm volatile("rdpmc" : "=a" (low), "=d" (high) : "c" (counter));

return low | ((u64)high) << 32;
}

static u64 rdtsc(void)
{
unsigned int low, high;

asm volatile("rdtsc" : "=a" (low), "=d" (high));

return low | ((u64)high) << 32;
}

static u64 mmap_read_self(void *addr)
{
struct perf_event_mmap_page *pc = addr;
u32 seq, idx, time_mult = 0, time_shift = 0, width = 0;
u64 count, cyc = 0, time_offset = 0, enabled, running, delta;
s64 pmc = 0;

do {
seq = pc->lock;
barrier();

enabled = pc->time_enabled;
running = pc->time_running;

if (pc->cap_usr_time && enabled != running) {
cyc = rdtsc();
time_mult = pc->time_mult;
time_shift = pc->time_shift;
time_offset = pc->time_offset;
}

idx = pc->index;
count = pc->offset;
if (pc->cap_usr_rdpmc && idx) {
width = pc->pmc_width;
pmc = rdpmc(idx - 1);
}

barrier();
} while (pc->lock != seq);

if (idx) {
pmc <<= 64 - width;
pmc >>= 64 - width; /* shift right signed */
count += pmc;
}

if (enabled != running) {
u64 quot, rem;

quot = (cyc >> time_shift);
rem = cyc & ((1 << time_shift) - 1);
delta = time_offset + quot * time_mult +
((rem * time_mult) >> time_shift);

enabled += delta;
if (idx)
running += delta;

quot = count / running;
rem = count % running;
count = quot * enabled + (rem * enabled) / running;
}

return count;
}

static int (*pthread_create_orig)(pthread_t *__restrict,
__const pthread_attr_t *__restrict,
void *(*)(void *),
void *__restrict) = NULL;

static struct perf_event_attr perf_attr = {
.type = PERF_TYPE_HARDWARE,
.config = PERF_COUNT_HW_CPU_CYCLES,
.exclude_kernel = 1,
};

struct prof_arc {
void *call_fn;
void *this_fn;
uint64_t count;
};

struct prof_fn {
void *this_fn;
uint64_t count;
};

#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \
+(uint32_t)(((const uint8_t *)(d))[0]) )

static uint32_t SuperFastHash (const char * data, int len, uint32_t hash) {
uint32_t tmp;
int rem;

if (len <= 0 || data == NULL) return 0;

rem = len & 3;
len >>= 2;

/* Main loop */
for (;len > 0; len--) {
hash += get16bits (data);
tmp = (get16bits (data+2) << 11) ^ hash;
hash = (hash << 16) ^ tmp;
data += 2*sizeof (uint16_t);
hash += hash >> 11;
}

/* Handle end cases */
switch (rem) {
case 3: hash += get16bits (data);
hash ^= hash << 16;
hash ^= data[sizeof (uint16_t)] << 18;
hash += hash >> 11;
break;
case 2: hash += get16bits (data);
hash ^= hash << 11;
hash += hash >> 17;
break;
case 1: hash += *data;
hash ^= hash << 10;
hash += hash >> 1;
}

/* Force "avalanching" of final 127 bits */
hash ^= hash << 3;
hash += hash >> 5;
hash ^= hash << 4;
hash += hash >> 17;
hash ^= hash << 25;
hash += hash >> 6;

return hash;
}

void die(const char *err, ...)
{
va_list params;

va_start(params, err);
vfprintf(stderr, err, params);
va_end(params);

exit(-1);
}


#define HASH_TABLE_SIZE 16384
#define AVG_STACK_DEPTH 8

#define ARC_HASH_SIZE (HASH_TABLE_SIZE * AVG_STACK_DEPTH)

static struct prof_arc prof_arc_hash[ARC_HASH_SIZE];
static pthread_mutex_t prof_arc_lock;

#define FN_HASH_SIZE (HASH_TABLE_SIZE)

static struct prof_fn prof_fn_hash[FN_HASH_SIZE];
static pthread_mutex_t prof_fn_lock;

#define HASH_INIT 0x9e370001UL
#define HASH_CHAIN 16

static inline void *
prof_hash_find(void *hash_base, const size_t hash_size,
const void *key, const size_t key_size,
const size_t entry_size, pthread_mutex_t *lock)

{
uint32_t hash = HASH_INIT;
void *entry = NULL;
int i, j;

for (i = 0; i < HASH_CHAIN; i++) {
hash = SuperFastHash(key, key_size, hash);
entry = hash_base + entry_size * (hash % FN_HASH_SIZE);
if (!memcmp(entry, key, key_size))
return entry;

for (j = 0; j < key_size; j++) {
if (*((char *)entry + j))
goto next_1;
}

goto found_empty;
next_1:
continue;
}
die("fn_hash too full");

found_empty:
hash = HASH_INIT;
pthread_mutex_lock(lock);
for (i = 0; i < HASH_CHAIN; i++) {
hash = SuperFastHash(key, key_size, hash);
entry = hash_base + entry_size * (hash % FN_HASH_SIZE);

if (!memcmp(entry, key, key_size))
goto unlock;

for (j = 0; j < key_size; j++) {
if (*((char *)entry + j))
goto next_2;
}

memcpy(entry, key, key_size);
goto unlock;
next_2:
continue;
}
die("fn_hash too full (locked)");
unlock:
pthread_mutex_unlock(lock);

return entry;
}

static struct prof_fn *prof_fn_find(void *this_fn)
{
return prof_hash_find(prof_fn_hash, FN_HASH_SIZE,
&this_fn, sizeof(this_fn), sizeof(struct prof_fn),
&prof_fn_lock);
}

static struct prof_arc *prof_arc_find(void *this_fn, void *call_fn)
{
struct prof_arc arc = { .this_fn = this_fn, .call_fn = call_fn, };

return prof_hash_find(prof_arc_hash, ARC_HASH_SIZE,
&arc, 2*sizeof(void *), sizeof(struct prof_arc),
&prof_arc_lock);
}

static unsigned long page_size;

struct prof_stack {
struct prof_arc *arc;
struct prof_fn *fn;
uint64_t stamp;
};

static __thread struct prof_stack prof_stack[128];
static __thread int prof_stack_idx = 0;

static __thread void *perf_event;

static void thread_init(void)
{
int fd;

fd = sys_perf_event_open(&perf_attr, 0, -1, -1, 0);
if (fd < 0)
die("failed to create perf_event");

perf_event = mmap(NULL, page_size, PROT_READ, MAP_SHARED, fd, 0);
if (perf_event == (void *)(-1))
die("failed to mmap perf_event");

close(fd);

prof_stack_idx = 0;
}

static u64 first_count;

void prof_init(void) __attribute__((constructor));
void prof_init(void)
{
char *event_str;

page_size = sysconf(_SC_PAGESIZE);

pthread_create_orig = dlsym(RTLD_NEXT, "pthread_create");
if (!pthread_create_orig) {
char *error = dlerror();
if (!error)
error = "pthread_create is NULL";
die("%s\n", error);
}

memset(prof_arc_hash, 0, sizeof(prof_arc_hash));
pthread_mutex_init(&prof_arc_lock, NULL);

memset(prof_fn_hash, 0, sizeof(prof_fn_hash));
pthread_mutex_init(&prof_fn_lock, NULL);

event_str = getenv("PROF_EVENT");
if (event_str)
/* perf_attr = parse_attr_crap(event_str); */
;

thread_init(); /* main thread */

first_count = mmap_read_self(perf_event);
}

struct prof_symbol {
void *addr;
unsigned long size;
const char *name;
};

static struct prof_symbol *prof_symbols;
static unsigned long prof_nr_symbols;

static void prof_gelf(const char *name, void (*func)(Elf *elf, Elf_Scn *scn, GElf_Shdr *shdr, void *data), void *data)
{
Elf *elf;
Elf_Scn *scn = NULL;
GElf_Shdr shdr;
int fd;

elf_version(EV_CURRENT);

fd = open(name, O_RDONLY);
elf = elf_begin(fd, ELF_C_READ, NULL);

while ((scn = elf_nextscn(elf, scn)) != NULL) {
gelf_getshdr(scn, &shdr);
if (shdr.sh_type == SHT_SYMTAB) {
func(elf, scn, &shdr, data);
}
}

elf_end(elf);
close(fd);
}

static void prof_gelf_nr_symbols(Elf *elf, Elf_Scn *scn, GElf_Shdr *shdr, void *data)
{
unsigned long *nr_symbols = data;
unsigned long count;

count = shdr->sh_size / shdr->sh_entsize;
*nr_symbols += count;
}

static int prof_count_symbols(struct dl_phdr_info *info, size_t size, void *data)
{
prof_gelf(info->dlpi_name, prof_gelf_nr_symbols, data);
return 0;
}

static void prof_gelf_load_symbols(Elf *elf, Elf_Scn *scn, GElf_Shdr *shdr, void *_info)
{
struct dl_phdr_info *info = _info;
unsigned long count = shdr->sh_size / shdr->sh_entsize;
unsigned long i;
Elf_Data *data;

data = elf_getdata(scn, NULL);

for (i = 0; i < count; i++) {
GElf_Sym sym;
char *name;
struct prof_symbol *symbol = &prof_symbols[prof_nr_symbols];

gelf_getsym(data, i, &sym);

if (GELF_ST_TYPE(sym.st_info) != STT_FUNC)
continue;

if (!sym.st_size)
continue;

name = elf_strptr(elf, shdr->sh_link, sym.st_name);
if (!name)
continue;

symbol->name = strdup(name);
symbol->addr = (void *)(sym.st_value + info->dlpi_addr);
symbol->size = sym.st_size;

prof_nr_symbols++;
}
}

static int prof_load_symbols(struct dl_phdr_info *info, size_t size, void *data)
{
prof_gelf(info->dlpi_name, prof_gelf_load_symbols, info);
return 0;
}

static int prof_cmp_symbol(const void *_a, const void *_b)
{
const struct prof_symbol *a = _a, *b = _b;

if (a->addr < b->addr)
return -1;

if (a->addr > b->addr)
return 1;

return 0;
}

static void load_symbols(void)
{
unsigned long nr_symbols = 0;
struct dl_phdr_info dl_info = {
.dlpi_name = "/proc/self/exe",
.dlpi_addr = 0,
};

prof_count_symbols(&dl_info, sizeof(dl_info), &nr_symbols);
dl_iterate_phdr(prof_count_symbols, &nr_symbols);

prof_symbols = calloc(nr_symbols, sizeof(struct prof_symbol));

prof_load_symbols(&dl_info, sizeof(dl_info), NULL);
dl_iterate_phdr(prof_load_symbols, NULL);

qsort(prof_symbols, prof_nr_symbols,
sizeof(struct prof_symbol), prof_cmp_symbol);
}

static struct prof_symbol *find_symbol(void *addr)
{
struct prof_symbol *sym;
unsigned long l, u, i;

l = 0;
u = prof_nr_symbols;

while (l < u) {
i = (l + u) / 2;
sym = &prof_symbols[i];

if (addr >= sym->addr && addr < sym->addr + sym->size)
return sym;

if (addr < sym->addr)
u = i;
else
l = i + 1;
}

return NULL;
}

void prof_exit(void) __attribute__((destructor));
void prof_exit(void)
{
FILE *file;
int i;
int64_t max_fn_count = 0;

load_symbols();

file = fopen("prof.dot", "w");
if (!file)
die("failed to create output file");

fprintf(file, "#\n# first count: %lu\n#\n", first_count);

/*
* Maybe replace "profile" with the argv
*/
fprintf(file, "digraph profile {\n");

for (i = 0; i < ARC_HASH_SIZE; i++) {
struct prof_arc *arc = &prof_arc_hash[i];
struct prof_symbol *c_sym, *t_sym;
struct prof_fn *c;
double p;

if (!(arc->call_fn && arc->this_fn))
continue;

c_sym = find_symbol(arc->call_fn);
t_sym = find_symbol(arc->this_fn);

if (!c_sym || !t_sym)
die("symbols missing");

c = prof_fn_find(c_sym->addr);
if (!c)
die("fn_hash|symtab borken");

p = (double)arc->count / (double)c->count;
fprintf(file,
" \"%s\" -> \"%s\" [label=\"%f\", color=\"%.3f %.3f %.3f\"]\n",
c_sym->name, t_sym->name, 100.0 * p,
0.33, 1.0, p);
}

for (i = 0; i < ARC_HASH_SIZE; i++) {
struct prof_arc *arc = &prof_arc_hash[i];
struct prof_symbol *c_sym;
struct prof_fn *c;
double p;

if (!(arc->call_fn && arc->this_fn))
continue;

c_sym = find_symbol(arc->call_fn);
c = prof_fn_find(c_sym->addr);

c->count -= arc->count;
}

for (i = 0; i < FN_HASH_SIZE; i++) {
struct prof_fn *fn = &prof_fn_hash[i];
struct prof_symbol *s;

if (!fn->this_fn)
continue;

if ((int64_t)fn->count > max_fn_count)
max_fn_count = fn->count;

s = find_symbol(fn->this_fn);
// fprintf(file, "# %s %ld\n", s->name, fn->count);
}

for (i = 0; i < FN_HASH_SIZE; i++) {
struct prof_fn *fn = &prof_fn_hash[i];
struct prof_symbol *s;
double p;

if (!fn->this_fn)
continue;

s = find_symbol(fn->this_fn);
p = (double)fn->count / (double)max_fn_count;
fprintf(file, " \"%s\" [color=\"%.3f %.3f %.3f\"]\n",
s->name, 0.0, 1.0, p);
}

fprintf(file, "}\n");
fflush(file);
fclose(file);
}

struct tramp_data {

void *(*func)(void *);
void *arg;

pthread_mutex_t lock;
pthread_cond_t wait;
};

static void *tramp_func(void *data)
{
struct tramp_data *tramp_data = data;
void *(*func)(void *) = tramp_data->func;
void *arg = tramp_data->arg;
void *ret;

thread_init();

pthread_mutex_lock(&tramp_data->lock);
pthread_cond_signal(&tramp_data->wait);
pthread_mutex_unlock(&tramp_data->lock);

ret = func(arg);

munmap(perf_event, page_size);

return ret;
}

/* hijack pthread_create() */
int pthread_create(pthread_t *__restrict thread,
__const pthread_attr_t *__restrict attr,
void *(*func)(void *),
void *__restrict arg)
{
struct tramp_data tramp_data = {
.func = func,
.arg = arg,
};
int ret;

pthread_cond_init(&tramp_data.wait, NULL);
pthread_mutex_init(&tramp_data.lock, NULL);

pthread_mutex_lock(&tramp_data.lock);

ret = pthread_create_orig(thread, attr, &tramp_func, &tramp_data);
if (!ret)
pthread_cond_wait(&tramp_data.wait, &tramp_data.lock);

pthread_mutex_unlock(&tramp_data.lock);

pthread_mutex_destroy(&tramp_data.lock);
pthread_cond_destroy(&tramp_data.wait);

return ret;
}

void __cyg_profile_func_enter(void *this_fn, void *call_fn)
{
struct prof_stack *st = &prof_stack[prof_stack_idx++];

st->arc = prof_arc_find(this_fn, call_fn);
st->fn = prof_fn_find(this_fn);
st->stamp = mmap_read_self(perf_event);
}

void __cyg_profile_func_exit(void *this_fn, void *call_fn)
{
struct prof_stack *st = &prof_stack[--prof_stack_idx];
uint64_t now, delta;

now = mmap_read_self(perf_event);
delta = now - st->stamp;
(void)__sync_fetch_and_add(&st->fn->count, delta);
(void)__sync_fetch_and_add(&st->arc->count, delta);
}
/*
* Library to hook into code compiled with -finstrument-functions it will
* record function arcs (call_fn, this_fn) as well as the sum of whatever event
* is being measured over that function.
*
* Copyright (C) 2011 Red Hat, Inc., Peter Zijlstra <[email protected]>
*
* This file is copyrighted under the GPLv2 License (and not any later version).
*
* SuperFastHash under LGPLv2.1 (http://www.azillionmonkeys.com/qed/hash.html)
*
* Compilation example:
* gcc -shared -fPIC profviz.c -o profviz.so -lpthread -ldl -lelf
*
* Usage example:
* LD_PRELOAD=./profviz.so your_program
*/

#define _GNU_SOURCE

#include "util/util.h"
#include "perf.h"
#include "util/parse-events.h"

#include <sys/time.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <pthread.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <libelf.h>
#include <gelf.h>
#include <link.h>
#include <math.h>

#define barrier() asm volatile("" ::: "memory")

static u64 rdpmc(unsigned int counter)
{
unsigned int low, high;

asm volatile("rdpmc" : "=a" (low), "=d" (high) : "c" (counter));

return low | ((u64)high) << 32;
}

static u64 rdtsc(void)
{
unsigned int low, high;

asm volatile("rdtsc" : "=a" (low), "=d" (high));

return low | ((u64)high) << 32;
}

static u64 mmap_read_self(void *addr)
{
struct perf_event_mmap_page *pc = addr;
u32 seq, idx, time_mult = 0, time_shift = 0, width = 0;
u64 count, cyc = 0, time_offset = 0, enabled, running, delta;
s64 pmc = 0;

do {
seq = pc->lock;
barrier();

enabled = pc->time_enabled;
running = pc->time_running;

if (pc->cap_usr_time && enabled != running) {
cyc = rdtsc();
time_mult = pc->time_mult;
time_shift = pc->time_shift;
time_offset = pc->time_offset;
}

idx = pc->index;
count = pc->offset;
if (pc->cap_usr_rdpmc && idx) {
width = pc->pmc_width;
pmc = rdpmc(idx - 1);
}

barrier();
} while (pc->lock != seq);

if (idx) {
pmc <<= 64 - width;
pmc >>= 64 - width; /* shift right signed */
count += pmc;
}

if (enabled != running) {
u64 quot, rem;

quot = (cyc >> time_shift);
rem = cyc & ((1 << time_shift) - 1);
delta = time_offset + quot * time_mult +
((rem * time_mult) >> time_shift);

enabled += delta;
if (idx)
running += delta;

quot = count / running;
rem = count % running;
count = quot * enabled + (rem * enabled) / running;
}

return count;
}

static int (*pthread_create_orig)(pthread_t *__restrict,
__const pthread_attr_t *__restrict,
void *(*)(void *),
void *__restrict) = NULL;

static struct perf_event_attr perf_attr = {
.type = PERF_TYPE_HARDWARE,
.config = PERF_COUNT_HW_CPU_CYCLES,
.exclude_kernel = 1,
};

struct prof_arc {
void *call_fn;
void *this_fn;
uint64_t count;
};

struct prof_fn {
void *this_fn;
uint64_t count;
};

#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \
+(uint32_t)(((const uint8_t *)(d))[0]) )

static uint32_t SuperFastHash (const char * data, int len, uint32_t hash) {
uint32_t tmp;
int rem;

if (len <= 0 || data == NULL) return 0;

rem = len & 3;
len >>= 2;

/* Main loop */
for (;len > 0; len--) {
hash += get16bits (data);
tmp = (get16bits (data+2) << 11) ^ hash;
hash = (hash << 16) ^ tmp;
data += 2*sizeof (uint16_t);
hash += hash >> 11;
}

/* Handle end cases */
switch (rem) {
case 3: hash += get16bits (data);
hash ^= hash << 16;
hash ^= data[sizeof (uint16_t)] << 18;
hash += hash >> 11;
break;
case 2: hash += get16bits (data);
hash ^= hash << 11;
hash += hash >> 17;
break;
case 1: hash += *data;
hash ^= hash << 10;
hash += hash >> 1;
}

/* Force "avalanching" of final 127 bits */
hash ^= hash << 3;
hash += hash >> 5;
hash ^= hash << 4;
hash += hash >> 17;
hash ^= hash << 25;
hash += hash >> 6;

return hash;
}

void die(const char *err, ...)
{
va_list params;

va_start(params, err);
vfprintf(stderr, err, params);
va_end(params);

exit(-1);
}


#define HASH_TABLE_SIZE 16384
#define AVG_STACK_DEPTH 8

#define ARC_HASH_SIZE (HASH_TABLE_SIZE * AVG_STACK_DEPTH)

static struct prof_arc prof_arc_hash[ARC_HASH_SIZE];
static pthread_mutex_t prof_arc_lock;

#define FN_HASH_SIZE (HASH_TABLE_SIZE)

static struct prof_fn prof_fn_hash[FN_HASH_SIZE];
static pthread_mutex_t prof_fn_lock;

#define HASH_INIT 0x9e370001UL
#define HASH_CHAIN 16

static inline void *
prof_hash_find(void *hash_base, const size_t hash_size,
const void *key, const size_t key_size,
const size_t entry_size, pthread_mutex_t *lock)

{
uint32_t hash = HASH_INIT;
void *entry = NULL;
int i, j;

for (i = 0; i < HASH_CHAIN; i++) {
hash = SuperFastHash(key, key_size, hash);
entry = hash_base + entry_size * (hash % FN_HASH_SIZE);
if (!memcmp(entry, key, key_size))
return entry;

for (j = 0; j < key_size; j++) {
if (*((char *)entry + j))
goto next_1;
}

goto found_empty;
next_1:
continue;
}
die("fn_hash too full");

found_empty:
hash = HASH_INIT;
pthread_mutex_lock(lock);
for (i = 0; i < HASH_CHAIN; i++) {
hash = SuperFastHash(key, key_size, hash);
entry = hash_base + entry_size * (hash % FN_HASH_SIZE);

if (!memcmp(entry, key, key_size))
goto unlock;

for (j = 0; j < key_size; j++) {
if (*((char *)entry + j))
goto next_2;
}

memcpy(entry, key, key_size);
goto unlock;
next_2:
continue;
}
die("fn_hash too full (locked)");
unlock:
pthread_mutex_unlock(lock);

return entry;
}

static struct prof_fn *prof_fn_find(void *this_fn)
{
return prof_hash_find(prof_fn_hash, FN_HASH_SIZE,
&this_fn, sizeof(this_fn), sizeof(struct prof_fn),
&prof_fn_lock);
}

static struct prof_arc *prof_arc_find(void *this_fn, void *call_fn)
{
struct prof_arc arc = { .this_fn = this_fn, .call_fn = call_fn, };

return prof_hash_find(prof_arc_hash, ARC_HASH_SIZE,
&arc, 2*sizeof(void *), sizeof(struct prof_arc),
&prof_arc_lock);
}

static unsigned long page_size;

struct prof_stack {
struct prof_arc *arc;
struct prof_fn *fn;
uint64_t stamp;
};

static __thread struct prof_stack prof_stack[128];
static __thread int prof_stack_idx = 0;

static __thread void *perf_event;

static void thread_init(void)
{
int fd;

fd = sys_perf_event_open(&perf_attr, 0, -1, -1, 0);
if (fd < 0)
die("failed to create perf_event");

perf_event = mmap(NULL, page_size, PROT_READ, MAP_SHARED, fd, 0);
if (perf_event == (void *)(-1))
die("failed to mmap perf_event");

close(fd);

prof_stack_idx = 0;
}

static u64 first_count;

void prof_init(void) __attribute__((constructor));
void prof_init(void)
{
char *event_str;

page_size = sysconf(_SC_PAGESIZE);

pthread_create_orig = dlsym(RTLD_NEXT, "pthread_create");
if (!pthread_create_orig) {
char *error = dlerror();
if (!error)
error = "pthread_create is NULL";
die("%s\n", error);
}

memset(prof_arc_hash, 0, sizeof(prof_arc_hash));
pthread_mutex_init(&prof_arc_lock, NULL);

memset(prof_fn_hash, 0, sizeof(prof_fn_hash));
pthread_mutex_init(&prof_fn_lock, NULL);

event_str = getenv("PROF_EVENT");
if (event_str)
/* perf_attr = parse_attr_crap(event_str); */
;

thread_init(); /* main thread */

first_count = mmap_read_self(perf_event);
}

struct prof_symbol {
void *addr;
unsigned long size;
const char *name;
};

static struct prof_symbol *prof_symbols;
static unsigned long prof_nr_symbols;

static void prof_gelf(const char *name, void (*func)(Elf *elf, Elf_Scn *scn, GElf_Shdr *shdr, void *data), void *data)
{
Elf *elf;
Elf_Scn *scn = NULL;
GElf_Shdr shdr;
int fd;

elf_version(EV_CURRENT);

fd = open(name, O_RDONLY);
elf = elf_begin(fd, ELF_C_READ, NULL);

while ((scn = elf_nextscn(elf, scn)) != NULL) {
gelf_getshdr(scn, &shdr);
if (shdr.sh_type == SHT_SYMTAB) {
func(elf, scn, &shdr, data);
}
}

elf_end(elf);
close(fd);
}

static void prof_gelf_nr_symbols(Elf *elf, Elf_Scn *scn, GElf_Shdr *shdr, void *data)
{
unsigned long *nr_symbols = data;
unsigned long count;

count = shdr->sh_size / shdr->sh_entsize;
*nr_symbols += count;
}

static int prof_count_symbols(struct dl_phdr_info *info, size_t size, void *data)
{
prof_gelf(info->dlpi_name, prof_gelf_nr_symbols, data);
return 0;
}

static void prof_gelf_load_symbols(Elf *elf, Elf_Scn *scn, GElf_Shdr *shdr, void *_info)
{
struct dl_phdr_info *info = _info;
unsigned long count = shdr->sh_size / shdr->sh_entsize;
unsigned long i;
Elf_Data *data;

data = elf_getdata(scn, NULL);

for (i = 0; i < count; i++) {
GElf_Sym sym;
char *name;
struct prof_symbol *symbol = &prof_symbols[prof_nr_symbols];

gelf_getsym(data, i, &sym);

if (GELF_ST_TYPE(sym.st_info) != STT_FUNC)
continue;

if (!sym.st_size)
continue;

name = elf_strptr(elf, shdr->sh_link, sym.st_name);
if (!name)
continue;

symbol->name = strdup(name);
symbol->addr = (void *)(sym.st_value + info->dlpi_addr);
symbol->size = sym.st_size;

prof_nr_symbols++;
}
}

static int prof_load_symbols(struct dl_phdr_info *info, size_t size, void *data)
{
prof_gelf(info->dlpi_name, prof_gelf_load_symbols, info);
return 0;
}

static int prof_cmp_symbol(const void *_a, const void *_b)
{
const struct prof_symbol *a = _a, *b = _b;

if (a->addr < b->addr)
return -1;

if (a->addr > b->addr)
return 1;

return 0;
}

static void load_symbols(void)
{
unsigned long nr_symbols = 0;
struct dl_phdr_info dl_info = {
.dlpi_name = "/proc/self/exe",
.dlpi_addr = 0,
};

prof_count_symbols(&dl_info, sizeof(dl_info), &nr_symbols);
dl_iterate_phdr(prof_count_symbols, &nr_symbols);

prof_symbols = calloc(nr_symbols, sizeof(struct prof_symbol));

prof_load_symbols(&dl_info, sizeof(dl_info), NULL);
dl_iterate_phdr(prof_load_symbols, NULL);

qsort(prof_symbols, prof_nr_symbols,
sizeof(struct prof_symbol), prof_cmp_symbol);
}

static struct prof_symbol *find_symbol(void *addr)
{
struct prof_symbol *sym;
unsigned long l, u, i;

l = 0;
u = prof_nr_symbols;

while (l < u) {
i = (l + u) / 2;
sym = &prof_symbols[i];

if (addr >= sym->addr && addr < sym->addr + sym->size)
return sym;

if (addr < sym->addr)
u = i;
else
l = i + 1;
}

return NULL;
}

void prof_exit(void) __attribute__((destructor));
void prof_exit(void)
{
FILE *file;
int i;
int64_t max_fn_count = 0;

load_symbols();

file = fopen("prof.dot", "w");
if (!file)
die("failed to create output file");

fprintf(file, "#\n# first count: %lu\n#\n", first_count);

/*
* Maybe replace "profile" with the argv
*/
fprintf(file, "digraph profile {\n");

for (i = 0; i < ARC_HASH_SIZE; i++) {
struct prof_arc *arc = &prof_arc_hash[i];
struct prof_symbol *c_sym, *t_sym;
struct prof_fn *c;
double p;

if (!(arc->call_fn && arc->this_fn))
continue;

c_sym = find_symbol(arc->call_fn);
t_sym = find_symbol(arc->this_fn);

if (!c_sym || !t_sym)
die("symbols missing");

c = prof_fn_find(c_sym->addr);
if (!c)
die("fn_hash|symtab borken");

p = (double)arc->count / (double)c->count;
fprintf(file,
" \"%s\" -> \"%s\" [label=\"%f\", color=\"%.3f %.3f %.3f\"]\n",
c_sym->name, t_sym->name, 100.0 * p,
0.33, 1.0, p);
}

for (i = 0; i < ARC_HASH_SIZE; i++) {
struct prof_arc *arc = &prof_arc_hash[i];
struct prof_symbol *c_sym;
struct prof_fn *c;
double p;

if (!(arc->call_fn && arc->this_fn))
continue;

c_sym = find_symbol(arc->call_fn);
c = prof_fn_find(c_sym->addr);

c->count -= arc->count;
}

for (i = 0; i < FN_HASH_SIZE; i++) {
struct prof_fn *fn = &prof_fn_hash[i];
struct prof_symbol *s;

if (!fn->this_fn)
continue;

if ((int64_t)fn->count > max_fn_count)
max_fn_count = fn->count;

s = find_symbol(fn->this_fn);
// fprintf(file, "# %s %ld\n", s->name, fn->count);
}

for (i = 0; i < FN_HASH_SIZE; i++) {
struct prof_fn *fn = &prof_fn_hash[i];
struct prof_symbol *s;
double p;

if (!fn->this_fn)
continue;

s = find_symbol(fn->this_fn);
p = (double)fn->count / (double)max_fn_count;
fprintf(file, " \"%s\" [color=\"%.3f %.3f %.3f\"]\n",
s->name, 0.0, 1.0, p);
}

fprintf(file, "}\n");
fflush(file);
fclose(file);
}

struct tramp_data {

void *(*func)(void *);
void *arg;

pthread_mutex_t lock;
pthread_cond_t wait;
};

static void *tramp_func(void *data)
{
struct tramp_data *tramp_data = data;
void *(*func)(void *) = tramp_data->func;
void *arg = tramp_data->arg;
void *ret;

thread_init();

pthread_mutex_lock(&tramp_data->lock);
pthread_cond_signal(&tramp_data->wait);
pthread_mutex_unlock(&tramp_data->lock);

ret = func(arg);

munmap(perf_event, page_size);

return ret;
}

/* hijack pthread_create() */
int pthread_create(pthread_t *__restrict thread,
__const pthread_attr_t *__restrict attr,
void *(*func)(void *),
void *__restrict arg)
{
struct tramp_data tramp_data = {
.func = func,
.arg = arg,
};
int ret;

pthread_cond_init(&tramp_data.wait, NULL);
pthread_mutex_init(&tramp_data.lock, NULL);

pthread_mutex_lock(&tramp_data.lock);

ret = pthread_create_orig(thread, attr, &tramp_func, &tramp_data);
if (!ret)
pthread_cond_wait(&tramp_data.wait, &tramp_data.lock);

pthread_mutex_unlock(&tramp_data.lock);

pthread_mutex_destroy(&tramp_data.lock);
pthread_cond_destroy(&tramp_data.wait);

return ret;
}

void __cyg_profile_func_enter(void *this_fn, void *call_fn)
{
struct prof_stack *st = &prof_stack[prof_stack_idx++];

st->arc = prof_arc_find(this_fn, call_fn);
st->fn = prof_fn_find(this_fn);
st->stamp = mmap_read_self(perf_event);
}

void __cyg_profile_func_exit(void *this_fn, void *call_fn)
{
struct prof_stack *st = &prof_stack[--prof_stack_idx];
uint64_t now, delta;

now = mmap_read_self(perf_event);
delta = now - st->stamp;
(void)__sync_fetch_and_add(&st->fn->count, delta);
(void)__sync_fetch_and_add(&st->arc->count, delta);
}

2013-03-27 17:07:56

by Roberto Vitillo

[permalink] [raw]
Subject: Re: [PATCH v2] perf: add callgrind conversion tool

On Wed, Mar 27, 2013 at 12:23 AM, Namhyung Kim <[email protected]> wrote:
> Hi Roberto,
>
> On Tue, 26 Mar 2013 12:14:49 -0700, Roberto Vitillo wrote:
>> The proposed patch adds the convert tool to perf which allows to convert a
>> perf.data file to a set of callgrind data files which can subsequently be
>> displayed with kcachegrind.
>>
>> Note that the code may trigger the following bug in libbfd:
>> http://sourceware.org/bugzilla/show_bug.cgi?id=15106
>
> Does it now support call graph too? Great if so!

Yes it does.

> Anyway, I suggest that splitting patch to two - one for addr2line and
> another for actual 'convert' tool.

Ok

> I think we *can* use the tool
> without libbfd (but with external addr2line binary or even with no line
> number information).

Well, I could use the external addr2line binary but the tool will
become terribly slow. A warning message could notify the
user to recompile perf with libbfd to speed up the conversion.

2013-03-27 21:29:45

by Viktor Ostashevskyi

[permalink] [raw]
Subject: Re: [PATCH v2] perf: add callgrind conversion tool

Hi Roberto,

Roberto Vitillo <ravitillo <at> lbl.gov> writes:

> The proposed patch adds the convert tool to perf which allows to convert a
> perf.data file to a set of callgrind data files which can subsequently be
> displayed with kcachegrind.


Incredible job! Absence of this feature forced me to write my own converter,
perfgrind (https://github.com/ostash/perfgrind).

BTW, you can get a2l functionality from elfutils as well, so there will be no
dependency on libbfd.


2013-03-28 23:59:05

by Sukadev Bhattiprolu

[permalink] [raw]
Subject: Re: [PATCH v2] perf: add callgrind conversion tool

Roberto Vitillo [[email protected]] wrote:
| The proposed patch adds the convert tool to perf which allows to convert a
| perf.data file to a set of callgrind data files which can subsequently be
| displayed with kcachegrind.

Sounds interesting and useful. My only comment is that 'convert' is a
generic term and this is converting to a specific format (i.e callgrind).
In theory we could have conversions to other formats in the future ?

Any chance it could be called 'perf callgrind' ?

Sukadev

2013-03-29 04:18:58

by Namhyung Kim

[permalink] [raw]
Subject: Re: [PATCH v2] perf: add callgrind conversion tool

On Thu, Mar 28, 2013 at 04:58:30PM -0700, Sukadev Bhattiprolu wrote:
> Roberto Vitillo [[email protected]] wrote:
> | The proposed patch adds the convert tool to perf which allows to convert a
> | perf.data file to a set of callgrind data files which can subsequently be
> | displayed with kcachegrind.
>
> Sounds interesting and useful. My only comment is that 'convert' is a
> generic term and this is converting to a specific format (i.e callgrind).
> In theory we could have conversions to other formats in the future ?

Maybe we can support converting to CTF or someother formats which have
nice viewer?

Thanks,
Namhyung

2013-03-29 04:50:44

by Roberto Vitillo

[permalink] [raw]
Subject: Re: [PATCH v2] perf: add callgrind conversion tool

On Thu, Mar 28, 2013 at 9:18 PM, Namhyung Kim <[email protected]> wrote:
> On Thu, Mar 28, 2013 at 04:58:30PM -0700, Sukadev Bhattiprolu wrote:
>> Roberto Vitillo [[email protected]] wrote:
>> | The proposed patch adds the convert tool to perf which allows to convert a
>> | perf.data file to a set of callgrind data files which can subsequently be
>> | displayed with kcachegrind.
>>
>> Sounds interesting and useful. My only comment is that 'convert' is a
>> generic term and this is converting to a specific format (i.e callgrind).
>> In theory we could have conversions to other formats in the future ?
>
> Maybe we can support converting to CTF or someother formats which have
> nice viewer?

That sounds like a good idea. I could add a -t option to the convert tool
that allows to specify the output format. Initially the tool would have just
the callgrind one but later on we can add support for CTF and possibly
other formats.

Roberto