From: Masami Hiramatsu <masami.hiramatsu...@hitachi.com>

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 <masami.hiramatsu...@hitachi.com>
Signed-off-by: Masami Hiramatsu <mhira...@kernel.org>
---
 Changes in v5:
  - Move probe_cache* definitions. (code cleanup)

 Changes in v4:
  - Remove cache saving failure message.
---
 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           |  126 +++++++++++--
 tools/perf/util/probe-event.h           |    5 +
 tools/perf/util/probe-file.c            |  299 +++++++++++++++++++++++++++++++
 tools/perf/util/probe-file.h            |   19 ++
 8 files changed, 441 insertions(+), 28 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 9af859b..6d7ab431 100644
--- a/tools/perf/builtin-probe.c
+++ b/tools/perf/builtin-probe.c
@@ -512,6 +512,7 @@ __cmd_probe(int argc, const char **argv, const char *prefix 
__maybe_unused)
                    "Enable symbol demangling"),
        OPT_BOOLEAN(0, "demangle-kernel", &symbol_conf.demangle_kernel,
                    "Enable kernel symbol demangling"),
+       OPT_BOOLEAN(0, "cache", &probe_conf.cache, "Manipulate probe cache"),
        OPT_END()
        };
        int ret;
diff --git a/tools/perf/util/build-id.c b/tools/perf/util/build-id.c
index 848e05f..588ff01 100644
--- a/tools/perf/util/build-id.c
+++ b/tools/perf/util/build-id.c
@@ -371,9 +371,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;
@@ -402,8 +401,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;
 
@@ -430,8 +429,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 64af3e2..1841524 100644
--- a/tools/perf/util/build-id.h
+++ b/tools/perf/util/build-id.h
@@ -28,6 +28,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 85d82f4..dc0265e 100644
--- a/tools/perf/util/probe-event.c
+++ b/tools/perf/util/probe-event.c
@@ -67,7 +67,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 */
@@ -1703,7 +1702,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;
@@ -1734,30 +1733,36 @@ static char *synthesize_perf_probe_point(struct 
perf_probe_point *pp)
        return strbuf_detach(&buf, NULL);
 }
 
-#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)
@@ -2000,6 +2005,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;
@@ -2403,6 +2481,7 @@ static int __add_probe_trace_events(struct 
perf_probe_event *pev,
 {
        int i, fd, ret;
        struct probe_trace_event *tev = NULL;
+       struct probe_cache *cache = NULL;
        struct strlist *namelist;
 
        fd = probe_file__open(PF_FL_RW | (pev->uprobes ? PF_FL_UPROBE : 0));
@@ -2444,6 +2523,14 @@ static int __add_probe_trace_events(struct 
perf_probe_event *pev,
        }
        if (ret == -EINVAL && pev->uprobes)
                warn_uprobe_event_compat(tev);
+       if (ret == 0 && probe_conf.cache) {
+               cache = probe_cache__new(pev->target);
+               if (cache) {
+                       probe_cache__add_entry(cache, pev, tevs, ntevs);
+                       probe_cache__commit(cache);
+                       probe_cache__delete(cache);
+               }
+       }
 
        strlist__delete(namelist);
 close_out:
@@ -2472,9 +2559,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 e220962..c451223 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;
@@ -121,6 +122,10 @@ int parse_probe_trace_command(const char *cmd, struct 
probe_trace_event *tev);
 char *synthesize_perf_probe_command(struct perf_probe_event *pev);
 char *synthesize_probe_trace_command(struct probe_trace_event *tev);
 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 */
 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 3fe6214..bb849c9 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 <sys/uio.h>
 #include "util.h"
 #include "event.h"
 #include "strlist.h"
@@ -324,3 +325,301 @@ 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(NULL, 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_debug("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_debug("Failed to add build-id 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_debug("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 18ac9cf..d2b8791d 100644
--- a/tools/perf/util/probe-file.h
+++ b/tools/perf/util/probe-file.h
@@ -5,6 +5,19 @@
 #include "strfilter.h"
 #include "probe-event.h"
 
+/* 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;
+};
+
 #define PF_FL_UPROBE   1
 #define PF_FL_RW       2
 
@@ -18,5 +31,11 @@ int probe_file__get_events(int fd, struct strfilter *filter,
                                  struct strlist *plist);
 int probe_file__del_strlist(int fd, struct strlist *namelist);
 
+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

Reply via email to