Commit 511e19b9 authored by Namhyung Kim's avatar Namhyung Kim Committed by Arnaldo Carvalho de Melo
Browse files

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: default avatarNamhyung Kim <namhyung@kernel.org>
Tested-by: default avatarArnaldo 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: default avatarArnaldo Carvalho de Melo <acme@redhat.com>
parent 529772c4
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -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
--------
+134 −6
Original line number Diff line number Diff line
@@ -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)
	};

+4 −0
Original line number Diff line number Diff line
@@ -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 {