perf lock contention: Add -L/--lock-filter option
authorNamhyung Kim <namhyung@kernel.org>
Mon, 19 Dec 2022 20:17:30 +0000 (12:17 -0800)
committerArnaldo Carvalho de Melo <acme@redhat.com>
Wed, 21 Dec 2022 17:52:39 +0000 (14:52 -0300)
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 <namhyung@kernel.org>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Blake Jones <blakejones@google.com>
Cc: Ian Rogers <irogers@google.com>
Cc: Ingo Molnar <mingo@kernel.org>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Song Liu <song@kernel.org>
Cc: bpf@vger.kernel.org
Link: https://lore.kernel.org/r/20221219201732.460111-5-namhyung@kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
tools/perf/Documentation/perf-lock.txt
tools/perf/builtin-lock.c
tools/perf/util/lock-contention.h

index dea04ad..0f9f720 100644 (file)
@@ -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
 --------
index 171b0fb..718b82b 100644 (file)
@@ -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)
        };
 
index dc62138..b99e83f 100644 (file)
@@ -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 {