summaryrefslogtreecommitdiff
path: root/tools/perf/util/dsos.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/perf/util/dsos.c')
-rw-r--r--tools/perf/util/dsos.c529
1 files changed, 339 insertions, 190 deletions
diff --git a/tools/perf/util/dsos.c b/tools/perf/util/dsos.c
index cf80aa42dd07..ab3d0c01dd63 100644
--- a/tools/perf/util/dsos.c
+++ b/tools/perf/util/dsos.c
@@ -12,115 +12,140 @@
#include <symbol.h> // filename__read_build_id
#include <unistd.h>
-static int __dso_id__cmp(struct dso_id *a, struct dso_id *b)
+void dsos__init(struct dsos *dsos)
{
- if (a->maj > b->maj) return -1;
- if (a->maj < b->maj) return 1;
+ init_rwsem(&dsos->lock);
- if (a->min > b->min) return -1;
- if (a->min < b->min) return 1;
+ dsos->cnt = 0;
+ dsos->allocated = 0;
+ dsos->dsos = NULL;
+ dsos->sorted = true;
+}
- if (a->ino > b->ino) return -1;
- if (a->ino < b->ino) return 1;
+static void dsos__purge(struct dsos *dsos)
+{
+ down_write(&dsos->lock);
- /*
- * Synthesized MMAP events have zero ino_generation, avoid comparing
- * them with MMAP events with actual ino_generation.
- *
- * I found it harmful because the mismatch resulted in a new
- * dso that did not have a build ID whereas the original dso did have a
- * build ID. The build ID was essential because the object was not found
- * otherwise. - Adrian
- */
- if (a->ino_generation && b->ino_generation) {
- if (a->ino_generation > b->ino_generation) return -1;
- if (a->ino_generation < b->ino_generation) return 1;
- }
+ for (unsigned int i = 0; i < dsos->cnt; i++) {
+ struct dso *dso = dsos->dsos[i];
- return 0;
-}
+ dso__set_dsos(dso, NULL);
+ dso__put(dso);
+ }
-static bool dso_id__empty(struct dso_id *id)
-{
- if (!id)
- return true;
+ zfree(&dsos->dsos);
+ dsos->cnt = 0;
+ dsos->allocated = 0;
+ dsos->sorted = true;
- return !id->maj && !id->min && !id->ino && !id->ino_generation;
+ up_write(&dsos->lock);
}
-static void dso__inject_id(struct dso *dso, struct dso_id *id)
+void dsos__exit(struct dsos *dsos)
{
- dso->id.maj = id->maj;
- dso->id.min = id->min;
- dso->id.ino = id->ino;
- dso->id.ino_generation = id->ino_generation;
+ dsos__purge(dsos);
+ exit_rwsem(&dsos->lock);
}
-static int dso_id__cmp(struct dso_id *a, struct dso_id *b)
+
+static int __dsos__for_each_dso(struct dsos *dsos,
+ int (*cb)(struct dso *dso, void *data),
+ void *data)
{
- /*
- * The second is always dso->id, so zeroes if not set, assume passing
- * NULL for a means a zeroed id
- */
- if (dso_id__empty(a) || dso_id__empty(b))
- return 0;
+ for (unsigned int i = 0; i < dsos->cnt; i++) {
+ struct dso *dso = dsos->dsos[i];
+ int err;
- return __dso_id__cmp(a, b);
+ err = cb(dso, data);
+ if (err)
+ return err;
+ }
+ return 0;
}
-int dso__cmp_id(struct dso *a, struct dso *b)
-{
- return __dso_id__cmp(&a->id, &b->id);
-}
+struct dsos__read_build_ids_cb_args {
+ bool with_hits;
+ bool have_build_id;
+};
-bool __dsos__read_build_ids(struct list_head *head, bool with_hits)
+static int dsos__read_build_ids_cb(struct dso *dso, void *data)
{
- bool have_build_id = false;
- struct dso *pos;
+ struct dsos__read_build_ids_cb_args *args = data;
struct nscookie nsc;
- list_for_each_entry(pos, head, node) {
- if (with_hits && !pos->hit && !dso__is_vdso(pos))
- continue;
- if (pos->has_build_id) {
- have_build_id = true;
- continue;
- }
- nsinfo__mountns_enter(pos->nsinfo, &nsc);
- if (filename__read_build_id(pos->long_name, &pos->bid) > 0) {
- have_build_id = true;
- pos->has_build_id = true;
- } else if (errno == ENOENT && pos->nsinfo) {
- char *new_name = dso__filename_with_chroot(pos, pos->long_name);
-
- if (new_name && filename__read_build_id(new_name,
- &pos->bid) > 0) {
- have_build_id = true;
- pos->has_build_id = true;
- }
- free(new_name);
+ if (args->with_hits && !dso__hit(dso) && !dso__is_vdso(dso))
+ return 0;
+ if (dso__has_build_id(dso)) {
+ args->have_build_id = true;
+ return 0;
+ }
+ nsinfo__mountns_enter(dso__nsinfo(dso), &nsc);
+ if (filename__read_build_id(dso__long_name(dso), dso__bid(dso)) > 0) {
+ args->have_build_id = true;
+ dso__set_has_build_id(dso);
+ } else if (errno == ENOENT && dso__nsinfo(dso)) {
+ char *new_name = dso__filename_with_chroot(dso, dso__long_name(dso));
+
+ if (new_name && filename__read_build_id(new_name, dso__bid(dso)) > 0) {
+ args->have_build_id = true;
+ dso__set_has_build_id(dso);
}
- nsinfo__mountns_exit(&nsc);
+ free(new_name);
}
+ nsinfo__mountns_exit(&nsc);
+ return 0;
+}
- return have_build_id;
+bool dsos__read_build_ids(struct dsos *dsos, bool with_hits)
+{
+ struct dsos__read_build_ids_cb_args args = {
+ .with_hits = with_hits,
+ .have_build_id = false,
+ };
+
+ dsos__for_each_dso(dsos, dsos__read_build_ids_cb, &args);
+ return args.have_build_id;
}
-static int __dso__cmp_long_name(const char *long_name, struct dso_id *id, struct dso *b)
+static int __dso__cmp_long_name(const char *long_name, const struct dso_id *id,
+ const struct dso *b)
{
- int rc = strcmp(long_name, b->long_name);
- return rc ?: dso_id__cmp(id, &b->id);
+ int rc = strcmp(long_name, dso__long_name(b));
+ return rc ?: dso_id__cmp(id, dso__id_const(b));
}
-static int __dso__cmp_short_name(const char *short_name, struct dso_id *id, struct dso *b)
+static int __dso__cmp_short_name(const char *short_name, const struct dso_id *id,
+ const struct dso *b)
{
- int rc = strcmp(short_name, b->short_name);
- return rc ?: dso_id__cmp(id, &b->id);
+ int rc = strcmp(short_name, dso__short_name(b));
+ return rc ?: dso_id__cmp(id, dso__id_const(b));
}
-static int dso__cmp_short_name(struct dso *a, struct dso *b)
+static int dsos__cmp_long_name_id_short_name(const void *va, const void *vb)
{
- return __dso__cmp_short_name(a->short_name, &a->id, b);
+ const struct dso *a = *((const struct dso **)va);
+ const struct dso *b = *((const struct dso **)vb);
+ int rc = strcmp(dso__long_name(a), dso__long_name(b));
+
+ if (!rc) {
+ rc = dso_id__cmp(dso__id_const(a), dso__id_const(b));
+ if (!rc)
+ rc = strcmp(dso__short_name(a), dso__short_name(b));
+ }
+ return rc;
+}
+
+struct dsos__key {
+ const char *long_name;
+ const struct dso_id *id;
+};
+
+static int dsos__cmp_key_long_name_id(const void *vkey, const void *vdso)
+{
+ const struct dsos__key *key = vkey;
+ const struct dso *dso = *((const struct dso **)vdso);
+
+ return __dso__cmp_long_name(key->long_name, key->id, dso);
}
/*
@@ -128,110 +153,121 @@ static int dso__cmp_short_name(struct dso *a, struct dso *b)
* Either one of the dso or name parameter must be non-NULL or the
* function will not work.
*/
-struct dso *__dsos__findnew_link_by_longname_id(struct rb_root *root, struct dso *dso,
- const char *name, struct dso_id *id)
+static struct dso *__dsos__find_by_longname_id(struct dsos *dsos,
+ const char *name,
+ struct dso_id *id,
+ bool write_locked)
{
- struct rb_node **p = &root->rb_node;
- struct rb_node *parent = NULL;
-
- if (!name)
- name = dso->long_name;
- /*
- * Find node with the matching name
- */
- while (*p) {
- struct dso *this = rb_entry(*p, struct dso, rb_node);
- int rc = __dso__cmp_long_name(name, id, this);
-
- parent = *p;
- if (rc == 0) {
- /*
- * In case the new DSO is a duplicate of an existing
- * one, print a one-time warning & put the new entry
- * at the end of the list of duplicates.
- */
- if (!dso || (dso == this))
- return this; /* Find matching dso */
- /*
- * The core kernel DSOs may have duplicated long name.
- * In this case, the short name should be different.
- * Comparing the short names to differentiate the DSOs.
- */
- rc = dso__cmp_short_name(dso, this);
- if (rc == 0) {
- pr_err("Duplicated dso name: %s\n", name);
- return NULL;
- }
+ struct dsos__key key = {
+ .long_name = name,
+ .id = id,
+ };
+ struct dso **res;
+
+ if (!dsos->sorted) {
+ if (!write_locked) {
+ struct dso *dso;
+
+ up_read(&dsos->lock);
+ down_write(&dsos->lock);
+ dso = __dsos__find_by_longname_id(dsos, name, id,
+ /*write_locked=*/true);
+ up_write(&dsos->lock);
+ down_read(&dsos->lock);
+ return dso;
}
- if (rc < 0)
- p = &parent->rb_left;
- else
- p = &parent->rb_right;
+ qsort(dsos->dsos, dsos->cnt, sizeof(struct dso *),
+ dsos__cmp_long_name_id_short_name);
+ dsos->sorted = true;
}
- if (dso) {
- /* Add new node and rebalance tree */
- rb_link_node(&dso->rb_node, parent, p);
- rb_insert_color(&dso->rb_node, root);
- dso->root = root;
- }
- return NULL;
+
+ res = bsearch(&key, dsos->dsos, dsos->cnt, sizeof(struct dso *),
+ dsos__cmp_key_long_name_id);
+ if (!res)
+ return NULL;
+
+ return dso__get(*res);
}
-void __dsos__add(struct dsos *dsos, struct dso *dso)
+int __dsos__add(struct dsos *dsos, struct dso *dso)
{
- list_add_tail(&dso->node, &dsos->head);
- __dsos__findnew_link_by_longname_id(&dsos->root, dso, NULL, &dso->id);
- /*
- * It is now in the linked list, grab a reference, then garbage collect
- * this when needing memory, by looking at LRU dso instances in the
- * list with atomic_read(&dso->refcnt) == 1, i.e. no references
- * anywhere besides the one for the list, do, under a lock for the
- * list: remove it from the list, then a dso__put(), that probably will
- * be the last and will then call dso__delete(), end of life.
- *
- * That, or at the end of the 'struct machine' lifetime, when all
- * 'struct dso' instances will be removed from the list, in
- * dsos__exit(), if they have no other reference from some other data
- * structure.
- *
- * E.g.: after processing a 'perf.data' file and storing references
- * to objects instantiated while processing events, we will have
- * references to the 'thread', 'map', 'dso' structs all from 'struct
- * hist_entry' instances, but we may not need anything not referenced,
- * so we might as well call machines__exit()/machines__delete() and
- * garbage collect it.
- */
- dso__get(dso);
+ if (dsos->cnt == dsos->allocated) {
+ unsigned int to_allocate = 2;
+ struct dso **temp;
+
+ if (dsos->allocated > 0)
+ to_allocate = dsos->allocated * 2;
+ temp = realloc(dsos->dsos, sizeof(struct dso *) * to_allocate);
+ if (!temp)
+ return -ENOMEM;
+ dsos->dsos = temp;
+ dsos->allocated = to_allocate;
+ }
+ dsos->dsos[dsos->cnt++] = dso__get(dso);
+ if (dsos->cnt >= 2 && dsos->sorted) {
+ dsos->sorted = dsos__cmp_long_name_id_short_name(&dsos->dsos[dsos->cnt - 2],
+ &dsos->dsos[dsos->cnt - 1])
+ <= 0;
+ }
+ dso__set_dsos(dso, dsos);
+ return 0;
}
-void dsos__add(struct dsos *dsos, struct dso *dso)
+int dsos__add(struct dsos *dsos, struct dso *dso)
{
+ int ret;
+
down_write(&dsos->lock);
- __dsos__add(dsos, dso);
+ ret = __dsos__add(dsos, dso);
up_write(&dsos->lock);
+ return ret;
}
-static struct dso *__dsos__findnew_by_longname_id(struct rb_root *root, const char *name, struct dso_id *id)
+struct dsos__find_id_cb_args {
+ const char *name;
+ struct dso_id *id;
+ struct dso *res;
+};
+
+static int dsos__find_id_cb(struct dso *dso, void *data)
{
- return __dsos__findnew_link_by_longname_id(root, NULL, name, id);
+ struct dsos__find_id_cb_args *args = data;
+
+ if (__dso__cmp_short_name(args->name, args->id, dso) == 0) {
+ args->res = dso__get(dso);
+ return 1;
+ }
+ return 0;
+
}
-static struct dso *__dsos__find_id(struct dsos *dsos, const char *name, struct dso_id *id, bool cmp_short)
+static struct dso *__dsos__find_id(struct dsos *dsos, const char *name, struct dso_id *id,
+ bool cmp_short, bool write_locked)
{
- struct dso *pos;
+ struct dso *res;
if (cmp_short) {
- list_for_each_entry(pos, &dsos->head, node)
- if (__dso__cmp_short_name(name, id, pos) == 0)
- return pos;
- return NULL;
+ struct dsos__find_id_cb_args args = {
+ .name = name,
+ .id = id,
+ .res = NULL,
+ };
+
+ __dsos__for_each_dso(dsos, dsos__find_id_cb, &args);
+ return args.res;
}
- return __dsos__findnew_by_longname_id(&dsos->root, name, id);
+ res = __dsos__find_by_longname_id(dsos, name, id, write_locked);
+ return res;
}
-struct dso *__dsos__find(struct dsos *dsos, const char *name, bool cmp_short)
+struct dso *dsos__find(struct dsos *dsos, const char *name, bool cmp_short)
{
- return __dsos__find_id(dsos, name, NULL, cmp_short);
+ struct dso *res;
+
+ down_read(&dsos->lock);
+ res = __dsos__find_id(dsos, name, NULL, cmp_short, /*write_locked=*/false);
+ up_read(&dsos->lock);
+ return res;
}
static void dso__set_basename(struct dso *dso)
@@ -239,7 +275,7 @@ static void dso__set_basename(struct dso *dso)
char *base, *lname;
int tid;
- if (sscanf(dso->long_name, "/tmp/perf-%d.map", &tid) == 1) {
+ if (sscanf(dso__long_name(dso), "/tmp/perf-%d.map", &tid) == 1) {
if (asprintf(&base, "[JIT] tid %d", tid) < 0)
return;
} else {
@@ -247,7 +283,7 @@ static void dso__set_basename(struct dso *dso)
* basename() may modify path buffer, so we must pass
* a copy.
*/
- lname = strdup(dso->long_name);
+ lname = strdup(dso__long_name(dso));
if (!lname)
return;
@@ -271,25 +307,23 @@ static struct dso *__dsos__addnew_id(struct dsos *dsos, const char *name, struct
struct dso *dso = dso__new_id(name, id);
if (dso != NULL) {
- __dsos__add(dsos, dso);
+ /*
+ * The dsos lock is held on entry, so rename the dso before
+ * adding it to avoid needing to take the dsos lock again to say
+ * the array isn't sorted.
+ */
dso__set_basename(dso);
- /* Put dso here because __dsos_add already got it */
- dso__put(dso);
+ __dsos__add(dsos, dso);
}
return dso;
}
-struct dso *__dsos__addnew(struct dsos *dsos, const char *name)
-{
- return __dsos__addnew_id(dsos, name, NULL);
-}
-
static struct dso *__dsos__findnew_id(struct dsos *dsos, const char *name, struct dso_id *id)
{
- struct dso *dso = __dsos__find_id(dsos, name, id, false);
+ struct dso *dso = __dsos__find_id(dsos, name, id, false, /*write_locked=*/true);
- if (dso && dso_id__empty(&dso->id) && !dso_id__empty(id))
- dso__inject_id(dso, id);
+ if (dso && dso_id__empty(dso__id(dso)) && !dso_id__empty(id))
+ __dso__inject_id(dso, id);
return dso ? dso : __dsos__addnew_id(dsos, name, id);
}
@@ -298,36 +332,151 @@ struct dso *dsos__findnew_id(struct dsos *dsos, const char *name, struct dso_id
{
struct dso *dso;
down_write(&dsos->lock);
- dso = dso__get(__dsos__findnew_id(dsos, name, id));
+ dso = __dsos__findnew_id(dsos, name, id);
up_write(&dsos->lock);
return dso;
}
-size_t __dsos__fprintf_buildid(struct list_head *head, FILE *fp,
- bool (skip)(struct dso *dso, int parm), int parm)
+struct dsos__fprintf_buildid_cb_args {
+ FILE *fp;
+ bool (*skip)(struct dso *dso, int parm);
+ int parm;
+ size_t ret;
+};
+
+static int dsos__fprintf_buildid_cb(struct dso *dso, void *data)
{
- struct dso *pos;
- size_t ret = 0;
+ struct dsos__fprintf_buildid_cb_args *args = data;
+ char sbuild_id[SBUILD_ID_SIZE];
- list_for_each_entry(pos, head, node) {
- char sbuild_id[SBUILD_ID_SIZE];
+ if (args->skip && args->skip(dso, args->parm))
+ return 0;
+ build_id__sprintf(dso__bid(dso), sbuild_id);
+ args->ret += fprintf(args->fp, "%-40s %s\n", sbuild_id, dso__long_name(dso));
+ return 0;
+}
- if (skip && skip(pos, parm))
- continue;
- build_id__sprintf(&pos->bid, sbuild_id);
- ret += fprintf(fp, "%-40s %s\n", sbuild_id, pos->long_name);
- }
- return ret;
+size_t dsos__fprintf_buildid(struct dsos *dsos, FILE *fp,
+ bool (*skip)(struct dso *dso, int parm), int parm)
+{
+ struct dsos__fprintf_buildid_cb_args args = {
+ .fp = fp,
+ .skip = skip,
+ .parm = parm,
+ .ret = 0,
+ };
+
+ dsos__for_each_dso(dsos, dsos__fprintf_buildid_cb, &args);
+ return args.ret;
+}
+
+struct dsos__fprintf_cb_args {
+ FILE *fp;
+ size_t ret;
+};
+
+static int dsos__fprintf_cb(struct dso *dso, void *data)
+{
+ struct dsos__fprintf_cb_args *args = data;
+
+ args->ret += dso__fprintf(dso, args->fp);
+ return 0;
+}
+
+size_t dsos__fprintf(struct dsos *dsos, FILE *fp)
+{
+ struct dsos__fprintf_cb_args args = {
+ .fp = fp,
+ .ret = 0,
+ };
+
+ dsos__for_each_dso(dsos, dsos__fprintf_cb, &args);
+ return args.ret;
+}
+
+static int dsos__hit_all_cb(struct dso *dso, void *data __maybe_unused)
+{
+ dso__set_hit(dso);
+ return 0;
+}
+
+int dsos__hit_all(struct dsos *dsos)
+{
+ return dsos__for_each_dso(dsos, dsos__hit_all_cb, NULL);
}
-size_t __dsos__fprintf(struct list_head *head, FILE *fp)
+struct dso *dsos__findnew_module_dso(struct dsos *dsos,
+ struct machine *machine,
+ struct kmod_path *m,
+ const char *filename)
{
- struct dso *pos;
- size_t ret = 0;
+ struct dso *dso;
+
+ down_write(&dsos->lock);
- list_for_each_entry(pos, head, node) {
- ret += dso__fprintf(pos, fp);
+ dso = __dsos__find_id(dsos, m->name, NULL, /*cmp_short=*/true, /*write_locked=*/true);
+ if (dso) {
+ up_write(&dsos->lock);
+ return dso;
}
+ /*
+ * Failed to find the dso so create it. Change the name before adding it
+ * to the array, to avoid unnecessary sorts and potential locking
+ * issues.
+ */
+ dso = dso__new_id(m->name, /*id=*/NULL);
+ if (!dso) {
+ up_write(&dsos->lock);
+ return NULL;
+ }
+ dso__set_basename(dso);
+ dso__set_module_info(dso, m, machine);
+ dso__set_long_name(dso, strdup(filename), true);
+ dso__set_kernel(dso, DSO_SPACE__KERNEL);
+ __dsos__add(dsos, dso);
- return ret;
+ up_write(&dsos->lock);
+ return dso;
+}
+
+static int dsos__find_kernel_dso_cb(struct dso *dso, void *data)
+{
+ struct dso **res = data;
+ /*
+ * The cpumode passed to is_kernel_module is not the cpumode of *this*
+ * event. If we insist on passing correct cpumode to is_kernel_module,
+ * we should record the cpumode when we adding this dso to the linked
+ * list.
+ *
+ * However we don't really need passing correct cpumode. We know the
+ * correct cpumode must be kernel mode (if not, we should not link it
+ * onto kernel_dsos list).
+ *
+ * Therefore, we pass PERF_RECORD_MISC_CPUMODE_UNKNOWN.
+ * is_kernel_module() treats it as a kernel cpumode.
+ */
+ if (!dso__kernel(dso) ||
+ is_kernel_module(dso__long_name(dso), PERF_RECORD_MISC_CPUMODE_UNKNOWN))
+ return 0;
+
+ *res = dso__get(dso);
+ return 1;
+}
+
+struct dso *dsos__find_kernel_dso(struct dsos *dsos)
+{
+ struct dso *res = NULL;
+
+ dsos__for_each_dso(dsos, dsos__find_kernel_dso_cb, &res);
+ return res;
+}
+
+int dsos__for_each_dso(struct dsos *dsos, int (*cb)(struct dso *dso, void *data), void *data)
+{
+ int err;
+
+ down_read(&dsos->lock);
+ err = __dsos__for_each_dso(dsos, cb, data);
+ up_read(&dsos->lock);
+ return err;
}