perf: Emit build-ids for processes and the kernel
authorBarret Rhoden <brho@cs.berkeley.edu>
Fri, 27 May 2016 19:20:57 +0000 (15:20 -0400)
committerBarret Rhoden <brho@cs.berkeley.edu>
Thu, 16 Jun 2016 15:48:39 +0000 (11:48 -0400)
For the kernel and for every mmap and process, we output a build-id.
The mmaps cover both shared libraries and the actual names of symlinked
binaries.

For instance, ash shows up as a process named ash as well as an mmap for
the actual binary busybox:

$ perf buildid-list perf.data -Hv  2>&1 | sort
...
build id event received for /bin/ash: 4eda16d472bcb2b6f610ed21d7036c6599515594
build id event received for /bin/busybox: 4eda16d472bcb2b6f610ed21d7036c6599515594

FWIW, we might not *need* build-ids with perf.  Even with them, you'll
need the absolute latest perf (e.g. late May, 2016) to handle symfs and
build-ids correctly.  Regardless, spitting out build-ids is the way to
go.

Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
tools/profile/perf/Makefile
tools/profile/perf/perf_core.c
tools/profile/perf/perf_format.h
tools/profile/perf/perfconv.c
tools/profile/perf/perfconv.h
tools/profile/perf/xlib.c
tools/profile/perf/xlib.h

index bed0460..6e506f6 100644 (file)
@@ -1,10 +1,10 @@
 include ../../Makefrag
 
-SOURCES = perf.c perfconv.c xlib.c perf_core.c akaros.c
+SOURCES = perf.c perfconv.c xlib.c perf_core.c akaros.c symbol-elf.c
 
 XCC = $(CROSS_COMPILE)gcc
 
-LIBS=-lperfmon
+LIBS=-lperfmon -lelf
 
 PHONY := all
 all: perf
index 287f891..9512e7e 100644 (file)
@@ -26,6 +26,7 @@
 #include "perfconv.h"
 #include "akaros.h"
 #include "perf_core.h"
+#include "elf.h"
 
 struct event_coords {
        char *buffer;
@@ -68,6 +69,7 @@ void perf_initialize(int argc, char *argv[])
                                pfm_strerror(err));
                exit(1);
        }
+       symbol__elf_init();
 }
 
 void perf_finalize(void)
@@ -615,6 +617,7 @@ void perf_convert_trace_data(struct perfconv_context *cctx, const char *input,
                outfile = xfopen(output, "wb");
 
                perfconv_add_kernel_mmap(cctx);
+               perfconv_add_kernel_buildid(cctx);
                perfconv_process_input(cctx, infile, outfile);
 
                fclose(outfile);
index ffe38cf..3dfe033 100644 (file)
@@ -466,6 +466,13 @@ struct nr_cpus {
        uint32_t                                        nr_cpus_available;
 };
 
+struct build_id_event {
+       struct perf_event_header        header;
+       pid_t                                           pid;
+       uint8_t                                         build_id[24];   /* BUILD_ID_SIZE aligned u64 */
+       char                                            filename[];
+};
+
 #define MAX_EVENT_NAME 64
 
 struct perf_trace_event_type {
index 4b064fb..66ca868 100644 (file)
@@ -29,6 +29,7 @@
 #include "xlib.h"
 #include "perf_core.h"
 #include "perfconv.h"
+#include "elf.h"
 
 #define MAX_PERF_RECORD_SIZE (32 * 1024 * 1024)
 #define PERF_RECORD_BUFFER_SIZE 1024
@@ -372,6 +373,106 @@ static void hdr_do_nrcpus(struct perf_header *ph, struct perf_headers *hdrs)
        headers_add_header(ph, hdrs, HEADER_NRCPUS, mb);
 }
 
+/* Returns TRUE if we already emitted a build-id for path.  If this gets too
+ * slow (which we'll know because of perf!) we can use a hash table (don't hash
+ * on the first few bytes btw - they are usually either /bin or /lib). */
+static bool lookup_buildid(struct perfconv_context *cctx, const char *path)
+{
+       struct build_id_event *b_evt;
+       struct mem_block *mb;
+       size_t b_evt_path_sz;
+
+       mb = cctx->hdrs.headers[HEADER_BUILD_ID];
+       while (mb) {
+               b_evt = (struct build_id_event*)mb->base;
+               b_evt_path_sz = b_evt->header.size -
+                               offsetof(struct build_id_event, filename);
+               /* ignoring the last byte since we forced it to be \0 earlier. */
+               if (!strncmp(b_evt->filename, path, b_evt_path_sz - 1))
+                       return TRUE;
+               mb = mb->next;
+       }
+       return FALSE;
+}
+
+/* Helper: given a path, allocs and inits a build_id_event within a mem_block,
+ * returning both via parameters.  Caller needs to set header.misc and fill in
+ * the actual build_id. */
+static void build_id_alloc(const char *path, struct build_id_event **b_evt_p,
+                           struct mem_block **mb_p)
+{
+       struct build_id_event *b_evt;
+       struct mem_block *mb;
+       size_t path_sz, b_evt_sz;
+
+       path_sz = strlen(path) + 1;
+       b_evt_sz = path_sz + sizeof(struct build_id_event);
+
+       mb = mem_block_alloc(b_evt_sz);
+       b_evt = (struct build_id_event*)mb->wptr;
+       mb->wptr += b_evt_sz;
+
+       b_evt->header.type = 0; /* if this fails, try 67 (synthetic build id) */
+       /* header.size filled in by the caller, depending on the type */
+       b_evt->header.size = b_evt_sz;
+       strlcpy(b_evt->filename, path, path_sz);
+
+       *b_evt_p = b_evt;
+       *mb_p = mb;
+}
+
+/* Add a build-id header.  Unlike many of the other headers, this one is built
+ * on the fly as we emit other records. */
+static void hdr_add_buildid(struct perfconv_context *cctx, const char *path,
+                            int pid)
+{
+       struct build_id_event *b_evt;
+       struct mem_block *mb;
+       int ret;
+
+       if (lookup_buildid(cctx, path))
+               return;
+
+       build_id_alloc(path, &b_evt, &mb);
+       b_evt->header.misc = PERF_RECORD_MISC_USER;
+       b_evt->pid = pid;
+       ret = filename__read_build_id(path, b_evt->build_id, BUILD_ID_SIZE);
+       if (ret <= 0)
+               free(mb);
+       else
+               headers_add_header(&cctx->ph, &cctx->hdrs, HEADER_BUILD_ID, mb);
+}
+
+static void convert_str_to_binary(char *b_id_str, uint8_t *b_id_raw)
+{
+       char *c = b_id_str;
+
+       for (int i = 0; i < BUILD_ID_SIZE; i++) {
+               b_id_raw[i] = nibble_to_num(*c) << 4 | nibble_to_num(*(c + 1));
+               c += 2;
+       }
+}
+
+void perfconv_add_kernel_buildid(struct perfconv_context *cctx)
+{
+       struct build_id_event *b_evt;
+       struct mem_block *mb;
+       int ret, fd;
+       char build_id[BUILD_ID_SIZE * 2 + 1] = {0};
+
+       build_id_alloc("[kernel.kallsyms]", &b_evt, &mb);
+       b_evt->header.misc = PERF_RECORD_MISC_KERNEL;
+       b_evt->pid = -1;
+       fd = xopen("#version/build_id", O_RDONLY, 0);
+       ret = read(fd, build_id, sizeof(build_id));
+       if (ret <= 0) {
+               free(mb);
+       } else {
+               convert_str_to_binary(build_id, b_evt->build_id);
+               headers_add_header(&cctx->ph, &cctx->hdrs, HEADER_BUILD_ID, mb);
+       }
+}
+
 /* Helper: adds all the headers, marking them in PH and storing them in
  * feat_hdrs. */
 static void headers_build(struct perf_header *ph, struct perf_headers *hdrs,
@@ -505,6 +606,8 @@ static void emit_pid_mmap64(struct perf_record *pr,
                      strlen((char*)rec->path) + 1;
        struct perf_record_mmap *xrec = xzmalloc(size);
 
+       hdr_add_buildid(cctx, (char*)rec->path, rec->pid);
+
        xrec->header.type = PERF_RECORD_MMAP;
        xrec->header.misc = PERF_RECORD_MISC_USER;
        xrec->header.size = size;
@@ -582,8 +685,11 @@ static void emit_new_process(struct perf_record *pr,
                                                         struct perfconv_context *cctx)
 {
        struct proftype_new_process *rec = (struct proftype_new_process *) pr->data;
-       const char *comm = strrchr((char*)rec->path, '/');
+       const char *comm;
+
+       hdr_add_buildid(cctx, (char*)rec->path, rec->pid);
 
+       comm = strrchr((char*)rec->path, '/');
        if (!comm)
                comm = (char*)rec->path;
        else
index 2b7929b..b014aa1 100644 (file)
@@ -71,5 +71,6 @@ struct perfconv_context *perfconv_create_context(struct perf_context *pctx);
 void perfconv_free_context(struct perfconv_context *cctx);
 void perfconv_set_dbglevel(int level, struct perfconv_context *cctx);
 void perfconv_add_kernel_mmap(struct perfconv_context *cctx);
+void perfconv_add_kernel_buildid(struct perfconv_context *cctx);
 void perfconv_process_input(struct perfconv_context *cctx, FILE *input,
                                                        FILE *output);
index 6281695..a1a40f1 100644 (file)
@@ -176,3 +176,48 @@ int vb_fdecode_uint64(FILE *file, uint64_t *pval)
 
        return i / 7;
 }
+
+uint8_t nibble_to_num(char c)
+{
+       switch (c) {
+       case '0':
+               return 0;
+       case '1':
+               return 1;
+       case '2':
+               return 2;
+       case '3':
+               return 3;
+       case '4':
+               return 4;
+       case '5':
+               return 5;
+       case '6':
+               return 6;
+       case '7':
+               return 7;
+       case '8':
+               return 8;
+       case '9':
+               return 9;
+       case 'a':
+       case 'A':
+               return 0xa;
+       case 'b':
+       case 'B':
+               return 0xb;
+       case 'c':
+       case 'C':
+               return 0xc;
+       case 'd':
+       case 'D':
+               return 0xd;
+       case 'e':
+       case 'E':
+               return 0xe;
+       case 'f':
+       case 'F':
+               return 0xf;
+       };
+       return -1;
+}
index ade4635..74d3eb1 100644 (file)
@@ -44,6 +44,7 @@ void *xzmalloc(size_t size);
 char *xstrdup(const char *str);
 const char *vb_decode_uint64(const char *data, uint64_t *pval);
 int vb_fdecode_uint64(FILE *file, uint64_t *pval);
+uint8_t nibble_to_num(char c);
 
 static inline void cpuid(uint32_t ieax, uint32_t iecx, uint32_t *eaxp,
                          uint32_t *ebxp, uint32_t *ecxp, uint32_t *edxp)