mirror of
https://github.com/torvalds/linux.git
synced 2026-01-24 23:16:46 +00:00
perf lock: Move common lock contention code to new file
Avoid references from util code to builtin-lock that require python stubs. Move the functions and related variables to util/lock-contention.c. Add max_stack_depth parameter to match_callstack_filter to avoid sharing a global variable. Signed-off-by: Ian Rogers <irogers@google.com> Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com> Acked-by: Arnaldo Carvalho de Melo <acme@redhat.com> Acked-by: Namhyung Kim <namhyung@kernel.org> Cc: Adrian Hunter <adrian.hunter@intel.com> Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com> Cc: Andi Kleen <ak@linux.intel.com> Cc: Athira Rajeev <atrajeev@linux.vnet.ibm.com> Cc: Colin Ian King <colin.i.king@gmail.com> Cc: Dapeng Mi <dapeng1.mi@linux.intel.com> Cc: Howard Chu <howardchu95@gmail.com> Cc: Ilya Leoshkevich <iii@linux.ibm.com> Cc: Ingo Molnar <mingo@redhat.com> Cc: James Clark <james.clark@linaro.org> Cc: Jiri Olsa <jolsa@kernel.org> Cc: Josh Poimboeuf <jpoimboe@redhat.com> Cc: Kan Liang <kan.liang@linux.intel.com> Cc: Mark Rutland <mark.rutland@arm.com> Cc: Michael Petlan <mpetlan@redhat.com> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Thomas Richter <tmricht@linux.ibm.com> Cc: Veronika Molnarova <vmolnaro@redhat.com> Cc: Weilin Wang <weilin.wang@intel.com> Link: https://lore.kernel.org/r/20241119011644.971342-16-irogers@google.com Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
This commit is contained in:
committed by
Arnaldo Carvalho de Melo
parent
16ecb4316f
commit
1a12ed09bc
@@ -46,15 +46,6 @@
|
||||
static struct perf_session *session;
|
||||
static struct target target;
|
||||
|
||||
/* based on kernel/lockdep.c */
|
||||
#define LOCKHASH_BITS 12
|
||||
#define LOCKHASH_SIZE (1UL << LOCKHASH_BITS)
|
||||
|
||||
static struct hlist_head *lockhash_table;
|
||||
|
||||
#define __lockhashfn(key) hash_long((unsigned long)key, LOCKHASH_BITS)
|
||||
#define lockhashentry(key) (lockhash_table + __lockhashfn((key)))
|
||||
|
||||
static struct rb_root thread_stats;
|
||||
|
||||
static bool combine_locks;
|
||||
@@ -67,24 +58,13 @@ static unsigned long bpf_map_entries = MAX_ENTRIES;
|
||||
static int max_stack_depth = CONTENTION_STACK_DEPTH;
|
||||
static int stack_skip = CONTENTION_STACK_SKIP;
|
||||
static int print_nr_entries = INT_MAX / 2;
|
||||
static LIST_HEAD(callstack_filters);
|
||||
static const char *output_name = NULL;
|
||||
static FILE *lock_output;
|
||||
|
||||
struct callstack_filter {
|
||||
struct list_head list;
|
||||
char name[];
|
||||
};
|
||||
|
||||
static struct lock_filter filters;
|
||||
|
||||
static enum lock_aggr_mode aggr_mode = LOCK_AGGR_ADDR;
|
||||
|
||||
static bool needs_callstack(void)
|
||||
{
|
||||
return !list_empty(&callstack_filters);
|
||||
}
|
||||
|
||||
static struct thread_stat *thread_stat_find(u32 tid)
|
||||
{
|
||||
struct rb_node *node;
|
||||
@@ -477,93 +457,6 @@ static struct lock_stat *pop_from_result(void)
|
||||
return container_of(node, struct lock_stat, rb);
|
||||
}
|
||||
|
||||
struct lock_stat *lock_stat_find(u64 addr)
|
||||
{
|
||||
struct hlist_head *entry = lockhashentry(addr);
|
||||
struct lock_stat *ret;
|
||||
|
||||
hlist_for_each_entry(ret, entry, hash_entry) {
|
||||
if (ret->addr == addr)
|
||||
return ret;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct lock_stat *lock_stat_findnew(u64 addr, const char *name, int flags)
|
||||
{
|
||||
struct hlist_head *entry = lockhashentry(addr);
|
||||
struct lock_stat *ret, *new;
|
||||
|
||||
hlist_for_each_entry(ret, entry, hash_entry) {
|
||||
if (ret->addr == addr)
|
||||
return ret;
|
||||
}
|
||||
|
||||
new = zalloc(sizeof(struct lock_stat));
|
||||
if (!new)
|
||||
goto alloc_failed;
|
||||
|
||||
new->addr = addr;
|
||||
new->name = strdup(name);
|
||||
if (!new->name) {
|
||||
free(new);
|
||||
goto alloc_failed;
|
||||
}
|
||||
|
||||
new->flags = flags;
|
||||
new->wait_time_min = ULLONG_MAX;
|
||||
|
||||
hlist_add_head(&new->hash_entry, entry);
|
||||
return new;
|
||||
|
||||
alloc_failed:
|
||||
pr_err("memory allocation failed\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool match_callstack_filter(struct machine *machine, u64 *callstack)
|
||||
{
|
||||
struct map *kmap;
|
||||
struct symbol *sym;
|
||||
u64 ip;
|
||||
const char *arch = perf_env__arch(machine->env);
|
||||
|
||||
if (list_empty(&callstack_filters))
|
||||
return true;
|
||||
|
||||
for (int i = 0; i < max_stack_depth; i++) {
|
||||
struct callstack_filter *filter;
|
||||
|
||||
/*
|
||||
* In powerpc, the callchain saved by kernel always includes
|
||||
* first three entries as the NIP (next instruction pointer),
|
||||
* LR (link register), and the contents of LR save area in the
|
||||
* second stack frame. In certain scenarios its possible to have
|
||||
* invalid kernel instruction addresses in either LR or the second
|
||||
* stack frame's LR. In that case, kernel will store that address as
|
||||
* zero.
|
||||
*
|
||||
* The below check will continue to look into callstack,
|
||||
* incase first or second callstack index entry has 0
|
||||
* address for powerpc.
|
||||
*/
|
||||
if (!callstack || (!callstack[i] && (strcmp(arch, "powerpc") ||
|
||||
(i != 1 && i != 2))))
|
||||
break;
|
||||
|
||||
ip = callstack[i];
|
||||
sym = machine__find_kernel_symbol(machine, ip, &kmap);
|
||||
if (sym == NULL)
|
||||
continue;
|
||||
|
||||
list_for_each_entry(filter, &callstack_filters, list) {
|
||||
if (strstr(sym->name, filter->name))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
struct trace_lock_handler {
|
||||
/* it's used on CONFIG_LOCKDEP */
|
||||
int (*acquire_event)(struct evsel *evsel,
|
||||
@@ -1165,7 +1058,7 @@ static int report_lock_contention_begin_event(struct evsel *evsel,
|
||||
if (callstack == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
if (!match_callstack_filter(machine, callstack)) {
|
||||
if (!match_callstack_filter(machine, callstack, max_stack_depth)) {
|
||||
free(callstack);
|
||||
return 0;
|
||||
}
|
||||
@@ -2449,34 +2342,6 @@ static int parse_lock_addr(const struct option *opt __maybe_unused, const char *
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int parse_call_stack(const struct option *opt __maybe_unused, const char *str,
|
||||
int unset __maybe_unused)
|
||||
{
|
||||
char *s, *tmp, *tok;
|
||||
int ret = 0;
|
||||
|
||||
s = strdup(str);
|
||||
if (s == NULL)
|
||||
return -1;
|
||||
|
||||
for (tok = strtok_r(s, ", ", &tmp); tok; tok = strtok_r(NULL, ", ", &tmp)) {
|
||||
struct callstack_filter *entry;
|
||||
|
||||
entry = malloc(sizeof(*entry) + strlen(tok) + 1);
|
||||
if (entry == NULL) {
|
||||
pr_err("Memory allocation failure\n");
|
||||
free(s);
|
||||
return -1;
|
||||
}
|
||||
|
||||
strcpy(entry->name, tok);
|
||||
list_add_tail(&entry->list, &callstack_filters);
|
||||
}
|
||||
|
||||
free(s);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int parse_output(const struct option *opt __maybe_unused, const char *str,
|
||||
int unset __maybe_unused)
|
||||
{
|
||||
|
||||
@@ -122,6 +122,7 @@ perf-util-y += topdown.o
|
||||
perf-util-y += iostat.o
|
||||
perf-util-y += stream.o
|
||||
perf-util-y += kvm-stat.o
|
||||
perf-util-y += lock-contention.o
|
||||
perf-util-$(CONFIG_AUXTRACE) += auxtrace.o
|
||||
perf-util-y += intel-pt-decoder/
|
||||
perf-util-$(CONFIG_AUXTRACE) += intel-pt.o
|
||||
|
||||
@@ -458,7 +458,7 @@ int lock_contention_read(struct lock_contention *con)
|
||||
if (con->save_callstack) {
|
||||
bpf_map_lookup_elem(stack, &key.stack_id, stack_trace);
|
||||
|
||||
if (!match_callstack_filter(machine, stack_trace)) {
|
||||
if (!match_callstack_filter(machine, stack_trace, con->max_stack)) {
|
||||
con->nr_filtered += data.count;
|
||||
goto next;
|
||||
}
|
||||
|
||||
143
tools/perf/util/lock-contention.c
Normal file
143
tools/perf/util/lock-contention.c
Normal file
@@ -0,0 +1,143 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "debug.h"
|
||||
#include "env.h"
|
||||
#include "lock-contention.h"
|
||||
#include "machine.h"
|
||||
#include "symbol.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <linux/hash.h>
|
||||
#include <linux/zalloc.h>
|
||||
|
||||
#define __lockhashfn(key) hash_long((unsigned long)key, LOCKHASH_BITS)
|
||||
#define lockhashentry(key) (lockhash_table + __lockhashfn((key)))
|
||||
|
||||
struct callstack_filter {
|
||||
struct list_head list;
|
||||
char name[];
|
||||
};
|
||||
|
||||
static LIST_HEAD(callstack_filters);
|
||||
struct hlist_head *lockhash_table;
|
||||
|
||||
int parse_call_stack(const struct option *opt __maybe_unused, const char *str,
|
||||
int unset __maybe_unused)
|
||||
{
|
||||
char *s, *tmp, *tok;
|
||||
int ret = 0;
|
||||
|
||||
s = strdup(str);
|
||||
if (s == NULL)
|
||||
return -1;
|
||||
|
||||
for (tok = strtok_r(s, ", ", &tmp); tok; tok = strtok_r(NULL, ", ", &tmp)) {
|
||||
struct callstack_filter *entry;
|
||||
|
||||
entry = malloc(sizeof(*entry) + strlen(tok) + 1);
|
||||
if (entry == NULL) {
|
||||
pr_err("Memory allocation failure\n");
|
||||
free(s);
|
||||
return -1;
|
||||
}
|
||||
|
||||
strcpy(entry->name, tok);
|
||||
list_add_tail(&entry->list, &callstack_filters);
|
||||
}
|
||||
|
||||
free(s);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool needs_callstack(void)
|
||||
{
|
||||
return !list_empty(&callstack_filters);
|
||||
}
|
||||
|
||||
struct lock_stat *lock_stat_find(u64 addr)
|
||||
{
|
||||
struct hlist_head *entry = lockhashentry(addr);
|
||||
struct lock_stat *ret;
|
||||
|
||||
hlist_for_each_entry(ret, entry, hash_entry) {
|
||||
if (ret->addr == addr)
|
||||
return ret;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct lock_stat *lock_stat_findnew(u64 addr, const char *name, int flags)
|
||||
{
|
||||
struct hlist_head *entry = lockhashentry(addr);
|
||||
struct lock_stat *ret, *new;
|
||||
|
||||
hlist_for_each_entry(ret, entry, hash_entry) {
|
||||
if (ret->addr == addr)
|
||||
return ret;
|
||||
}
|
||||
|
||||
new = zalloc(sizeof(struct lock_stat));
|
||||
if (!new)
|
||||
goto alloc_failed;
|
||||
|
||||
new->addr = addr;
|
||||
new->name = strdup(name);
|
||||
if (!new->name) {
|
||||
free(new);
|
||||
goto alloc_failed;
|
||||
}
|
||||
|
||||
new->flags = flags;
|
||||
new->wait_time_min = ULLONG_MAX;
|
||||
|
||||
hlist_add_head(&new->hash_entry, entry);
|
||||
return new;
|
||||
|
||||
alloc_failed:
|
||||
pr_err("memory allocation failed\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool match_callstack_filter(struct machine *machine, u64 *callstack, int max_stack_depth)
|
||||
{
|
||||
struct map *kmap;
|
||||
struct symbol *sym;
|
||||
u64 ip;
|
||||
const char *arch = perf_env__arch(machine->env);
|
||||
|
||||
if (list_empty(&callstack_filters))
|
||||
return true;
|
||||
|
||||
for (int i = 0; i < max_stack_depth; i++) {
|
||||
struct callstack_filter *filter;
|
||||
|
||||
/*
|
||||
* In powerpc, the callchain saved by kernel always includes
|
||||
* first three entries as the NIP (next instruction pointer),
|
||||
* LR (link register), and the contents of LR save area in the
|
||||
* second stack frame. In certain scenarios its possible to have
|
||||
* invalid kernel instruction addresses in either LR or the second
|
||||
* stack frame's LR. In that case, kernel will store that address as
|
||||
* zero.
|
||||
*
|
||||
* The below check will continue to look into callstack,
|
||||
* incase first or second callstack index entry has 0
|
||||
* address for powerpc.
|
||||
*/
|
||||
if (!callstack || (!callstack[i] && (strcmp(arch, "powerpc") ||
|
||||
(i != 1 && i != 2))))
|
||||
break;
|
||||
|
||||
ip = callstack[i];
|
||||
sym = machine__find_kernel_symbol(machine, ip, &kmap);
|
||||
if (sym == NULL)
|
||||
continue;
|
||||
|
||||
list_for_each_entry(filter, &callstack_filters, list) {
|
||||
if (strstr(sym->name, filter->name))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -67,10 +67,11 @@ struct lock_stat {
|
||||
*/
|
||||
#define MAX_LOCK_DEPTH 48
|
||||
|
||||
struct lock_stat *lock_stat_find(u64 addr);
|
||||
struct lock_stat *lock_stat_findnew(u64 addr, const char *name, int flags);
|
||||
/* based on kernel/lockdep.c */
|
||||
#define LOCKHASH_BITS 12
|
||||
#define LOCKHASH_SIZE (1UL << LOCKHASH_BITS)
|
||||
|
||||
bool match_callstack_filter(struct machine *machine, u64 *callstack);
|
||||
extern struct hlist_head *lockhash_table;
|
||||
|
||||
/*
|
||||
* struct lock_seq_stat:
|
||||
@@ -148,8 +149,17 @@ struct lock_contention {
|
||||
bool save_callstack;
|
||||
};
|
||||
|
||||
#ifdef HAVE_BPF_SKEL
|
||||
struct option;
|
||||
int parse_call_stack(const struct option *opt, const char *str, int unset);
|
||||
bool needs_callstack(void);
|
||||
|
||||
struct lock_stat *lock_stat_find(u64 addr);
|
||||
struct lock_stat *lock_stat_findnew(u64 addr, const char *name, int flags);
|
||||
|
||||
bool match_callstack_filter(struct machine *machine, u64 *callstack, int max_stack_depth);
|
||||
|
||||
|
||||
#ifdef HAVE_BPF_SKEL
|
||||
int lock_contention_prepare(struct lock_contention *con);
|
||||
int lock_contention_start(void);
|
||||
int lock_contention_stop(void);
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#include "mmap.h"
|
||||
#include "util/kwork.h"
|
||||
#include "util/sample.h"
|
||||
#include "util/lock-contention.h"
|
||||
#include <internal/lib.h>
|
||||
#include "../builtin.h"
|
||||
|
||||
@@ -1311,22 +1310,6 @@ struct kwork_work *perf_kwork_add_work(struct perf_kwork *kwork __maybe_unused,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool match_callstack_filter(struct machine *machine __maybe_unused, u64 *callstack __maybe_unused)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
struct lock_stat *lock_stat_find(u64 addr __maybe_unused)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct lock_stat *lock_stat_findnew(u64 addr __maybe_unused, const char *name __maybe_unused,
|
||||
int flags __maybe_unused)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int cmd_inject(int argc __maybe_unused, const char *argv[] __maybe_unused)
|
||||
{
|
||||
return -1;
|
||||
|
||||
Reference in New Issue
Block a user