From 511e19b9e2112463c33a744ecb8a798056074408 Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Mon, 19 Dec 2022 12:17:30 -0800 Subject: [PATCH] perf lock contention: Add -L/--lock-filter option 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 Committer testing: # uname -a Linux quaco 6.0.12-200.fc36.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Dec 8 17:15:53 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux # perf lock record ^C[ perf record: Woken up 1 times to write data ] # perf lock con -L jiffies_lock,rcu_state contended total wait max wait avg wait type caller # perf lock con contended total wait max wait avg wait type caller 1 9.06 us 9.06 us 9.06 us spinlock call_timer_fn+0x24 # perf lock con -L call ignore unknown symbol: call contended total wait max wait avg wait type caller 1 9.06 us 9.06 us 9.06 us spinlock call_timer_fn+0x24 # Signed-off-by: Namhyung Kim Tested-by: Arnaldo Carvalho de Melo Cc: Adrian Hunter Cc: Blake Jones Cc: Ian Rogers Cc: Ingo Molnar Cc: Jiri Olsa Cc: Peter Zijlstra Cc: Song Liu Cc: bpf@vger.kernel.org Link: https://lore.kernel.org/r/20221219201732.460111-5-namhyung@kernel.org Signed-off-by: Arnaldo Carvalho de Melo --- 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=:: + 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 171b0fb5c974..718b82bfcdff 100644 --- a/tools/perf/builtin-lock.c +++ b/tools/perf/builtin-lock.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -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 {