Skip to content

Commit 1b8f85d

Browse files
anakryikomhiramat
authored andcommitted
uprobes: prepare uprobe args buffer lazily
uprobe_cpu_buffer and corresponding logic to store uprobe args into it are used for uprobes/uretprobes that are created through tracefs or perf events. BPF is yet another user of uprobe/uretprobe infrastructure, but doesn't need uprobe_cpu_buffer and associated data. For BPF-only use cases this buffer handling and preparation is a pure overhead. At the same time, BPF-only uprobe/uretprobe usage is very common in practice. Also, for a lot of cases applications are very senstivie to performance overheads, as they might be tracing a very high frequency functions like malloc()/free(), so every bit of performance improvement matters. All that is to say that this uprobe_cpu_buffer preparation is an unnecessary overhead that each BPF user of uprobes/uretprobe has to pay. This patch is changing this by making uprobe_cpu_buffer preparation optional. It will happen only if either tracefs-based or perf event-based uprobe/uretprobe consumer is registered for given uprobe/uretprobe. For BPF-only use cases this step will be skipped. We used uprobe/uretprobe benchmark which is part of BPF selftests (see [0]) to estimate the improvements. We have 3 uprobe and 3 uretprobe scenarios, which vary an instruction that is replaced by uprobe: nop (fastest uprobe case), `push rbp` (typical case), and non-simulated `ret` instruction (slowest case). Benchmark thread is constantly calling user space function in a tight loop. User space function has attached BPF uprobe or uretprobe program doing nothing but atomic counter increments to count number of triggering calls. Benchmark emits throughput in millions of executions per second. BEFORE these changes ==================== uprobe-nop : 2.657 ± 0.024M/s uprobe-push : 2.499 ± 0.018M/s uprobe-ret : 1.100 ± 0.006M/s uretprobe-nop : 1.356 ± 0.004M/s uretprobe-push : 1.317 ± 0.019M/s uretprobe-ret : 0.785 ± 0.007M/s AFTER these changes =================== uprobe-nop : 2.732 ± 0.022M/s (+2.8%) uprobe-push : 2.621 ± 0.016M/s (+4.9%) uprobe-ret : 1.105 ± 0.007M/s (+0.5%) uretprobe-nop : 1.396 ± 0.007M/s (+2.9%) uretprobe-push : 1.347 ± 0.008M/s (+2.3%) uretprobe-ret : 0.800 ± 0.006M/s (+1.9) So the improvements on this particular machine seems to be between 2% and 5%. [0] https://github.com/torvalds/linux/blob/master/tools/testing/selftests/bpf/benchs/bench_trigger.c Reviewed-by: Jiri Olsa <[email protected]> Link: https://lore.kernel.org/all/[email protected]/ Signed-off-by: Andrii Nakryiko <[email protected]> Acked-by: Masami Hiramatsu (Google) <[email protected]> Signed-off-by: Masami Hiramatsu (Google) <[email protected]>
1 parent 3eaea21 commit 1b8f85d

File tree

1 file changed

+28
-21
lines changed

1 file changed

+28
-21
lines changed

kernel/trace/trace_uprobe.c

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -941,15 +941,21 @@ static struct uprobe_cpu_buffer *uprobe_buffer_get(void)
941941

942942
static void uprobe_buffer_put(struct uprobe_cpu_buffer *ucb)
943943
{
944+
if (!ucb)
945+
return;
944946
mutex_unlock(&ucb->mutex);
945947
}
946948

947949
static struct uprobe_cpu_buffer *prepare_uprobe_buffer(struct trace_uprobe *tu,
948-
struct pt_regs *regs)
950+
struct pt_regs *regs,
951+
struct uprobe_cpu_buffer **ucbp)
949952
{
950953
struct uprobe_cpu_buffer *ucb;
951954
int dsize, esize;
952955

956+
if (*ucbp)
957+
return *ucbp;
958+
953959
esize = SIZEOF_TRACE_ENTRY(is_ret_probe(tu));
954960
dsize = __get_data_size(&tu->tp, regs, NULL);
955961

@@ -958,22 +964,25 @@ static struct uprobe_cpu_buffer *prepare_uprobe_buffer(struct trace_uprobe *tu,
958964

959965
store_trace_args(ucb->buf, &tu->tp, regs, NULL, esize, dsize);
960966

967+
*ucbp = ucb;
961968
return ucb;
962969
}
963970

964971
static void __uprobe_trace_func(struct trace_uprobe *tu,
965972
unsigned long func, struct pt_regs *regs,
966-
struct uprobe_cpu_buffer *ucb,
973+
struct uprobe_cpu_buffer **ucbp,
967974
struct trace_event_file *trace_file)
968975
{
969976
struct uprobe_trace_entry_head *entry;
970977
struct trace_event_buffer fbuffer;
978+
struct uprobe_cpu_buffer *ucb;
971979
void *data;
972980
int size, esize;
973981
struct trace_event_call *call = trace_probe_event_call(&tu->tp);
974982

975983
WARN_ON(call != trace_file->event_call);
976984

985+
ucb = prepare_uprobe_buffer(tu, regs, ucbp);
977986
if (WARN_ON_ONCE(ucb->dsize > PAGE_SIZE))
978987
return;
979988

@@ -1002,7 +1011,7 @@ static void __uprobe_trace_func(struct trace_uprobe *tu,
10021011

10031012
/* uprobe handler */
10041013
static int uprobe_trace_func(struct trace_uprobe *tu, struct pt_regs *regs,
1005-
struct uprobe_cpu_buffer *ucb)
1014+
struct uprobe_cpu_buffer **ucbp)
10061015
{
10071016
struct event_file_link *link;
10081017

@@ -1011,21 +1020,21 @@ static int uprobe_trace_func(struct trace_uprobe *tu, struct pt_regs *regs,
10111020

10121021
rcu_read_lock();
10131022
trace_probe_for_each_link_rcu(link, &tu->tp)
1014-
__uprobe_trace_func(tu, 0, regs, ucb, link->file);
1023+
__uprobe_trace_func(tu, 0, regs, ucbp, link->file);
10151024
rcu_read_unlock();
10161025

10171026
return 0;
10181027
}
10191028

10201029
static void uretprobe_trace_func(struct trace_uprobe *tu, unsigned long func,
10211030
struct pt_regs *regs,
1022-
struct uprobe_cpu_buffer *ucb)
1031+
struct uprobe_cpu_buffer **ucbp)
10231032
{
10241033
struct event_file_link *link;
10251034

10261035
rcu_read_lock();
10271036
trace_probe_for_each_link_rcu(link, &tu->tp)
1028-
__uprobe_trace_func(tu, func, regs, ucb, link->file);
1037+
__uprobe_trace_func(tu, func, regs, ucbp, link->file);
10291038
rcu_read_unlock();
10301039
}
10311040

@@ -1353,10 +1362,11 @@ static bool uprobe_perf_filter(struct uprobe_consumer *uc,
13531362

13541363
static void __uprobe_perf_func(struct trace_uprobe *tu,
13551364
unsigned long func, struct pt_regs *regs,
1356-
struct uprobe_cpu_buffer *ucb)
1365+
struct uprobe_cpu_buffer **ucbp)
13571366
{
13581367
struct trace_event_call *call = trace_probe_event_call(&tu->tp);
13591368
struct uprobe_trace_entry_head *entry;
1369+
struct uprobe_cpu_buffer *ucb;
13601370
struct hlist_head *head;
13611371
void *data;
13621372
int size, esize;
@@ -1374,6 +1384,7 @@ static void __uprobe_perf_func(struct trace_uprobe *tu,
13741384

13751385
esize = SIZEOF_TRACE_ENTRY(is_ret_probe(tu));
13761386

1387+
ucb = prepare_uprobe_buffer(tu, regs, ucbp);
13771388
size = esize + ucb->dsize;
13781389
size = ALIGN(size + sizeof(u32), sizeof(u64)) - sizeof(u32);
13791390
if (WARN_ONCE(size > PERF_MAX_TRACE_SIZE, "profile buffer not large enough"))
@@ -1410,21 +1421,21 @@ static void __uprobe_perf_func(struct trace_uprobe *tu,
14101421

14111422
/* uprobe profile handler */
14121423
static int uprobe_perf_func(struct trace_uprobe *tu, struct pt_regs *regs,
1413-
struct uprobe_cpu_buffer *ucb)
1424+
struct uprobe_cpu_buffer **ucbp)
14141425
{
14151426
if (!uprobe_perf_filter(&tu->consumer, 0, current->mm))
14161427
return UPROBE_HANDLER_REMOVE;
14171428

14181429
if (!is_ret_probe(tu))
1419-
__uprobe_perf_func(tu, 0, regs, ucb);
1430+
__uprobe_perf_func(tu, 0, regs, ucbp);
14201431
return 0;
14211432
}
14221433

14231434
static void uretprobe_perf_func(struct trace_uprobe *tu, unsigned long func,
14241435
struct pt_regs *regs,
1425-
struct uprobe_cpu_buffer *ucb)
1436+
struct uprobe_cpu_buffer **ucbp)
14261437
{
1427-
__uprobe_perf_func(tu, func, regs, ucb);
1438+
__uprobe_perf_func(tu, func, regs, ucbp);
14281439
}
14291440

14301441
int bpf_get_uprobe_info(const struct perf_event *event, u32 *fd_type,
@@ -1489,7 +1500,7 @@ static int uprobe_dispatcher(struct uprobe_consumer *con, struct pt_regs *regs)
14891500
{
14901501
struct trace_uprobe *tu;
14911502
struct uprobe_dispatch_data udd;
1492-
struct uprobe_cpu_buffer *ucb;
1503+
struct uprobe_cpu_buffer *ucb = NULL;
14931504
int ret = 0;
14941505

14951506
tu = container_of(con, struct trace_uprobe, consumer);
@@ -1503,14 +1514,12 @@ static int uprobe_dispatcher(struct uprobe_consumer *con, struct pt_regs *regs)
15031514
if (WARN_ON_ONCE(!uprobe_cpu_buffer))
15041515
return 0;
15051516

1506-
ucb = prepare_uprobe_buffer(tu, regs);
1507-
15081517
if (trace_probe_test_flag(&tu->tp, TP_FLAG_TRACE))
1509-
ret |= uprobe_trace_func(tu, regs, ucb);
1518+
ret |= uprobe_trace_func(tu, regs, &ucb);
15101519

15111520
#ifdef CONFIG_PERF_EVENTS
15121521
if (trace_probe_test_flag(&tu->tp, TP_FLAG_PROFILE))
1513-
ret |= uprobe_perf_func(tu, regs, ucb);
1522+
ret |= uprobe_perf_func(tu, regs, &ucb);
15141523
#endif
15151524
uprobe_buffer_put(ucb);
15161525
return ret;
@@ -1521,7 +1530,7 @@ static int uretprobe_dispatcher(struct uprobe_consumer *con,
15211530
{
15221531
struct trace_uprobe *tu;
15231532
struct uprobe_dispatch_data udd;
1524-
struct uprobe_cpu_buffer *ucb;
1533+
struct uprobe_cpu_buffer *ucb = NULL;
15251534

15261535
tu = container_of(con, struct trace_uprobe, consumer);
15271536

@@ -1533,14 +1542,12 @@ static int uretprobe_dispatcher(struct uprobe_consumer *con,
15331542
if (WARN_ON_ONCE(!uprobe_cpu_buffer))
15341543
return 0;
15351544

1536-
ucb = prepare_uprobe_buffer(tu, regs);
1537-
15381545
if (trace_probe_test_flag(&tu->tp, TP_FLAG_TRACE))
1539-
uretprobe_trace_func(tu, func, regs, ucb);
1546+
uretprobe_trace_func(tu, func, regs, &ucb);
15401547

15411548
#ifdef CONFIG_PERF_EVENTS
15421549
if (trace_probe_test_flag(&tu->tp, TP_FLAG_PROFILE))
1543-
uretprobe_perf_func(tu, func, regs, ucb);
1550+
uretprobe_perf_func(tu, func, regs, &ucb);
15441551
#endif
15451552
uprobe_buffer_put(ucb);
15461553
return 0;

0 commit comments

Comments
 (0)