From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1754375AbbGOJTM (ORCPT ); Wed, 15 Jul 2015 05:19:12 -0400 Received: from mail7.hitachi.co.jp ([133.145.228.42]:43512 "EHLO mail7.hitachi.co.jp" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1754356AbbGOJTJ (ORCPT ); Wed, 15 Jul 2015 05:19:09 -0400 Subject: [RFC PATCH perf/core v2 09/16] perf probe: Add --cache option to cache the probe definitions From: Masami Hiramatsu To: Arnaldo Carvalho de Melo Cc: Peter Zijlstra , linux-kernel@vger.kernel.org, Adrian Hunter , Ingo Molnar , Paul Mackerras , Jiri Olsa , Namhyung Kim , Borislav Petkov , Hemant Kumar Date: Wed, 15 Jul 2015 18:14:55 +0900 Message-ID: <20150715091455.8915.96488.stgit@localhost.localdomain> In-Reply-To: <20150715091352.8915.87480.stgit@localhost.localdomain> References: <20150715091352.8915.87480.stgit@localhost.localdomain> User-Agent: StGit/0.17.1-dirty MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Add --cache option to cache the probe definitions. This just saves the result of the dwarf analysis to probe cache. Signed-off-by: Masami Hiramatsu --- Changes in v2: - Update documentations/perf-probe.txt. --- tools/perf/Documentation/perf-probe.txt | 4 tools/perf/builtin-probe.c | 1 tools/perf/util/build-id.c | 13 + tools/perf/util/build-id.h | 2 tools/perf/util/probe-event.c | 136 ++++++++++++-- tools/perf/util/probe-event.h | 5 + tools/perf/util/probe-file.c | 298 +++++++++++++++++++++++++++++++ tools/perf/util/probe-file.h | 20 ++ 8 files changed, 447 insertions(+), 32 deletions(-) diff --git a/tools/perf/Documentation/perf-probe.txt b/tools/perf/Documentation/perf-probe.txt index 3a8a9ba..947db6f 100644 --- a/tools/perf/Documentation/perf-probe.txt +++ b/tools/perf/Documentation/perf-probe.txt @@ -109,6 +109,10 @@ OPTIONS Dry run. With this option, --add and --del doesn't execute actual adding and removal operations. +--cache:: + Cache the probes (with --add option). Any events which successfully added + are also stored in the cache file. + --max-probes=NUM:: Set the maximum number of probe points for an event. Default is 128. diff --git a/tools/perf/builtin-probe.c b/tools/perf/builtin-probe.c index b81cec3..5ac8a79 100644 --- a/tools/perf/builtin-probe.c +++ b/tools/perf/builtin-probe.c @@ -396,6 +396,7 @@ __cmd_probe(int argc, const char **argv, const char *prefix __maybe_unused) opt_set_filter), OPT_CALLBACK('x', "exec", NULL, "executable|path", "target executable name or path", opt_set_target), + OPT_BOOLEAN(0, "cache", &probe_conf.cache, "Manipulate probe cache"), OPT_BOOLEAN(0, "demangle", &symbol_conf.demangle, "Enable symbol demangling"), OPT_BOOLEAN(0, "demangle-kernel", &symbol_conf.demangle_kernel, diff --git a/tools/perf/util/build-id.c b/tools/perf/util/build-id.c index 02a5e0d..169639e 100644 --- a/tools/perf/util/build-id.c +++ b/tools/perf/util/build-id.c @@ -313,9 +313,8 @@ void disable_buildid_cache(void) no_buildid_cache = true; } -static char *build_id_cache__dirname_from_path(const char *name, - bool is_kallsyms, bool is_vdso, - const char *sbuild_id) +char *build_id_cache__dirname_from_path(const char *sbuild_id, const char *name, + bool is_kallsyms, bool is_vdso) { char *realname = (char *)name, *filename; bool slash = is_kallsyms || is_vdso; @@ -344,8 +343,8 @@ int build_id_cache__list_build_ids(const char *pathname, char *dir_name; int ret = 0; - dir_name = build_id_cache__dirname_from_path(pathname, false, false, - NULL); + dir_name = build_id_cache__dirname_from_path(NULL, pathname, + false, false); if (!dir_name) return -ENOMEM; @@ -372,8 +371,8 @@ int build_id_cache__add_s(const char *sbuild_id, const char *name, goto out_free; } - dir_name = build_id_cache__dirname_from_path(name, is_kallsyms, - is_vdso, sbuild_id); + dir_name = build_id_cache__dirname_from_path(sbuild_id, name, + is_kallsyms, is_vdso); if (!dir_name) goto out_free; diff --git a/tools/perf/util/build-id.h b/tools/perf/util/build-id.h index 27a14a8..a1f428d 100644 --- a/tools/perf/util/build-id.h +++ b/tools/perf/util/build-id.h @@ -27,6 +27,8 @@ bool perf_session__read_build_ids(struct perf_session *session, bool with_hits); int perf_session__write_buildid_table(struct perf_session *session, int fd); int perf_session__cache_build_ids(struct perf_session *session); +char *build_id_cache__dirname_from_path(const char *sbuild_id, const char *name, + bool is_kallsyms, bool is_vdso); int build_id_cache__list_build_ids(const char *pathname, struct strlist **result); bool build_id_cache__cached(const char *sbuild_id); diff --git a/tools/perf/util/probe-event.c b/tools/perf/util/probe-event.c index b6ed15e..fc30c2d 100644 --- a/tools/perf/util/probe-event.c +++ b/tools/perf/util/probe-event.c @@ -68,7 +68,6 @@ int e_snprintf(char *str, size_t size, const char *format, ...) return ret; } -static char *synthesize_perf_probe_point(struct perf_probe_point *pp); static struct machine *host_machine; /* Initialize symbol maps and path of vmlinux/modules */ @@ -1593,7 +1592,7 @@ char *synthesize_perf_probe_arg(struct perf_probe_arg *pa) } /* Compose only probe point (not argument) */ -static char *synthesize_perf_probe_point(struct perf_probe_point *pp) +char *synthesize_perf_probe_point(struct perf_probe_point *pp) { struct strbuf buf; char *tmp; @@ -1626,30 +1625,36 @@ static char *synthesize_perf_probe_point(struct perf_probe_point *pp) return tmp; } -#if 0 char *synthesize_perf_probe_command(struct perf_probe_event *pev) { - char *buf; - int i, len, ret; + struct strbuf buf; + char *tmp; + int i; - buf = synthesize_perf_probe_point(&pev->point); - if (!buf) - return NULL; + strbuf_init(&buf, 64); + if (pev->event) + strbuf_addf(&buf, "%s:%s=", pev->group ?: PERFPROBE_GROUP, + pev->event); + + tmp = synthesize_perf_probe_point(&pev->point); + if (!tmp) + goto out; + strbuf_addstr(&buf, tmp); + free(tmp); - len = strlen(buf); for (i = 0; i < pev->nargs; i++) { - ret = e_snprintf(&buf[len], MAX_CMDLEN - len, " %s", - pev->args[i].name); - if (ret <= 0) { - free(buf); - return NULL; - } - len += ret; + tmp = synthesize_perf_probe_arg(pev->args + i); + if (!tmp) + goto out; + strbuf_addf(&buf, " %s", tmp); + free(tmp); } - return buf; + tmp = strbuf_detach(&buf, NULL); +out: + strbuf_release(&buf); + return tmp; } -#endif static int __synthesize_probe_trace_arg_ref(struct probe_trace_arg_ref *ref, struct strbuf *buf, int depth) @@ -1730,7 +1735,6 @@ char *synthesize_probe_trace_command(struct probe_trace_event *tev) if (synthesize_probe_trace_arg(&tev->args[i], &buf) < 0) goto error; } - ret = strbuf_detach(&buf, NULL); error: strbuf_release(&buf); @@ -1876,6 +1880,79 @@ void clear_perf_probe_event(struct perf_probe_event *pev) memset(pev, 0, sizeof(*pev)); } +#define strdup_or_goto(str, label) \ +({ char *__p = NULL; if (str && !(__p = strdup(str))) goto label; __p; }) + +static int perf_probe_point__copy(struct perf_probe_point *dst, + struct perf_probe_point *src) +{ + dst->file = strdup_or_goto(src->file, out_err); + dst->function = strdup_or_goto(src->function, out_err); + dst->lazy_line = strdup_or_goto(src->lazy_line, out_err); + dst->line = src->line; + dst->retprobe = src->retprobe; + dst->offset = src->offset; + return 0; + +out_err: + clear_perf_probe_point(dst); + return -ENOMEM; +} + +static int perf_probe_arg__copy(struct perf_probe_arg *dst, + struct perf_probe_arg *src) +{ + struct perf_probe_arg_field *field, **ppfield; + + dst->name = strdup_or_goto(src->name, out_err); + dst->var = strdup_or_goto(src->var, out_err); + dst->type = strdup_or_goto(src->type, out_err); + + field = src->field; + ppfield = &(dst->field); + while (field) { + *ppfield = zalloc(sizeof(*field)); + if (!*ppfield) + goto out_err; + (*ppfield)->name = strdup_or_goto(field->name, out_err); + (*ppfield)->index = field->index; + (*ppfield)->ref = field->ref; + field = field->next; + ppfield = &((*ppfield)->next); + } + return 0; +out_err: + return -ENOMEM; +} + +int perf_probe_event__copy(struct perf_probe_event *dst, + struct perf_probe_event *src) +{ + int i; + + dst->event = strdup_or_goto(src->event, out_err); + dst->group = strdup_or_goto(src->group, out_err); + dst->target = strdup_or_goto(src->target, out_err); + dst->uprobes = src->uprobes; + + if (perf_probe_point__copy(&dst->point, &src->point) < 0) + goto out_err; + + dst->args = zalloc(sizeof(struct perf_probe_arg) * src->nargs); + if (!dst->args) + goto out_err; + dst->nargs = src->nargs; + + for (i = 0; i < src->nargs; i++) + if (perf_probe_arg__copy(&dst->args[i], &src->args[i]) < 0) + goto out_err; + return 0; + +out_err: + clear_perf_probe_event(dst); + return -ENOMEM; +} + void clear_probe_trace_event(struct probe_trace_event *tev) { struct probe_trace_arg_ref *ref, *next; @@ -2262,15 +2339,17 @@ static int probe_trace_event__set_name(struct probe_trace_event *tev, } static int __add_probe_trace_events(struct perf_probe_event *pev, - struct probe_trace_event *tevs, - int ntevs, bool allow_suffix) + struct probe_trace_event *tevs, + int ntevs, bool allow_suffix) { int i, fd, ret; struct probe_trace_event *tev = NULL; const char *event = NULL, *group = NULL; + struct probe_cache *cache = NULL; struct strlist *namelist; + int flag = PF_FL_RW | (pev->uprobes ? PF_FL_UPROBE : 0); - fd = probe_file__open(PF_FL_RW | (pev->uprobes ? PF_FL_UPROBE : 0)); + fd = probe_file__open(flag); if (fd < 0) return fd; @@ -2320,6 +2399,16 @@ static int __add_probe_trace_events(struct perf_probe_event *pev, /* Note that it is possible to skip all events because of blacklist */ if (ret >= 0 && event) { + if (probe_conf.cache) { + cache = probe_cache__new(pev->target); + if (!cache) + pr_info("Warning: Failed to add cache\n"); + else { + probe_cache__add_entry(cache, pev, tevs, ntevs); + probe_cache__commit(cache); + probe_cache__delete(cache); + } + } /* Show how to use the event. */ pr_info("\nYou can now use it in all perf tools, such as:\n\n"); pr_info("\tperf record -e %s:%s -aR sleep 1\n\n", group, event); @@ -2352,9 +2441,6 @@ static int find_probe_functions(struct map *map, char *name, return found; } -#define strdup_or_goto(str, label) \ - ({ char *__p = strdup(str); if (!__p) goto label; __p; }) - void __weak arch__fix_tev_from_maps(struct perf_probe_event *pev __maybe_unused, struct probe_trace_event *tev __maybe_unused, struct map *map __maybe_unused) { } diff --git a/tools/perf/util/probe-event.h b/tools/perf/util/probe-event.h index 4cff6be..9aceba3 100644 --- a/tools/perf/util/probe-event.h +++ b/tools/perf/util/probe-event.h @@ -12,6 +12,7 @@ struct probe_conf { bool show_location_range; bool force_add; bool no_inlines; + bool cache; int max_probes; }; extern struct probe_conf probe_conf; @@ -116,6 +117,10 @@ extern int parse_probe_trace_command(const char *cmd, extern char *synthesize_perf_probe_command(struct perf_probe_event *pev); extern char *synthesize_probe_trace_command(struct probe_trace_event *tev); extern char *synthesize_perf_probe_arg(struct perf_probe_arg *pa); +char *synthesize_perf_probe_point(struct perf_probe_point *pp); + +int perf_probe_event__copy(struct perf_probe_event *dst, + struct perf_probe_event *src); /* Check the perf_probe_event needs debuginfo */ extern bool perf_probe_event_need_dwarf(struct perf_probe_event *pev); diff --git a/tools/perf/util/probe-file.c b/tools/perf/util/probe-file.c index 2a7cc8c..572e8c2 100644 --- a/tools/perf/util/probe-file.c +++ b/tools/perf/util/probe-file.c @@ -14,6 +14,7 @@ * GNU General Public License for more details. * */ +#include #include "util.h" #include "event.h" #include "strlist.h" @@ -300,3 +301,300 @@ int probe_file__del_events(int fd, struct strfilter *filter) return ret; } +static void probe_cache_entry__delete(struct probe_cache_entry *node) +{ + if (!list_empty(&node->list)) + list_del(&node->list); + if (node->tevlist) + strlist__delete(node->tevlist); + clear_perf_probe_event(&node->pev); + free(node->spev); + free(node); +} + +static struct probe_cache_entry * +probe_cache_entry__new(struct perf_probe_event *pev) +{ + struct probe_cache_entry *ret = zalloc(sizeof(*ret)); + + if (ret) { + INIT_LIST_HEAD(&ret->list); + ret->tevlist = strlist__new(true, NULL); + if (!ret->tevlist) + zfree(&ret); + if (ret && pev) { + ret->spev = synthesize_perf_probe_command(pev); + if (!ret->spev || + perf_probe_event__copy(&ret->pev, pev) < 0) { + probe_cache_entry__delete(ret); + return NULL; + } + } + } + + return ret; +} + +/* For the kernel probe caches, pass target = NULL */ +static int probe_cache__open(struct probe_cache *pcache, const char *target) +{ + char cpath[PATH_MAX]; + char sbuildid[SBUILD_ID_SIZE]; + char *dir_name; + bool is_kallsyms = !target; + int ret, fd; + + if (target) + ret = filename__sprintf_build_id(target, sbuildid); + else { + target = "[kernel.kallsyms]"; + ret = sysfs__sprintf_build_id("/", sbuildid); + } + if (ret < 0) { + pr_warning("Failed to get build-id from %s.\n", target ?: "kernel"); + return ret; + } + + /* If we have no buildid cache, make it */ + if (!build_id_cache__cached(sbuildid)) { + ret = build_id_cache__add_s(sbuildid, target, + is_kallsyms, NULL); + if (ret < 0) { + pr_warning("Failed to add cache: %s\n", target); + return ret; + } + } + + dir_name = build_id_cache__dirname_from_path(sbuildid, target, + is_kallsyms, false); + if (!dir_name) + return -ENOMEM; + + snprintf(cpath, PATH_MAX, "%s/probes", dir_name); + fd = open(cpath, O_CREAT | O_RDWR | O_APPEND, 0644); + if (fd < 0) + pr_warning("Failed to open cache(%d): %s\n", fd, cpath); + free(dir_name); + pcache->fd = fd; + + return fd; +} + +static int probe_cache__load(struct probe_cache *pcache) +{ + struct probe_cache_entry *entry = NULL; + char buf[MAX_CMDLEN], *p; + int ret = 0; + FILE *fp; + + fp = fdopen(dup(pcache->fd), "r"); + while (!feof(fp)) { + if (!fgets(buf, MAX_CMDLEN, fp)) + break; + p = strchr(buf, '\n'); + if (p) + *p = '\0'; + if (buf[0] == '#') { /* #perf_probe_event */ + entry = probe_cache_entry__new(NULL); + entry->spev = strdup(buf + 1); + ret = parse_perf_probe_command(buf + 1, &entry->pev); + if (!entry->spev || ret < 0) { + probe_cache_entry__delete(entry); + goto out; + } + list_add_tail(&entry->list, &pcache->list); + } else { /* trace_probe_event */ + if (!entry) { + ret = -EINVAL; + goto out; + } + strlist__add(entry->tevlist, buf); + } + } +out: + fclose(fp); + return ret; +} + +static struct probe_cache *probe_cache__alloc(void) +{ + struct probe_cache *ret = zalloc(sizeof(*ret)); + + if (ret) { + INIT_LIST_HEAD(&ret->list); + ret->fd = -EINVAL; + } + return ret; +} + +void probe_cache__delete(struct probe_cache *pcache) +{ + struct probe_cache_entry *entry; + + if (!pcache) + return; + + while (!list_empty(&pcache->list)) { + entry = list_first_entry(&pcache->list, typeof(*entry), list); + probe_cache_entry__delete(entry); + } + if (pcache->fd > 0) + close(pcache->fd); + free(pcache); +} + +struct probe_cache *probe_cache__new(const char *target) +{ + struct probe_cache *pcache = probe_cache__alloc(); + int ret; + + if (!pcache) + return NULL; + + ret = probe_cache__open(pcache, target); + if (ret < 0) { + pr_debug("Cache open error: %d\n", ret); + goto out_err; + } + + ret = probe_cache__load(pcache); + if (ret < 0) { + pr_debug("Cache read error: %d\n", ret); + goto out_err; + } + + return pcache; + +out_err: + probe_cache__delete(pcache); + return NULL; +} + +static bool streql(const char *a, const char *b) +{ + if (a == b) + return true; + + if (!a || !b) + return false; + + return !strcmp(a, b); +} + +static struct probe_cache_entry * +probe_cache__find(struct probe_cache *pcache, struct perf_probe_event *pev) +{ + struct probe_cache_entry *entry = NULL; + char *cmd = NULL; + + cmd = synthesize_perf_probe_command(pev); + if (!cmd) + return NULL; + + list_for_each_entry(entry, &pcache->list, list) { + /* Hit if same event name or same command-string */ + if ((pev->event && + (streql(entry->pev.group, pev->group) && + streql(entry->pev.event, pev->event))) || + (!strcmp(entry->spev, cmd))) + goto found; + } + entry = NULL; + +found: + free(cmd); + return entry; +} + +int probe_cache__add_entry(struct probe_cache *pcache, + struct perf_probe_event *pev, + struct probe_trace_event *tevs, int ntevs) +{ + struct probe_cache_entry *entry = NULL; + char *command; + int i, ret = 0; + + if (!pcache || !pev || !tevs || ntevs <= 0) { + ret = -EINVAL; + goto out_err; + } + + /* Remove old cache entry */ + entry = probe_cache__find(pcache, pev); + if (entry) + probe_cache_entry__delete(entry); + + ret = -ENOMEM; + entry = probe_cache_entry__new(pev); + if (!entry) + goto out_err; + + for (i = 0; i < ntevs; i++) { + if (!tevs[i].point.symbol) + continue; + + command = synthesize_probe_trace_command(&tevs[i]); + if (!command) + goto out_err; + strlist__add(entry->tevlist, command); + free(command); + } + list_add_tail(&entry->list, &pcache->list); + pr_debug("Added probe cache: %d\n", ntevs); + return 0; + +out_err: + pr_debug("Failed to add probe caches\n"); + if (entry) + probe_cache_entry__delete(entry); + return ret; +} + +static int probe_cache_entry__write(struct probe_cache_entry *entry, int fd) +{ + struct str_node *snode; + struct iovec iov[3]; + int ret; + + pr_debug("Writing cache: #%s\n", entry->spev); + iov[0].iov_base = (void *)"#"; iov[0].iov_len = 1; + iov[1].iov_base = entry->spev; iov[1].iov_len = strlen(entry->spev); + iov[2].iov_base = (void *)"\n"; iov[2].iov_len = 1; + ret = writev(fd, iov, 3); + if (ret < 0) + return ret; + + strlist__for_each(snode, entry->tevlist) { + iov[0].iov_base = (void *)snode->s; + iov[0].iov_len = strlen(snode->s); + iov[1].iov_base = (void *)"\n"; iov[1].iov_len = 1; + ret = writev(fd, iov, 2); + if (ret < 0) + return ret; + } + return 0; +} + +int probe_cache__commit(struct probe_cache *pcache) +{ + struct probe_cache_entry *entry; + int ret = 0; + + /* TBD: if we do not update existing entries, skip it */ + ret = lseek(pcache->fd, 0, SEEK_SET); + if (ret < 0) + goto out; + + ret = ftruncate(pcache->fd, 0); + if (ret < 0) + goto out; + + list_for_each_entry(entry, &pcache->list, list) { + ret = probe_cache_entry__write(entry, pcache->fd); + pr_debug("Cache committed: %d\n", ret); + if (ret < 0) + break; + } +out: + return ret; +} diff --git a/tools/perf/util/probe-file.h b/tools/perf/util/probe-file.h index ada94a2..7b473b39 100644 --- a/tools/perf/util/probe-file.h +++ b/tools/perf/util/probe-file.h @@ -15,4 +15,24 @@ struct strlist *probe_file__get_rawlist(int fd); int probe_file__add_event(int fd, struct probe_trace_event *tev); int probe_file__del_events(int fd, struct strfilter *filter); +/* Cache of probe definitions */ +struct probe_cache_entry { + struct list_head list; + struct perf_probe_event pev; + char *spev; + struct strlist *tevlist; +}; + +struct probe_cache { + int fd; + struct list_head list; +}; + +struct probe_cache *probe_cache__new(const char *target); +int probe_cache__add_entry(struct probe_cache *pcache, + struct perf_probe_event *pev, + struct probe_trace_event *tevs, int ntevs); +int probe_cache__commit(struct probe_cache *pcache); +void probe_cache__delete(struct probe_cache *pcache); + #endif