perf probe: Introduce perf_cache interfaces
authorMasami Hiramatsu <mhiramat@kernel.org>
Wed, 15 Jun 2016 03:28:30 +0000 (12:28 +0900)
committerArnaldo Carvalho de Melo <acme@redhat.com>
Wed, 15 Jun 2016 17:34:31 +0000 (14:34 -0300)
Introduce perf_cache object and interfaces to create, add entries,
commit, and delete the object.

perf_cache represents a file for the cached "perf probe" definitions on
one binary file or vmlinux which has its own build id. The probe cache
file is located under the build-id cache directory of the target binary,
as below;

  <perf-debug-dir>/.build-id/<BU>/<ILDID>/probe

Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org>
Signed-off-by: Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com>
Cc: Ananth N Mavinakayanahalli <ananth@linux.vnet.ibm.com>
Cc: Brendan Gregg <brendan.d.gregg@gmail.com>
Cc: Hemant Kumar <hemant@linux.vnet.ibm.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Link: http://lkml.kernel.org/r/20160615032830.31330.84998.stgit@devbox
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
tools/perf/util/probe-file.c
tools/perf/util/probe-file.h

index 3fe6214970e632932cb3809dea4c36d0aa792029..25a40427003e19c33f074583ff8ddb7e18f2b9b0 100644 (file)
@@ -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,333 @@ int probe_file__del_events(int fd, struct strfilter *filter)
 
        return ret;
 }
+
+/* Caller must ensure to remove this entry from list */
+static void probe_cache_entry__delete(struct probe_cache_entry *entry)
+{
+       if (entry) {
+               BUG_ON(!list_empty(&entry->node));
+
+               strlist__delete(entry->tevlist);
+               clear_perf_probe_event(&entry->pev);
+               zfree(&entry->spev);
+               free(entry);
+       }
+}
+
+static struct probe_cache_entry *
+probe_cache_entry__new(struct perf_probe_event *pev)
+{
+       struct probe_cache_entry *entry = zalloc(sizeof(*entry));
+
+       if (entry) {
+               INIT_LIST_HEAD(&entry->node);
+               entry->tevlist = strlist__new(NULL, NULL);
+               if (!entry->tevlist)
+                       zfree(&entry);
+               else if (pev) {
+                       entry->spev = synthesize_perf_probe_command(pev);
+                       if (!entry->spev ||
+                           perf_probe_event__copy(&entry->pev, pev) < 0) {
+                               probe_cache_entry__delete(entry);
+                               return NULL;
+                       }
+               }
+       }
+
+       return entry;
+}
+
+/* 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 = DSO__NAME_KALLSYMS;
+               ret = sysfs__sprintf_build_id("/", sbuildid);
+       }
+       if (ret < 0) {
+               pr_debug("Failed to get build-id from %s.\n", target);
+               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__cachedir(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, 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");
+       if (!fp)
+               return -EINVAL;
+
+       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);
+                       if (!entry) {
+                               ret = -ENOMEM;
+                               goto out;
+                       }
+                       entry->spev = strdup(buf + 1);
+                       if (entry->spev)
+                               ret = parse_perf_probe_command(buf + 1,
+                                                               &entry->pev);
+                       else
+                               ret = -ENOMEM;
+                       if (ret < 0) {
+                               probe_cache_entry__delete(entry);
+                               goto out;
+                       }
+                       list_add_tail(&entry->node, &pcache->entries);
+               } 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 *pcache = zalloc(sizeof(*pcache));
+
+       if (pcache) {
+               INIT_LIST_HEAD(&pcache->entries);
+               pcache->fd = -EINVAL;
+       }
+       return pcache;
+}
+
+void probe_cache__purge(struct probe_cache *pcache)
+{
+       struct probe_cache_entry *entry, *n;
+
+       list_for_each_entry_safe(entry, n, &pcache->entries, node) {
+               list_del_init(&entry->node);
+               probe_cache_entry__delete(entry);
+       }
+}
+
+void probe_cache__delete(struct probe_cache *pcache)
+{
+       if (!pcache)
+               return;
+
+       probe_cache__purge(pcache);
+       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 = synthesize_perf_probe_command(pev);
+
+       if (!cmd)
+               return NULL;
+
+       list_for_each_entry(entry, &pcache->entries, node) {
+               /* 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) {
+               list_del_init(&entry->node);
+               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->node, &pcache->entries);
+       pr_debug("Added probe cache: %d\n", ntevs);
+       return 0;
+
+out_err:
+       pr_debug("Failed to add probe caches\n");
+       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 stat st;
+       struct iovec iov[3];
+       int ret;
+       /* Save stat for rollback */
+       ret = fstat(fd, &st);
+       if (ret < 0)
+               return 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 < (int)iov[1].iov_len + 2)
+               goto rollback;
+
+       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 < (int)iov[0].iov_len + 1)
+                       goto rollback;
+       }
+       return 0;
+
+rollback:
+       /* Rollback to avoid cache file corruption */
+       if (ret > 0)
+               ret = -1;
+       if (ftruncate(fd, st.st_size) < 0)
+               ret = -2;
+
+       return ret;
+}
+
+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->entries, node) {
+               ret = probe_cache_entry__write(entry, pcache->fd);
+               pr_debug("Cache committed: %d\n", ret);
+               if (ret < 0)
+                       break;
+       }
+out:
+       return ret;
+}
index 18ac9cf51c3433438eb3036d340f34e5975161bc..d872e3df7e59166d00bb51eb696e5df38f8c032d 100644 (file)
@@ -5,6 +5,19 @@
 #include "strfilter.h"
 #include "probe-event.h"
 
+/* Cache of probe definitions */
+struct probe_cache_entry {
+       struct list_head        node;
+       struct perf_probe_event pev;
+       char                    *spev;
+       struct strlist          *tevlist;
+};
+
+struct probe_cache {
+       int     fd;
+       struct list_head entries;
+};
+
 #define PF_FL_UPROBE   1
 #define PF_FL_RW       2
 
@@ -18,5 +31,12 @@ 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__purge(struct probe_cache *pcache);
+void probe_cache__delete(struct probe_cache *pcache);
 
 #endif