[PATCH 21/49] perf tools: Maintain map groups list in a leader thread

Jiri Olsa
2018-01-09T15:36:36Z

From: Namhyung Kim <namhyung@kernel.org>

To support multi-threaded perf report, we need to maintain time-sorted
map groups. Add ->mg_list member to struct thread and sort the list
by time. Now leader threads have one more refcnt for map groups in
the list so also update the thread-mg-share test case.

Currently only add a new map groups when an exec (comm) event is
received.

Cc: Frederic Weisbecker <fweisbec@gmail.com>
Link: http://lkml.kernel.org/n/tip-xj6w1y6nwhkatvo1dcd5t6sk@git.kernel.org
Signed-off-by: Namhyung Kim <namhyung@kernel.org>
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
tools/perf/tests/thread-mg-share.c | 7 ++-
tools/perf/util/event.c | 2 +
tools/perf/util/machine.c | 15 ++++-
tools/perf/util/map.c | 3 +
tools/perf/util/map.h | 2 +
tools/perf/util/thread.c | 117 ++++++++++++++++++++++++++++++++++++-
tools/perf/util/thread.h | 3 +
7 files changed, 142 insertions(+), 7 deletions(-)

diff --git a/tools/perf/tests/thread-mg-share.c b/tools/perf/tests/thread-mg-share.c
index b1d1bbafe7ae..44a63d7961c8 100644
--- a/tools/perf/tests/thread-mg-share.c
+++ b/tools/perf/tests/thread-mg-share.c
@@ -24,6 +24,9 @@ int test__thread_mg_share(struct test *test __maybe_unused, int subtest __maybe_
* with several threads and checks they properly share and
* maintain map groups info (struct map_groups).
*
+ * Note that a leader thread has one more refcnt for its
+ * (current) map groups.
+ *
* thread group (pid: 0, tids: 0, 1, 2, 3)
* other group (pid: 4, tids: 4, 5)
*/
@@ -44,7 +47,7 @@ int test__thread_mg_share(struct test *test __maybe_unused, int subtest __maybe_
leader && t1 && t2 && t3 && other);

mg = leader->mg;
- TEST_ASSERT_EQUAL("wrong refcnt", refcount_read(&mg->refcnt), 4);
+ TEST_ASSERT_EQUAL("wrong refcnt", refcount_read(&mg->refcnt), 5);

/* test the map groups pointer is shared */
TEST_ASSERT_VAL("map groups don't match", mg == t1->mg);
@@ -72,7 +75,7 @@ int test__thread_mg_share(struct test *test __maybe_unused, int subtest __maybe_
machine__remove_thread(machine, other_leader);

other_mg = other->mg;
- TEST_ASSERT_EQUAL("wrong refcnt", refcount_read(&other_mg->refcnt), 2);
+ TEST_ASSERT_EQUAL("wrong refcnt", refcount_read(&other_mg->refcnt), 3);

TEST_ASSERT_VAL("map groups don't match", other_mg == other_leader->mg);

diff --git a/tools/perf/util/event.c b/tools/perf/util/event.c
index 14c4cf5cd707..79cc3c34cde2 100644
--- a/tools/perf/util/event.c
+++ b/tools/perf/util/event.c
@@ -1510,6 +1510,8 @@ void thread__find_addr_map(struct thread *thread, u8 cpumode,
return;
}

+ BUG_ON(mg == NULL);
+
if (cpumode == PERF_RECORD_MISC_KERNEL && perf_host) {
al->level = 'k';
mg = &machine->kmaps;
diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c
index ab79c2797a3c..6349ea11753b 100644
--- a/tools/perf/util/machine.c
+++ b/tools/perf/util/machine.c
@@ -395,8 +395,19 @@ static void machine__update_thread_pid(struct machine *machine,
if (!leader)
goto out_err;

- if (!leader->mg)
- leader->mg = map_groups__new(machine);
+ if (!leader->mg) {
+ struct map_groups *mg = map_groups__new(machine);
+
+ if (mg == NULL) {
+ pr_err("Not enough memory for map groups\n");
+ return;
+ }
+
+ if (thread__set_map_groups(leader, mg, 0) < 0) {
+ map_groups__put(mg);
+ goto out_err;
+ }
+ }

if (!leader->mg)
goto out_err;
diff --git a/tools/perf/util/map.c b/tools/perf/util/map.c
index 8fe57031e1a8..dbd8c2c3d712 100644
--- a/tools/perf/util/map.c
+++ b/tools/perf/util/map.c
@@ -500,6 +500,8 @@ void map_groups__init(struct map_groups *mg, struct machine *machine)
}
mg->machine = machine;
refcount_set(&mg->refcnt, 1);
+ mg->timestamp = 0;
+ INIT_LIST_HEAD(&mg->list);
}

static void __maps__purge(struct maps *maps)
@@ -556,6 +558,7 @@ struct map_groups *map_groups__new(struct machine *machine)
void map_groups__delete(struct map_groups *mg)
{
map_groups__exit(mg);
+ list_del(&mg->list);
free(mg);
}

diff --git a/tools/perf/util/map.h b/tools/perf/util/map.h
index edeb7291c8e1..38d5954771e8 100644
--- a/tools/perf/util/map.h
+++ b/tools/perf/util/map.h
@@ -70,6 +70,8 @@ struct map_groups {
struct maps maps[MAP__NR_TYPES];
struct machine *machine;
refcount_t refcnt;
+ u64 timestamp;
+ struct list_head list;
};

struct map_groups *map_groups__new(struct machine *machine);
diff --git a/tools/perf/util/thread.c b/tools/perf/util/thread.c
index 38510a40c898..6ad32879440d 100644
--- a/tools/perf/util/thread.c
+++ b/tools/perf/util/thread.c
@@ -16,12 +16,78 @@

#include <api/fs/fs.h>

+struct map_groups *thread__get_map_groups(struct thread *thread, u64 timestamp)
+{
+ struct map_groups *mg;
+ struct thread *leader = thread;
+
+ BUG_ON(thread->mg == NULL);
+
+ if (thread->tid != thread->pid_) {
+ leader = machine__find_thread_by_time(thread->mg->machine,
+ thread->pid_, thread->pid_,
+ timestamp);
+ if (leader == NULL)
+ goto out;
+ }
+
+ list_for_each_entry(mg, &leader->mg_list, list)
+ if (timestamp >= mg->timestamp)
+ return mg;
+
+out:
+ return thread->mg;
+}
+
+int thread__set_map_groups(struct thread *thread, struct map_groups *mg,
+ u64 timestamp)
+{
+ struct list_head *pos;
+ struct map_groups *old;
+
+ if (mg == NULL)
+ return -ENOMEM;
+
+ /*
+ * Only a leader thread can have map groups list - others
+ * reference it through map_groups__get. This means the
+ * leader thread will have one more refcnt than others.
+ */
+ if (thread->tid != thread->pid_)
+ return -EINVAL;
+
+ if (thread->mg) {
+ BUG_ON(refcount_read(&thread->mg->refcnt) <= 1);
+ map_groups__put(thread->mg);
+ }
+
+ /* sort by time */
+ list_for_each(pos, &thread->mg_list) {
+ old = list_entry(pos, struct map_groups, list);
+ if (timestamp > old->timestamp)
+ break;
+ }
+
+ list_add_tail(&mg->list, pos);
+ mg->timestamp = timestamp;
+
+ /* set current ->mg to most recent one */
+ thread->mg = list_first_entry(&thread->mg_list, struct map_groups, list);
+ /* increase one more refcnt for current */
+ map_groups__get(thread->mg);
+
+ return 0;
+}
+
int thread__init_map_groups(struct thread *thread, struct machine *machine)
{
pid_t pid = thread->pid_;

if (pid == thread->tid || pid == -1) {
- thread->mg = map_groups__new(machine);
+ struct map_groups *mg = map_groups__new(machine);
+
+ if (thread__set_map_groups(thread, mg, 0) < 0)
+ map_groups__put(mg);
} else {
struct thread *leader = __machine__findnew_thread(machine, pid, pid);
if (leader) {
@@ -48,6 +114,7 @@ struct thread *thread__new(pid_t pid, pid_t tid)
INIT_LIST_HEAD(&thread->comm_list);
init_rwsem(&thread->namespaces_lock);
init_rwsem(&thread->comm_lock);
+ INIT_LIST_HEAD(&thread->mg_list);

comm_str = malloc(32);
if (!comm_str)
@@ -77,7 +144,8 @@ struct thread *thread__new(pid_t pid, pid_t tid)
void thread__delete(struct thread *thread)
{
struct namespaces *namespaces, *tmp_namespaces;
- struct comm *comm, *tmp_comm;
+ struct comm *comm, *tmp;
+ struct map_groups *mg, *tmp_mg;

BUG_ON(!RB_EMPTY_NODE(&thread->rb_node));
BUG_ON(!list_empty(&thread->tid_list));
@@ -89,6 +157,7 @@ void thread__delete(struct thread *thread)
thread->mg = NULL;
}
down_write(&thread->namespaces_lock);
+
list_for_each_entry_safe(namespaces, tmp_namespaces,
&thread->namespaces_list, list) {
list_del(&namespaces->list);
@@ -97,7 +166,12 @@ void thread__delete(struct thread *thread)
up_write(&thread->namespaces_lock);

down_write(&thread->comm_lock);
- list_for_each_entry_safe(comm, tmp_comm, &thread->comm_list, list) {
+
+ /* only leader threads have mg list */
+ list_for_each_entry_safe(mg, tmp_mg, &thread->mg_list, list)
+ map_groups__put(mg);
+
+ list_for_each_entry_safe(comm, tmp, &thread->comm_list, list) {
list_del(&comm->list);
comm__free(comm);
}
@@ -217,6 +291,9 @@ struct comm *thread__comm_by_time(const struct thread *thread, u64 timestamp)
return list_last_entry(&thread->comm_list, struct comm, list);
}

+static int thread__clone_map_groups(struct thread *thread,
+ struct thread *parent);
+
static int ____thread__set_comm(struct thread *thread, const char *str,
u64 timestamp, bool exec)
{
@@ -247,6 +324,40 @@ static int ____thread__set_comm(struct thread *thread, const char *str,
unwind__flush_access(thread);
}

+ if (exec) {
+ struct machine *machine;
+
+ BUG_ON(thread->mg == NULL || thread->mg->machine == NULL);
+
+ machine = thread->mg->machine;
+
+ if (thread->tid != thread->pid_) {
+ struct map_groups *old = thread->mg;
+ struct thread *leader;
+
+ leader = machine__findnew_thread(machine, thread->pid_,
+ thread->pid_);
+
+ /* now it'll be a new leader */
+ thread->pid_ = thread->tid;
+
+ thread->mg = map_groups__new(old->machine);
+ if (thread->mg == NULL)
+ return -ENOMEM;
+
+ /* save current mg in the new leader */
+ thread__clone_map_groups(thread, leader);
+
+ /* current mg of leader thread needs one more refcnt */
+ map_groups__get(thread->mg);
+
+ thread__set_map_groups(thread, thread->mg, old->timestamp);
+ }
+
+ /* create a new mg for newly executed binary */
+ thread__set_map_groups(thread, map_groups__new(machine), timestamp);
+ }
+
thread->comm_set = true;

return 0;
diff --git a/tools/perf/util/thread.h b/tools/perf/util/thread.h
index 5ca81f29afa8..81995cb7102d 100644
--- a/tools/perf/util/thread.h
+++ b/tools/perf/util/thread.h
@@ -19,6 +19,7 @@ struct thread {
struct rb_node rb_node;
struct list_head tid_list;
struct map_groups *mg;
+ struct list_head mg_list;
pid_t pid_; /* Not all tools update this */
pid_t tid;
pid_t ppid;
@@ -90,6 +91,8 @@ struct comm *thread__comm_by_time(const struct thread *thread, u64 timestamp);
const char *thread__comm_str(const struct thread *thread);
int thread__insert_map(struct thread *thread, struct map *map);
const char *thread__comm_str_by_time(const struct thread *thread, u64 timestamp);
+struct map_groups *thread__get_map_groups(struct thread *thread, u64 timestamp);
+int thread__set_map_groups(struct thread *thread, struct map_groups *mg, u64 timestamp);
int thread__fork(struct thread *thread, struct thread *parent, u64 timestamp);
size_t thread__fprintf(struct thread *thread, FILE *fp);

--
2.13.6