Skip to content

Commit 3477f07

Browse files
namhyungacmel
authored andcommitted
perf lock contention: Add -o/--lock-owner option
When there're many lock contentions in the system, people sometimes want to know who caused the contention, IOW who's the owner of the locks. The -o/--lock-owner option tries to follow the lock owners for the contended mutexes and rwsems from BPF, and then attributes the contention time to the owner instead of the waiter. It's a best effort approach to get the owner info at the time of the contention and doesn't guarantee to have the precise tracking of owners if it's changing over time. Currently it only handles mutex and rwsem that have owner field in their struct and it basically points to a task_struct that owns the lock at the moment. Technically its type is atomic_long_t and it comes with some LSB bits used for other meanings. So it needs to clear them when casting it to a pointer to task_struct. Also the atomic_long_t is a typedef of the atomic 32 or 64 bit types depending on arch which is a wrapper struct for the counter value. I'm not aware of proper ways to access those kernel atomic types from BPF so I just read the internal counter value directly. Please let me know if there's a better way. When -o/--lock-owner option is used, it goes to the task aggregation mode like -t/--threads option does. However it cannot get the owner for other lock types like spinlock and sometimes even for mutex. $ sudo ./perf lock con -abo -- ./perf bench sched pipe # Running 'sched/pipe' benchmark: # Executed 1000000 pipe operations between two processes Total time: 4.766 [sec] 4.766540 usecs/op 209795 ops/sec contended total wait max wait avg wait pid owner 403 565.32 us 26.81 us 1.40 us -1 Unknown 4 27.99 us 8.57 us 7.00 us 1583145 sched-pipe 1 8.25 us 8.25 us 8.25 us 1583144 sched-pipe 1 2.03 us 2.03 us 2.03 us 5068 chrome As you can see, the owner is unknown for the most cases. But if we filter only for the mutex locks, it'd more likely get the onwers. $ sudo ./perf lock con -abo -Y mutex -- ./perf bench sched pipe # Running 'sched/pipe' benchmark: # Executed 1000000 pipe operations between two processes Total time: 4.910 [sec] 4.910435 usecs/op 203647 ops/sec contended total wait max wait avg wait pid owner 2 15.50 us 8.29 us 7.75 us 1582852 sched-pipe 7 7.20 us 2.47 us 1.03 us -1 Unknown 1 6.74 us 6.74 us 6.74 us 1582851 sched-pipe Signed-off-by: Namhyung Kim <[email protected]> Cc: Adrian Hunter <[email protected]> Cc: Boqun Feng <[email protected]> Cc: Davidlohr Bueso <[email protected]> Cc: Hao Luo <[email protected]> Cc: Ian Rogers <[email protected]> Cc: Ingo Molnar <[email protected]> Cc: Jiri Olsa <[email protected]> Cc: Peter Zijlstra <[email protected]> Cc: Song Liu <[email protected]> Cc: Waiman Long <[email protected]> Cc: Will Deacon <[email protected]> Cc: [email protected] Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Arnaldo Carvalho de Melo <[email protected]>
1 parent 55e3918 commit 3477f07

File tree

5 files changed

+102
-14
lines changed

5 files changed

+102
-14
lines changed

tools/perf/Documentation/perf-lock.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,11 @@ CONTENTION OPTIONS
172172
--lock-addr::
173173
Show lock contention stat by address
174174

175+
-o::
176+
--lock-owner::
177+
Show lock contention stat by owners. Implies --threads and
178+
requires --use-bpf.
179+
175180
-Y::
176181
--type-filter=<value>::
177182
Show lock contention only for given lock types (comma separated list).

tools/perf/builtin-lock.c

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ static struct rb_root thread_stats;
5858
static bool combine_locks;
5959
static bool show_thread_stats;
6060
static bool show_lock_addrs;
61+
static bool show_lock_owner;
6162
static bool use_bpf;
6263
static unsigned long bpf_map_entries = 10240;
6364
static int max_stack_depth = CONTENTION_STACK_DEPTH;
@@ -1616,7 +1617,8 @@ static void print_contention_result(struct lock_contention *con)
16161617

16171618
switch (aggr_mode) {
16181619
case LOCK_AGGR_TASK:
1619-
pr_info(" %10s %s\n\n", "pid", "comm");
1620+
pr_info(" %10s %s\n\n", "pid",
1621+
show_lock_owner ? "owner" : "comm");
16201622
break;
16211623
case LOCK_AGGR_CALLER:
16221624
pr_info(" %10s %s\n\n", "type", "caller");
@@ -1656,7 +1658,8 @@ static void print_contention_result(struct lock_contention *con)
16561658
case LOCK_AGGR_TASK:
16571659
pid = st->addr;
16581660
t = perf_session__findnew(session, pid);
1659-
pr_info(" %10d %s\n", pid, thread__comm_str(t));
1661+
pr_info(" %10d %s\n",
1662+
pid, pid == -1 ? "Unknown" : thread__comm_str(t));
16601663
break;
16611664
case LOCK_AGGR_ADDR:
16621665
pr_info(" %016llx %s\n", (unsigned long long)st->addr,
@@ -1768,6 +1771,37 @@ static void sighandler(int sig __maybe_unused)
17681771
{
17691772
}
17701773

1774+
static int check_lock_contention_options(const struct option *options,
1775+
const char * const *usage)
1776+
1777+
{
1778+
if (show_thread_stats && show_lock_addrs) {
1779+
pr_err("Cannot use thread and addr mode together\n");
1780+
parse_options_usage(usage, options, "threads", 0);
1781+
parse_options_usage(NULL, options, "lock-addr", 0);
1782+
return -1;
1783+
}
1784+
1785+
if (show_lock_owner && !use_bpf) {
1786+
pr_err("Lock owners are available only with BPF\n");
1787+
parse_options_usage(usage, options, "lock-owner", 0);
1788+
parse_options_usage(NULL, options, "use-bpf", 0);
1789+
return -1;
1790+
}
1791+
1792+
if (show_lock_owner && show_lock_addrs) {
1793+
pr_err("Cannot use owner and addr mode together\n");
1794+
parse_options_usage(usage, options, "lock-owner", 0);
1795+
parse_options_usage(NULL, options, "lock-addr", 0);
1796+
return -1;
1797+
}
1798+
1799+
if (show_lock_owner)
1800+
show_thread_stats = true;
1801+
1802+
return 0;
1803+
}
1804+
17711805
static int __cmd_contention(int argc, const char **argv)
17721806
{
17731807
int err = -EINVAL;
@@ -1793,6 +1827,7 @@ static int __cmd_contention(int argc, const char **argv)
17931827
.stack_skip = stack_skip,
17941828
.filters = &filters,
17951829
.save_callstack = needs_callstack(),
1830+
.owner = show_lock_owner,
17961831
};
17971832

17981833
session = perf_session__new(use_bpf ? NULL : &data, &eops);
@@ -2272,6 +2307,7 @@ int cmd_lock(int argc, const char **argv)
22722307
"Filter specific address/symbol of locks", parse_lock_addr),
22732308
OPT_CALLBACK('S', "callstack-filter", NULL, "NAMES",
22742309
"Filter specific function in the callstack", parse_call_stack),
2310+
OPT_BOOLEAN('o', "lock-owner", &show_lock_owner, "show lock owners instead of waiters"),
22752311
OPT_PARENT(lock_options)
22762312
};
22772313

@@ -2342,14 +2378,9 @@ int cmd_lock(int argc, const char **argv)
23422378
contention_usage, 0);
23432379
}
23442380

2345-
if (show_thread_stats && show_lock_addrs) {
2346-
pr_err("Cannot use thread and addr mode together\n");
2347-
parse_options_usage(contention_usage, contention_options,
2348-
"threads", 0);
2349-
parse_options_usage(NULL, contention_options,
2350-
"lock-addr", 0);
2381+
if (check_lock_contention_options(contention_options,
2382+
contention_usage) < 0)
23512383
return -1;
2352-
}
23532384

23542385
rc = __cmd_contention(argc, argv);
23552386
} else {

tools/perf/util/bpf_lock_contention.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ int lock_contention_prepare(struct lock_contention *con)
149149
skel->bss->stack_skip = con->stack_skip;
150150
skel->bss->aggr_mode = con->aggr_mode;
151151
skel->bss->needs_callstack = con->save_callstack;
152+
skel->bss->lock_owner = con->owner;
152153

153154
lock_contention_bpf__attach(skel);
154155
return 0;

tools/perf/util/bpf_skel/lock_contention.bpf.c

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@
1010
/* default buffer size */
1111
#define MAX_ENTRIES 10240
1212

13+
/* lock contention flags from include/trace/events/lock.h */
14+
#define LCB_F_SPIN (1U << 0)
15+
#define LCB_F_READ (1U << 1)
16+
#define LCB_F_WRITE (1U << 2)
17+
#define LCB_F_RT (1U << 3)
18+
#define LCB_F_PERCPU (1U << 4)
19+
#define LCB_F_MUTEX (1U << 5)
20+
1321
struct tstamp_data {
1422
__u64 timestamp;
1523
__u64 lock;
@@ -84,6 +92,7 @@ int has_type;
8492
int has_addr;
8593
int needs_callstack;
8694
int stack_skip;
95+
int lock_owner;
8796

8897
/* determine the key of lock stat */
8998
int aggr_mode;
@@ -132,17 +141,24 @@ static inline int can_record(u64 *ctx)
132141
return 1;
133142
}
134143

135-
static inline void update_task_data(__u32 pid)
144+
static inline int update_task_data(struct task_struct *task)
136145
{
137146
struct contention_task_data *p;
147+
int pid, err;
148+
149+
err = bpf_core_read(&pid, sizeof(pid), &task->pid);
150+
if (err)
151+
return -1;
138152

139153
p = bpf_map_lookup_elem(&task_data, &pid);
140154
if (p == NULL) {
141-
struct contention_task_data data;
155+
struct contention_task_data data = {};
142156

143-
bpf_get_current_comm(data.comm, sizeof(data.comm));
157+
BPF_CORE_READ_STR_INTO(&data.comm, task, comm);
144158
bpf_map_update_elem(&task_data, &pid, &data, BPF_NOEXIST);
145159
}
160+
161+
return 0;
146162
}
147163

148164
SEC("tp_btf/contention_begin")
@@ -179,6 +195,38 @@ int contention_begin(u64 *ctx)
179195
BPF_F_FAST_STACK_CMP | stack_skip);
180196
if (pelem->stack_id < 0)
181197
lost++;
198+
} else if (aggr_mode == LOCK_AGGR_TASK) {
199+
struct task_struct *task;
200+
201+
if (lock_owner) {
202+
if (pelem->flags & LCB_F_MUTEX) {
203+
struct mutex *lock = (void *)pelem->lock;
204+
unsigned long owner = BPF_CORE_READ(lock, owner.counter);
205+
206+
task = (void *)(owner & ~7UL);
207+
} else if (pelem->flags == LCB_F_READ || pelem->flags == LCB_F_WRITE) {
208+
struct rw_semaphore *lock = (void *)pelem->lock;
209+
unsigned long owner = BPF_CORE_READ(lock, owner.counter);
210+
211+
task = (void *)(owner & ~7UL);
212+
} else {
213+
task = NULL;
214+
}
215+
216+
/* The flags is not used anymore. Pass the owner pid. */
217+
if (task)
218+
pelem->flags = BPF_CORE_READ(task, pid);
219+
else
220+
pelem->flags = -1U;
221+
222+
} else {
223+
task = bpf_get_current_task_btf();
224+
}
225+
226+
if (task) {
227+
if (update_task_data(task) < 0 && lock_owner)
228+
pelem->flags = -1U;
229+
}
182230
}
183231

184232
return 0;
@@ -208,8 +256,10 @@ int contention_end(u64 *ctx)
208256
key.stack_id = pelem->stack_id;
209257
break;
210258
case LOCK_AGGR_TASK:
211-
key.pid = pid;
212-
update_task_data(pid);
259+
if (lock_owner)
260+
key.pid = pelem->flags;
261+
else
262+
key.pid = pid;
213263
if (needs_callstack)
214264
key.stack_id = pelem->stack_id;
215265
break;

tools/perf/util/lock-contention.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ struct lock_contention {
133133
int max_stack;
134134
int stack_skip;
135135
int aggr_mode;
136+
int owner;
136137
bool save_callstack;
137138
};
138139

0 commit comments

Comments
 (0)