[PATCH 4/6] perf lock contention: Add -L/--lock-filter option

From: Namhyung Kim
Date: Mon Dec 19 2022 - 15:18:04 EST


The -L/--lock-filter option is to filter only given locks. The locks
can be specified by address or name (if exists).

$ sudo ./perf lock record -a sleep 1

$ sudo ./perf lock con -l
contended total wait max wait avg wait address symbol

57 1.11 ms 42.83 us 19.54 us ffff9f4140059000
15 280.88 us 23.51 us 18.73 us ffffffff9d007a40 jiffies_lock
1 20.49 us 20.49 us 20.49 us ffffffff9d0d50c0 rcu_state
1 9.02 us 9.02 us 9.02 us ffff9f41759e9ba0

$ sudo ./perf lock con -L jiffies_lock,rcu_state
contended total wait max wait avg wait type caller

15 280.88 us 23.51 us 18.73 us spinlock tick_sched_do_timer+0x93
1 20.49 us 20.49 us 20.49 us spinlock __softirqentry_text_start+0xeb

$ sudo ./perf lock con -L ffff9f4140059000
contended total wait max wait avg wait type caller

38 779.40 us 42.83 us 20.51 us spinlock worker_thread+0x50
11 216.30 us 39.87 us 19.66 us spinlock queue_work_on+0x39
8 118.13 us 20.51 us 14.77 us spinlock kthread+0xe5

Signed-off-by: Namhyung Kim <namhyung@xxxxxxxxxx>
---
tools/perf/Documentation/perf-lock.txt | 4 +
tools/perf/builtin-lock.c | 140 +++++++++++++++++++++++--
tools/perf/util/lock-contention.h | 4 +
3 files changed, 142 insertions(+), 6 deletions(-)

diff --git a/tools/perf/Documentation/perf-lock.txt b/tools/perf/Documentation/perf-lock.txt
index dea04ad5c28e..0f9f720e599d 100644
--- a/tools/perf/Documentation/perf-lock.txt
+++ b/tools/perf/Documentation/perf-lock.txt
@@ -183,6 +183,10 @@ CONTENTION OPTIONS
Note that RW-variant of locks have :R and :W suffix. Names without the
suffix are shortcuts for the both variants. Ex) rwsem = rwsem:R + rwsem:W.

+-L::
+--lock-filter=<value>::
+ Show lock contention only for given lock addresses or names (comma separated list).
+

SEE ALSO
--------
diff --git a/tools/perf/builtin-lock.c b/tools/perf/builtin-lock.c
index e4e785d3b4ec..6b8ea2f0b90a 100644
--- a/tools/perf/builtin-lock.c
+++ b/tools/perf/builtin-lock.c
@@ -32,6 +32,7 @@
#include <semaphore.h>
#include <math.h>
#include <limits.h>
+#include <ctype.h>

#include <linux/list.h>
#include <linux/hash.h>
@@ -995,24 +996,52 @@ static int report_lock_contention_begin_event(struct evsel *evsel,
unsigned int flags = evsel__intval(evsel, sample, "flags");
u64 key;
int i, ret;
+ static bool kmap_loaded;
+ struct machine *machine = &session->machines.host;
+ struct map *kmap;
+ struct symbol *sym;

ret = get_key_by_aggr_mode(&key, addr, evsel, sample);
if (ret < 0)
return ret;

+ if (!kmap_loaded) {
+ unsigned long *addrs;
+
+ /* make sure it loads the kernel map to find lock symbols */
+ map__load(machine__kernel_map(machine));
+ kmap_loaded = true;
+
+ /* convert (kernel) symbols to addresses */
+ for (i = 0; i < filters.nr_syms; i++) {
+ sym = machine__find_kernel_symbol_by_name(machine,
+ filters.syms[i],
+ &kmap);
+ if (sym == NULL) {
+ pr_warning("ignore unknown symbol: %s\n",
+ filters.syms[i]);
+ continue;
+ }
+
+ addrs = realloc(filters.addrs,
+ (filters.nr_addrs + 1) * sizeof(*addrs));
+ if (addrs == NULL) {
+ pr_warning("memory allocation failure\n");
+ return -ENOMEM;
+ }
+
+ addrs[filters.nr_addrs++] = kmap->unmap_ip(kmap, sym->start);
+ filters.addrs = addrs;
+ }
+ }
+
ls = lock_stat_find(key);
if (!ls) {
char buf[128];
const char *name = "";
- struct machine *machine = &session->machines.host;
- struct map *kmap;
- struct symbol *sym;

switch (aggr_mode) {
case LOCK_AGGR_ADDR:
- /* make sure it loads the kernel map to find lock symbols */
- map__load(machine__kernel_map(machine));
-
sym = machine__find_kernel_symbol(machine, key, &kmap);
if (sym)
name = sym->name;
@@ -1052,6 +1081,20 @@ static int report_lock_contention_begin_event(struct evsel *evsel,
return 0;
}

+ if (filters.nr_addrs) {
+ bool found = false;
+
+ for (i = 0; i < filters.nr_addrs; i++) {
+ if (addr == filters.addrs[i]) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ return 0;
+ }
+
ts = thread_stat_findnew(sample->tid);
if (!ts)
return -ENOMEM;
@@ -1496,6 +1539,15 @@ static void lock_filter_finish(void)
{
zfree(&filters.types);
filters.nr_types = 0;
+
+ zfree(&filters.addrs);
+ filters.nr_addrs = 0;
+
+ for (int i = 0; i < filters.nr_syms; i++)
+ free(filters.syms[i]);
+
+ zfree(&filters.syms);
+ filters.nr_syms = 0;
}

static void sort_contention_result(void)
@@ -1995,6 +2047,80 @@ static int parse_lock_type(const struct option *opt __maybe_unused, const char *
return ret;
}

+static bool add_lock_addr(unsigned long addr)
+{
+ unsigned long *tmp;
+
+ tmp = realloc(filters.addrs, (filters.nr_addrs + 1) * sizeof(*filters.addrs));
+ if (tmp == NULL) {
+ pr_err("Memory allocation failure\n");
+ return false;
+ }
+
+ tmp[filters.nr_addrs++] = addr;
+ filters.addrs = tmp;
+ return true;
+}
+
+static bool add_lock_sym(char *name)
+{
+ char **tmp;
+ char *sym = strdup(name);
+
+ if (sym == NULL) {
+ pr_err("Memory allocation failure\n");
+ return false;
+ }
+
+ tmp = realloc(filters.syms, (filters.nr_syms + 1) * sizeof(*filters.syms));
+ if (tmp == NULL) {
+ pr_err("Memory allocation failure\n");
+ free(sym);
+ return false;
+ }
+
+ tmp[filters.nr_syms++] = sym;
+ filters.syms = tmp;
+ return true;
+}
+
+static int parse_lock_addr(const struct option *opt __maybe_unused, const char *str,
+ int unset __maybe_unused)
+{
+ char *s, *tmp, *tok;
+ int ret = 0;
+ u64 addr;
+
+ s = strdup(str);
+ if (s == NULL)
+ return -1;
+
+ for (tok = strtok_r(s, ", ", &tmp); tok; tok = strtok_r(NULL, ", ", &tmp)) {
+ char *end;
+
+ addr = strtoul(tok, &end, 16);
+ if (*end == '\0') {
+ if (!add_lock_addr(addr)) {
+ ret = -1;
+ break;
+ }
+ continue;
+ }
+
+ /*
+ * At this moment, we don't have kernel symbols. Save the symbols
+ * in a separate list and resolve them to addresses later.
+ */
+ if (!add_lock_sym(tok)) {
+ ret = -1;
+ break;
+ }
+ }
+
+ free(s);
+ return ret;
+}
+
int cmd_lock(int argc, const char **argv)
{
const struct option lock_options[] = {
@@ -2060,6 +2186,8 @@ int cmd_lock(int argc, const char **argv)
OPT_BOOLEAN('l', "lock-addr", &show_lock_addrs, "show lock stats by address"),
OPT_CALLBACK('Y', "type-filter", NULL, "FLAGS",
"Filter specific type of locks", parse_lock_type),
+ OPT_CALLBACK('L', "lock-filter", NULL, "ADDRS/NAMES",
+ "Filter specific address/symbol of locks", parse_lock_addr),
OPT_PARENT(lock_options)
};

diff --git a/tools/perf/util/lock-contention.h b/tools/perf/util/lock-contention.h
index dc621386a16b..b99e83fccf5c 100644
--- a/tools/perf/util/lock-contention.h
+++ b/tools/perf/util/lock-contention.h
@@ -7,7 +7,11 @@

struct lock_filter {
int nr_types;
+ int nr_addrs;
+ int nr_syms;
unsigned int *types;
+ unsigned long *addrs;
+ char **syms;
};

struct lock_stat {
--
2.39.0.314.g84b9a713c41-goog