perf: Support the deferred unwinding infrastructure
Subsystems (5)
v15 cover perf: Support the deferred unwinding infrastructure
This patch is based off of: https://lore.kernel.org/linux-trace-kernel/[email protected]
And requires these patches to be enabled: https://lore.kernel.org/linux-trace-kernel/[email protected]/
To run this series, you can checkout this repo that has this series as well as the above:
git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace.git unwind/perf-test
This series implements the perf interface to use deferred user space stack tracing.
The first 4 patches implement the kernel side of perf to do the deferred stack tracing, and the last 4 patches implement the perf user space side to read this new interface.
Patch 1 adds a new API interface to the user unwinder logic to allow perf to get the current context cookie for it's task event tracing. Perf's task event tracing maps a single task per perf event buffer and it follows the task around, so it only needs to implement its own task_work to do the deferred stack trace. Because it can still suffer not knowing which user stack trace belongs to which kernel stack due to dropped events, having the cookie to create a unique identifier for each user space stack trace to know which kernel stack to append it to is useful.
Patch 2 adds the per task deferred stack traces to perf. It adds a new event type called PERF_RECORD_CALLCHAIN_DEFERRED that is recorded when a task is about to go back to user space and happens in a location that pages may be faulted in. It also adds a new callchain context called PERF_CONTEXT_USER_DEFERRED that is used as a place holder in a kernel callchain to append the deferred user space stack trace to.
Patch 3 adds the user stack trace context cookie in the kernel callchain right after the PERF_CONTEXT_USER_DEFERRED context so that the user space side can map the request to the deferred user space stack trace.
Patch 4 adds support for the per CPU perf events that will allow the kernel to associate each of the per CPU perf event buffers to a single application. This is needed so that when a request for a deferred stack trace happens on a task that then migrates to another CPU, it will know which CPU buffer to use to record the stack trace on. It is possible to have more than one perf user tool running and a request made by one perf tool should have the deferred trace go to the same perf tool's perf CPU event buffer. A global list of all the descriptors representing each perf tool that is using deferred stack tracing is created to manage this.
The last 4 patches implement the perf user space tooling side of this.
Changes since v14: https://lore.kernel.org/linux-trace-kernel/[email protected]/
- Moved the clean up patches into their own series (mentioned at the beginning)
- Added unwind_user_get_cookie() API to allow the task events to add cookies to differentiate which user stack belongs to which kernel stack in the event of dropped events.
- Save the cookie in the kernel callchain right after the PERF_CONTEXT_USER_DEFERRED
- Have the perf user space tooling match the cookies as well as the TID between the request and the user stack recording, to know which kernel stack gets the user space trace appended to it based on its context cookie.
Josh Poimboeuf (1):
perf: Support deferred user callchains
Namhyung Kim (4):
perf tools: Minimal CALLCHAIN_DEFERRED support perf record: Enable defer_callchain for user callchains perf script: Display PERF_RECORD_CALLCHAIN_DEFERRED perf tools: Merge deferred user callchains
Steven Rostedt (3):
unwind deferred: Add unwind_user_get_cookie() API
perf: Have the deferred request record the user context cookie
perf: Support deferred user callchains for per CPU events
----
include/linux/perf_event.h | 11 +- include/linux/unwind_deferred.h | 5 + include/uapi/linux/perf_event.h | 25 +- kernel/bpf/stackmap.c | 4 +- kernel/events/callchain.c | 14 +- kernel/events/core.c | 421 +++++++++++++++++++++++++++++- kernel/unwind/deferred.c | 21 ++ tools/include/uapi/linux/perf_event.h | 25 +- tools/lib/perf/include/perf/event.h | 8 + tools/perf/Documentation/perf-script.txt | 5 + tools/perf/builtin-script.c | 92 +++++++ tools/perf/util/callchain.c | 24 ++ tools/perf/util/callchain.h | 3 + tools/perf/util/event.c | 1 + tools/perf/util/evlist.c | 1 + tools/perf/util/evlist.h | 1 + tools/perf/util/evsel.c | 42 +++ tools/perf/util/evsel.h | 1 + tools/perf/util/machine.c | 1 + tools/perf/util/perf_event_attr_fprintf.c | 1 + tools/perf/util/sample.h | 4 +- tools/perf/util/session.c | 80 ++++++ tools/perf/util/tool.c | 2 + tools/perf/util/tool.h | 4 +- 24 files changed, 786 insertions(+), 10 deletions(-)
PATCH v15 1/8 unwind deferred: Add unwind_user_get_cookie() API
From: Steven Rostedt <[email protected]>
Add the function unwind_user_get_cookie() API that allows a subsystem to retrieve the current context cookie. This can be used by perf to attach a cookie to its task deferred unwinding code that doesn't use the deferred unwind logic.
--- include/linux/unwind_deferred.h | 5 +++++ kernel/unwind/deferred.c | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+)
include/linux/unwind_deferred.h +5 -0
@@ -41,6 +41,8 @@ void unwind_deferred_cancel(struct unwind_work *work);void unwind_deferred_task_exit (struct task_struct *task); +u64 unwind_user_get_cookie (void ); + static __always_inline void unwind_reset_info(void) {struct unwind_task_info *info = ¤t->unwind_info;
@@ -76,6 +78,9 @@ static inline void unwind_deferred_cancel(struct unwind_work *work) {}static inline void unwind_deferred_task_exit (struct task_struct *task) {}static inline void unwind_reset_info (void ) {} +/* Must be non-zero */ +static inline u64 unwind_user_get_cookie (void ) {return (u64)-1 ; } +#endif /* !CONFIG_UNWIND_USER */ #endif /* _LINUX_UNWIND_USER_DEFERRED_H */
kernel/unwind/deferred.c +21 -0
@@ -94,6 +94,27 @@ static u64 get_cookie(struct unwind_task_info *info)return info->id.id; } +/** +* unwind_user_get_cookie - Get the current user context cookie +* +* This is used to get a unique context cookie for the current task. +* Every time a task enters the kernel it has a new context. If +* a subsystem needs to have a unique identifier for that context for +* the current task, it can call this function to retrieve a unique +* cookie for that task context. +* +* Returns: A unque identifier for the current task user context. +*/ +u64 unwind_user_get_cookie (void ) +{ +struct unwind_task_info *info = ¤t->unwind_info; + +guard (irqsave)(); +/* Make sure to clear the info->id.id when exiting the kernel */ +set_bit (UNWIND_USED_BIT , &info->unwind_mask); +return get_cookie (info); +} +/** * unwind_user_faultable - Produce a user stacktrace in faultable context * @trace: The descriptor that will store the user stacktrace
Signature · 1 line
2.50.1
PATCH v15 2/8 perf: Support deferred user callchains
In reply to Steven RostedtFrom: Josh Poimboeuf <[email protected]>
If the user fault unwind is available (the one that will be used for sframes), have perf be able to utilize it. Currently all user stack traces are done at the request site. This mostly happens in interrupt or NMI context where user space is only accessible if it is currently present in memory. It is possible that the user stack was swapped out and is not present, but mostly the use of sframes will require faulting in user pages which will not be possible from interrupt context. Instead, add a frame work that will delay the reading of the user space stack until the task goes back to user space where faulting in pages is possible. This is also advantageous as the user space stack doesn't change while in the kernel, and this will also remove duplicate entries of user space stacks for a long running system call being profiled.
A new perf context is created called PERF_CONTEXT_USER_DEFERRED. It is added to the kernel callchain, usually when an interrupt or NMI is triggered (but can be added to any callchain). When a deferred unwind is required, a new task_work is triggered (pending_unwind_work) on the task. The callchain that is done immediately for the kernel is appended with the PERF_CONTEXT_USER_DEFERRED.
When the task exits to user space and the task_work handler is triggered, it will execute the user stack unwinding and record the user stack trace. This user stack trace will go into a new perf type called PERF_RECORD_CALLCHAIN_DEFERRED. The perf user space will need to attach this stack trace to each of the previous kernel callchains for that task with the PERF_CONTEXT_USER_DEFERRED context in them.
As the struct unwind_stacktrace has its entries as "unsigned long", and it is used to copy directly into struct perf_callchain_entry which its "ip" field is defined as u64, currently only deferred callchains are allowed for 64bit architectures. This could change in the future if there is a demand for it for 32 bit architectures.
---
Changes since v14: https://lore.kernel.org/[email protected]
- Added "cookie" to struct perf_callchain_deferred_event to allow this commit to work with the parsing of the user space perf changes.
include/linux/perf_event.h | 7 +- include/uapi/linux/perf_event.h | 20 +++- kernel/bpf/stackmap.c | 4 +- kernel/events/callchain.c | 11 +- kernel/events/core.c | 156 +++++++++++++++++++++++++- tools/include/uapi/linux/perf_event.h | 20 +++- 6 files changed, 210 insertions(+), 8 deletions(-)
include/linux/perf_event.h +6 -1
@@ -53,6 +53,7 @@#include <linux/security.h> #include <linux/static_call.h> #include <linux/lockdep.h> +#include <linux/unwind_deferred.h> #include <asm/local.h>
@@ -880,6 +881,10 @@ struct perf_event {struct callback_head pending_task;unsigned int pending_work; +unsigned int pending_unwind_callback; +struct callback_head pending_unwind_work; +struct rcuwait pending_unwind_wait; +atomic_t event_limit;/* address range filters */
@@ -1720,7 +1725,7 @@ extern void perf_callchain_user(struct perf_callchain_entry_ctx *entry, struct pextern void perf_callchain_kernel (struct perf_callchain_entry_ctx *entry,struct pt_regs *regs);extern struct perf_callchain_entry *get_perf_callchain (struct pt_regs *regs,bool kernel,bool user, -u32 max_stack,bool crosstask,bool add_mark); +u32 max_stack,bool crosstask,bool add_mark,bool defer_user);extern int get_callchain_buffers (int max_stack);extern void put_callchain_buffers (void );extern struct perf_callchain_entry *get_callchain_entry (int *rctx);
include/uapi/linux/perf_event.h +19 -1
@@ -463,7 +463,8 @@ struct perf_event_attr { inherit_thread : 1, /* children only inherit if cloned with CLONE_THREAD */ remove_on_exec : 1, /* event is removed from task on exec */ sigtrap : 1, /* send synchronous SIGTRAP on event */ - __reserved_1 : 26; + defer_callchain: 1, /* generate PERF_RECORD_CALLCHAIN_DEFERRED records */ + __reserved_1 : 25;union {__u32 wakeup_events;/* wake up every n events */
@@ -1239,6 +1240,22 @@ enum perf_event_type {*/ PERF_RECORD_AUX_OUTPUT_HW_ID =21 , +/* +* This user callchain capture was deferred until shortly before +* returning to user space. Previous samples would have kernel +* callchains only and they need to be stitched with this to make full +* callchains. +* +* struct { +* struct perf_event_header header; +* u64 cookie; +* u64 nr; +* u64 ips[nr]; +* struct sample_id sample_id; +* }; +*/ +PERF_RECORD_CALLCHAIN_DEFERRED =22 , + PERF_RECORD_MAX, /* non-ABI */ };
@@ -1269,6 +1286,7 @@ enum perf_callchain_context {PERF_CONTEXT_HV = (__u64)-32 ,PERF_CONTEXT_KERNEL = (__u64)-128 ,PERF_CONTEXT_USER = (__u64)-512 , +PERF_CONTEXT_USER_DEFERRED = (__u64)-640 ,PERF_CONTEXT_GUEST = (__u64)-2048 , PERF_CONTEXT_GUEST_KERNEL = (__u64)-2176,
kernel/bpf/stackmap.c +2 -2
@@ -315,7 +315,7 @@ BPF_CALL_3(bpf_get_stackid, struct pt_regs *, regs, struct bpf_map *, map, max_depth = sysctl_perf_event_max_stack; trace =get_perf_callchain (regs, kernel, user, max_depth, - false, false); + false, false, false);if (unlikely (!trace))/* couldn't fetch the stack trace */
@@ -452,7 +452,7 @@ static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task, trace =get_callchain_entry_for_task (task, max_depth); else trace =get_perf_callchain (regs, kernel, user, max_depth, - crosstask, false); + crosstask, false, false);if (unlikely (!trace) || trace->nr < skip) {if (may_fault)
kernel/events/callchain.c +10 -1
@@ -218,7 +218,7 @@ static void fixup_uretprobe_trampoline_entries(struct perf_callchain_entry *entrstruct perf_callchain_entry *get_perf_callchain (struct pt_regs *regs,bool kernel,bool user, -u32 max_stack,bool crosstask,bool add_mark) +u32 max_stack,bool crosstask,bool add_mark,bool defer_user) {struct perf_callchain_entry *entry;struct perf_callchain_entry_ctx ctx;
@@ -251,6 +251,15 @@ get_perf_callchain(struct pt_regs *regs, bool kernel, bool user, regs =task_pt_regs (current); } +if (defer_user) { +/* +* Foretell the coming of PERF_RECORD_CALLCHAIN_DEFERRED +* which can be stitched to this one. +*/ +perf_callchain_store_context (&ctx,PERF_CONTEXT_USER_DEFERRED ); + goto exit_put; + } +if (add_mark)perf_callchain_store_context (&ctx,PERF_CONTEXT_USER );
kernel/events/core.c +154 -2
198 lines not on this page · raw message
tools/include/uapi/linux/perf_event.h +19 -1
@@ -463,7 +463,8 @@ struct perf_event_attr { inherit_thread : 1, /* children only inherit if cloned with CLONE_THREAD */ remove_on_exec : 1, /* event is removed from task on exec */ sigtrap : 1, /* send synchronous SIGTRAP on event */ - __reserved_1 : 26; + defer_callchain: 1, /* generate PERF_RECORD_CALLCHAIN_DEFERRED records */ + __reserved_1 : 25;union {__u32 wakeup_events;/* wake up every n events */
@@ -1239,6 +1240,22 @@ enum perf_event_type {*/ PERF_RECORD_AUX_OUTPUT_HW_ID =21 , +/* +* This user callchain capture was deferred until shortly before +* returning to user space. Previous samples would have kernel +* callchains only and they need to be stitched with this to make full +* callchains. +* +* struct { +* struct perf_event_header header; +* u64 cookie; +* u64 nr; +* u64 ips[nr]; +* struct sample_id sample_id; +* }; +*/ +PERF_RECORD_CALLCHAIN_DEFERRED =22 , + PERF_RECORD_MAX, /* non-ABI */ };
@@ -1269,6 +1286,7 @@ enum perf_callchain_context {PERF_CONTEXT_HV = (__u64)-32 ,PERF_CONTEXT_KERNEL = (__u64)-128 ,PERF_CONTEXT_USER = (__u64)-512 , +PERF_CONTEXT_USER_DEFERRED = (__u64)-640 ,PERF_CONTEXT_GUEST = (__u64)-2048 , PERF_CONTEXT_GUEST_KERNEL = (__u64)-2176,
Signature · 1 line
2.50.1
PATCH v15 3/8 perf: Have the deferred request record the user context cookie
In reply to Steven RostedtFrom: Steven Rostedt <[email protected]>
When a request to have a deferred unwind is made, have the cookie associated to the user context recorded in the event that represents that request. It is added after the PERF_CONTEXT_USER_DEFERRED in the callchain. That perf context is a marker of where to add the associated user space stack trace in the callchain. Adding the cookie after that marker will not affect the appending of the callchain as it will be overwritten by the user space stack in the perf tool.
The cookie will be used to match the cookie that is saved when the deferred callchain is recorded. The perf tool will be able to use the cooking saved at the request to know if the callchain that was recorded when the task goes back to user space is for that event. If there were dropped events after the request was made where it dropped the calltrace that happened when the task went back to user space and then came back into the kernel and a new request was dropped, but then the record started again and it recorded a new callchain going back to user space, this callchain would not be for the initial request. The cookie matching will prevent this scenario from happening.
The cookie prevents:
record kernel stack trace with PERF_CONTEXT_USER_DEFERRED
[ dropped events starts here ]
record user stack trace - DROPPED
[enters user space ]
[exits user space back to the kernel ]
record kernel stack trace with PERF_CONTEXT_USER_DEFERRED - DROPPED!
[ events stop being dropped here ]
record user stack trace
Without a differentiating "cookie" identifier, the user space tool will incorrectly attach the last recorded user stack trace to the first kernel stack trace with the PERF_CONTEXT_USER_DEFERRED, as using the TID is not enough to identify this situation.
--- include/linux/perf_event.h | 2 +- include/uapi/linux/perf_event.h | 5 +++++ kernel/bpf/stackmap.c | 4 ++-- kernel/events/callchain.c | 9 ++++++--- kernel/events/core.c | 11 +++++++---- tools/include/uapi/linux/perf_event.h | 5 +++++ 6 files changed, 26 insertions(+), 10 deletions(-)
include/linux/perf_event.h +1 -1
@@ -1725,7 +1725,7 @@ extern void perf_callchain_user(struct perf_callchain_entry_ctx *entry, struct pextern void perf_callchain_kernel (struct perf_callchain_entry_ctx *entry,struct pt_regs *regs);extern struct perf_callchain_entry *get_perf_callchain (struct pt_regs *regs,bool kernel,bool user, -u32 max_stack,bool crosstask,bool add_mark,bool defer_user); +u32 max_stack,bool crosstask,bool add_mark,u64 defer_cookie);extern int get_callchain_buffers (int max_stack);extern void put_callchain_buffers (void );extern struct perf_callchain_entry *get_callchain_entry (int *rctx);
include/uapi/linux/perf_event.h +5 -0
@@ -1282,6 +1282,11 @@ enum perf_bpf_event_type {#define PERF_MAX_STACK_DEPTH 127#define PERF_MAX_CONTEXTS_PER_STACK 8 +/* +* The PERF_CONTEXT_USER_DEFERRED has two items (context and cookie) +*/ +#define PERF_DEFERRED_ITEMS 2 +enum perf_callchain_context {PERF_CONTEXT_HV = (__u64)-32 ,PERF_CONTEXT_KERNEL = (__u64)-128 ,
kernel/bpf/stackmap.c +2 -2
@@ -315,7 +315,7 @@ BPF_CALL_3(bpf_get_stackid, struct pt_regs *, regs, struct bpf_map *, map, max_depth = sysctl_perf_event_max_stack; trace =get_perf_callchain (regs, kernel, user, max_depth, - false, false, false); + false, false,0 );if (unlikely (!trace))/* couldn't fetch the stack trace */
@@ -452,7 +452,7 @@ static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task, trace =get_callchain_entry_for_task (task, max_depth); else trace =get_perf_callchain (regs, kernel, user, max_depth, - crosstask, false, false); + crosstask, false,0 );if (unlikely (!trace) || trace->nr < skip) {if (may_fault)
kernel/events/callchain.c +6 -3
@@ -218,7 +218,7 @@ static void fixup_uretprobe_trampoline_entries(struct perf_callchain_entry *entrstruct perf_callchain_entry *get_perf_callchain (struct pt_regs *regs,bool kernel,bool user, -u32 max_stack,bool crosstask,bool add_mark,bool defer_user) +u32 max_stack,bool crosstask,bool add_mark,u64 defer_cookie) {struct perf_callchain_entry *entry;struct perf_callchain_entry_ctx ctx;
@@ -251,12 +251,15 @@ get_perf_callchain(struct pt_regs *regs, bool kernel, bool user, regs =task_pt_regs (current); } -if (defer_user) { +if (defer_cookie) {/* * Foretell the coming of PERF_RECORD_CALLCHAIN_DEFERRED -* which can be stitched to this one. +* which can be stitched to this one, and add +* the cookie after it (it will be cut off when the +* user stack is copied to the callchain). */ perf_callchain_store_context (&ctx,PERF_CONTEXT_USER_DEFERRED ); +perf_callchain_store_context (&ctx, defer_cookie); goto exit_put; }
kernel/events/core.c +7 -4
@@ -8290,7 +8290,7 @@ static struct perf_callchain_entry __empty_callchain = { .nr = 0, };* 0 : if it performed the queuing * < 0 : if it did not get queued. */ -static int deferred_request (struct perf_event *event) +static int deferred_request (struct perf_event *event,u64 *defer_cookie) {struct callback_head *work = &event->pending_unwind_work;int pending;
@@ -8306,6 +8306,8 @@ static int deferred_request(struct perf_event *event)guard (irqsave)(); + *defer_cookie =unwind_user_get_cookie (); +/* callback already pending? */ pending =READ_ONCE (event->pending_unwind_callback);if (pending)
@@ -8334,6 +8336,7 @@ perf_callchain(struct perf_event *event, struct pt_regs *regs)bool crosstask = event->ctx->task && event->ctx->task != current;const u32 max_stack = event->attr.sample_max_stack;struct perf_callchain_entry *callchain; +u64 defer_cookie =0 ;/* perf currently only supports deferred in 64bit */ bool defer_user =IS_ENABLED (CONFIG_UNWIND_USER ) && user && event->attr.defer_callchain;
@@ -8349,15 +8352,15 @@ perf_callchain(struct perf_event *event, struct pt_regs *regs)return &__empty_callchain;if (defer_user) { -int ret =deferred_request (event); +int ret =deferred_request (event, &defer_cookie);if (!ret)local_inc (&event->ctx->nr_no_switch_fast);else if (ret <0 ) - defer_user = false; + defer_cookie =0 ; } callchain =get_perf_callchain (regs, kernel, user, max_stack, - crosstask, true, defer_user); + crosstask, true, defer_cookie);return callchain ?: &__empty_callchain; }
tools/include/uapi/linux/perf_event.h +5 -0
@@ -1282,6 +1282,11 @@ enum perf_bpf_event_type {#define PERF_MAX_STACK_DEPTH 127#define PERF_MAX_CONTEXTS_PER_STACK 8 +/* +* The PERF_CONTEXT_USER_DEFERRED has two items (context and cookie) +*/ +#define PERF_DEFERRED_ITEMS 2 +enum perf_callchain_context {PERF_CONTEXT_HV = (__u64)-32 ,PERF_CONTEXT_KERNEL = (__u64)-128 ,
Signature · 1 line
2.50.1
PATCH v15 4/8 perf: Support deferred user callchains for per CPU events
In reply to Steven RostedtFrom: Steven Rostedt <[email protected]>
The deferred unwinder works fine for task events (events that trace only a specific task), as it can use a task_work from an interrupt or NMI and when the task goes back to user space it will call the event's callback to do the deferred unwinding.
But for per CPU events things are not so simple. When a per CPU event wants a deferred unwinding to occur, it cannot simply use a task_work as there's a many to many relationship. If the task migrates and another task is scheduled in where the per CPU event wants a deferred unwinding to occur on that task as well, and the task that migrated to another CPU has that CPU's event want to unwind it too, each CPU may need unwinding from more than one task, and each task may have requests from many CPUs.
The main issue is that from the kernel point of view, there's currently nothing that associates a per CPU event for one CPU to the per CPU events that cover the other CPUs for a given process. To the kernel, they are all just individual events buffers. This is problematic if a delayed request is made on one CPU and the task migrates to another CPU where the delayed user stack trace will be performed. The kernel needs to know which CPU buffer to add it to that belongs to the same process that initiated the deferred request.
To solve this, when a per CPU event is created that has defer_callchain attribute set, it will do a lookup from a global list (unwind_deferred_list), for a perf_unwind_deferred descriptor that has the id that matches the PID of the current task's group_leader. (The process ID for all the threads of a process)
If it is not found, then it will create one and add it to the global list. This descriptor contains an array of all possible CPUs, where each element is a perf_unwind_cpu descriptor.
The perf_unwind_cpu descriptor has a list of all the per CPU events that is tracing the matching CPU that corresponds to its index in the array, where the events belong to a task that has the same group_leader. It also has a processing bit and rcuwait to handle removal.
For each occupied perf_unwind_cpu descriptor in the array, the perf_deferred_unwind descriptor increments its nr_cpu_events. When a perf_unwind_cpu descriptor is empty, the nr_cpu_events is decremented. This is used to know when to free the perf_deferred_unwind descriptor, as when it becomes empty, it is no longer referenced.
Finally, the perf_deferred_unwind descriptor has an id that holds the PID of the group_leader for the tasks that the events were created by.
When a second (or more) per CPU event is created where the perf_deferred_unwind descriptor already exists, it just adds itself to the perf_unwind_cpu array of that descriptor. Updating the necessary counter. This is used to map different per CPU events to each other based on their group leader PID.
Each of these perf_deferred_unwind descriptors have a unwind_work that registers with the deferred unwind infrastructure via unwind_deferred_init(), where it also registers a callback to perf_event_deferred_cpu().
Now when a per CPU event requests a deferred unwinding, it calls unwind_deferred_request() with the associated perf_deferred_unwind descriptor. It is expected that the program that uses this has events on all CPUs, as the deferred trace may not be called on the CPU event that requested it. That is, the task may migrate and its user stack trace will be recorded on the CPU event of the CPU that it exits back to user space on.
--- include/linux/perf_event.h | 4 + kernel/events/core.c | 320 +++++++++++++++++++++++++++++++++---- 2 files changed, 295 insertions(+), 29 deletions(-)
include/linux/perf_event.h +4 -0
@@ -733,6 +733,7 @@ struct swevent_hlist {struct bpf_prog ;struct perf_cgroup ;struct perf_buffer ; +struct perf_unwind_deferred ;struct pmu_event_list {raw_spinlock_t lock;
@@ -885,6 +886,9 @@ struct perf_event {struct callback_head pending_unwind_work;struct rcuwait pending_unwind_wait; +struct perf_unwind_deferred *unwind_deferred; +struct list_head unwind_list; +atomic_t event_limit;/* address range filters */
kernel/events/core.c +291 -29
400 lines not on this page · raw message
Signature · 1 line
2.50.1
PATCH v15 5/8 perf tools: Minimal CALLCHAIN_DEFERRED support
In reply to Steven RostedtFrom: Namhyung Kim <[email protected]>
Add a new event type for deferred callchains and a new callback for the struct perf_tool. For now it doesn't actually handle the deferred callchains but it just marks the sample if it has the PERF_CONTEXT_ USER_DEFFERED in the callchain array.
At least, perf report can dump the raw data with this change. Actually this requires the next commit to enable attr.defer_callchain, but if you already have a data file, it'll show the following result.
$ perf report -D ... [email protected] [0x40]: event: 22 . . ... raw event: size 64 bytes . 0000: 16 00 00 00 02 00 40 00 02 00 00 00 00 00 00 00 ......@......... . 0010: 00 fe ff ff ff ff ff ff 4b d3 3f 25 45 7f 00 00 ........K.?%E... . 0020: 21 03 00 00 21 03 00 00 43 02 12 ab 05 00 00 00 !...!...C....... . 0030: 00 00 00 00 00 00 00 00 09 00 00 00 00 00 00 00 ................
0 24344920643 0x5fe0 [0x40]: PERF_RECORD_CALLCHAIN_DEFERRED(IP, 0x2): 801/801: 0 [300000001] ... FP chain: nr:2
..... 0: fffffffffffffe00
..... 1: 00007f45253fd34b
: unhandled!
---
Changes since v14: https://lore.kernel.org/[email protected]
- Updated to use PERF_DEFERRED_ITEMS to find PERF_CONTEXT_USER_DEFERRED
tools/lib/perf/include/perf/event.h | 8 ++++++++ tools/perf/util/event.c | 1 + tools/perf/util/evsel.c | 18 ++++++++++++++++++ tools/perf/util/machine.c | 1 + tools/perf/util/perf_event_attr_fprintf.c | 1 + tools/perf/util/sample.h | 4 +++- tools/perf/util/session.c | 18 ++++++++++++++++++ tools/perf/util/tool.c | 1 + tools/perf/util/tool.h | 3 ++- 9 files changed, 53 insertions(+), 2 deletions(-)
tools/lib/perf/include/perf/event.h +8 -0
@@ -151,6 +151,13 @@ struct perf_record_switch {__u32 next_prev_tid; }; +struct perf_record_callchain_deferred { +struct perf_event_header header; +__u64 cookie; +__u64 nr; +__u64 ips[]; +}; +struct perf_record_header_attr {struct perf_event_header header;struct perf_event_attr attr;
@@ -522,6 +529,7 @@ union perf_event {struct perf_record_read read;struct perf_record_throttle throttle;struct perf_record_sample sample; +struct perf_record_callchain_deferred callchain_deferred;struct perf_record_bpf_event bpf;struct perf_record_ksymbol ksymbol;struct perf_record_text_poke_event text_poke;
tools/perf/util/event.c +1 -0
@@ -61,6 +61,7 @@ static const char *perf_event__names[] = { [PERF_RECORD_CGROUP] = "CGROUP", [PERF_RECORD_TEXT_POKE] = "TEXT_POKE", [PERF_RECORD_AUX_OUTPUT_HW_ID] = "AUX_OUTPUT_HW_ID", + [PERF_RECORD_CALLCHAIN_DEFERRED] = "CALLCHAIN_DEFERRED", [PERF_RECORD_HEADER_ATTR] = "ATTR", [PERF_RECORD_HEADER_EVENT_TYPE] = "EVENT_TYPE", [PERF_RECORD_HEADER_TRACING_DATA] = "TRACING_DATA",
tools/perf/util/evsel.c +18 -0
@@ -3002,6 +3002,18 @@ int evsel__parse_sample(struct evsel *evsel, union perf_event *event, data->data_src =PERF_MEM_DATA_SRC_NONE ; data->vcpu =-1 ; +if (event->header.type ==PERF_RECORD_CALLCHAIN_DEFERRED ) { +const u64 max_callchain_nr =UINT64_MAX /sizeof (u64); + + data->callchain = (struct ip_callchain *)&event->callchain_deferred.nr; +if (data->callchain->nr > max_callchain_nr) +return -EFAULT ; + +if (evsel->core.attr.sample_id_all) +perf_evsel__parse_id_sample (evsel, event, data); +return 0 ; + } +if (event->header.type !=PERF_RECORD_SAMPLE ) {if (!evsel->core.attr.sample_id_all)return 0 ;
@@ -3132,6 +3144,12 @@ int evsel__parse_sample(struct evsel *evsel, union perf_event *event,if (data->callchain->nr > max_callchain_nr)return -EFAULT ; sz = data->callchain->nr *sizeof (u64); +if (evsel->core.attr.defer_callchain && + data->callchain->nr >=PERF_DEFERRED_ITEMS && + data->callchain->ips[data->callchain->nr -PERF_DEFERRED_ITEMS ] ==PERF_CONTEXT_USER_DEFERRED ) { + data->deferred_callchain = true; + data->deferred_cookie = data->callchain->ips[data->callchain->nr -1 ]; + }OVERFLOW_CHECK (array, sz, max_size); array = (void *)array + sz; }
tools/perf/util/machine.c +1 -0
@@ -2124,6 +2124,7 @@ static int add_callchain_ip(struct thread *thread, *cpumode =PERF_RECORD_MISC_KERNEL ;break ;case PERF_CONTEXT_USER : +case PERF_CONTEXT_USER_DEFERRED : *cpumode =PERF_RECORD_MISC_USER ;break ;default :
tools/perf/util/perf_event_attr_fprintf.c +1 -0
@@ -343,6 +343,7 @@ int perf_event_attr__fprintf(FILE *fp, struct perf_event_attr *attr,PRINT_ATTRf (inherit_thread, p_unsigned);PRINT_ATTRf (remove_on_exec, p_unsigned);PRINT_ATTRf (sigtrap, p_unsigned); +PRINT_ATTRf (defer_callchain, p_unsigned);PRINT_ATTRn ("{ wakeup_events, wakeup_watermark }" , wakeup_events, p_unsigned, false);PRINT_ATTRf (bp_type, p_unsigned);
tools/perf/util/sample.h +3 -1
@@ -106,7 +106,9 @@ struct perf_sample {u16 ins_lat;/** @weight3: On x86 holds retire_lat, on powerpc holds p_stage_cyc. */ u16 weight3; -bool no_hw_idx;/* No hw_idx collected in branch_stack */ +bool no_hw_idx;/* No hw_idx collected in branch_stack */ +bool deferred_callchain;/* Has deferred user callchains */ +u64 deferred_cookie;char insn[MAX_INSN ];void *raw_data;struct ip_callchain *callchain;
tools/perf/util/session.c +18 -0
@@ -720,6 +720,7 @@ static perf_event__swap_op perf_event__swap_ops[] = { [PERF_RECORD_CGROUP] = perf_event__cgroup_swap, [PERF_RECORD_TEXT_POKE] = perf_event__text_poke_swap, [PERF_RECORD_AUX_OUTPUT_HW_ID] = perf_event__all64_swap, + [PERF_RECORD_CALLCHAIN_DEFERRED] = perf_event__all64_swap, [PERF_RECORD_HEADER_ATTR] = perf_event__hdr_attr_swap, [PERF_RECORD_HEADER_EVENT_TYPE] = perf_event__event_type_swap, [PERF_RECORD_HEADER_TRACING_DATA] = perf_event__tracing_data_swap,
@@ -1123,6 +1124,20 @@ static void dump_sample(struct evsel *evsel, union perf_event *event,sample_read__printf (sample, evsel->core.attr.read_format); } +static void dump_deferred_callchain (struct evsel *evsel,union perf_event *event, +struct perf_sample *sample) +{ +if (!dump_trace) +return ; + +printf ("(IP, 0x%x): %d/%d: %#" PRIx64" [%llx]\n" , + event->header.misc, sample->pid, sample->tid, sample->ip, + event->callchain_deferred.cookie); + +if (evsel__has_callchain (evsel)) +callchain__printf (evsel, sample); +} +static void dump_read (struct evsel *evsel,union perf_event *event) {struct perf_record_read *read_event = &event->read;
@@ -1353,6 +1368,9 @@ static int machines__deliver_event(struct machines *machines,return tool->text_poke (tool, event, sample, machine);case PERF_RECORD_AUX_OUTPUT_HW_ID :return tool->aux_output_hw_id (tool, event, sample, machine); +case PERF_RECORD_CALLCHAIN_DEFERRED : +dump_deferred_callchain (evsel, event, sample); +return tool->callchain_deferred (tool, event, sample, evsel, machine);default : ++evlist->stats.nr_unknown_events;return -1 ;
tools/perf/util/tool.c +1 -0
@@ -279,6 +279,7 @@ void perf_tool__init(struct perf_tool *tool, bool ordered_events) tool->read = process_event_sample_stub; tool->throttle = process_event_stub; tool->unthrottle = process_event_stub; + tool->callchain_deferred = process_event_sample_stub; tool->attr = process_event_synth_attr_stub; tool->event_update = process_event_synth_event_update_stub; tool->tracing_data = process_event_synth_tracing_data_stub;
tools/perf/util/tool.h +2 -1
@@ -42,7 +42,8 @@ enum show_feature_header {struct perf_tool {event_sample sample, - read; + read, + callchain_deferred;event_op mmap, mmap2, comm,
Signature · 1 line
2.50.1
PATCH v15 6/8 perf record: Enable defer_callchain for user callchains
In reply to Steven RostedtFrom: Namhyung Kim <[email protected]>
And add the missing feature detection logic to clear the flag on old kernels.
$ perf record -g -vv true
...
------------------------------------------------------------
perf_event_attr:
type 0 (PERF_TYPE_HARDWARE)
size 136
config 0 (PERF_COUNT_HW_CPU_CYCLES)
{ sample_period, sample_freq } 4000
sample_type IP|TID|TIME|CALLCHAIN|PERIOD
read_format ID|LOST
disabled 1
inherit 1
mmap 1
comm 1
freq 1
enable_on_exec 1
task 1
sample_id_all 1
mmap2 1
comm_exec 1
ksymbol 1
bpf_event 1
defer_callchain 1
------------------------------------------------------------
sys_perf_event_open: pid 162755 cpu 0 group_fd -1 flags 0x8
sys_perf_event_open failed, error -22
switching off deferred callchain support--- tools/perf/util/evsel.c | 24 ++++++++++++++++++++++++ tools/perf/util/evsel.h | 1 + 2 files changed, 25 insertions(+)
tools/perf/util/evsel.c +24 -0
@@ -1061,6 +1061,14 @@ static void __evsel__config_callchain(struct evsel *evsel, struct record_opts *o } } +if (param->record_mode ==CALLCHAIN_FP && !attr->exclude_callchain_user) { +/* +* Enable deferred callchains optimistically. It'll be switched +* off later if the kernel doesn't support it. +*/ + attr->defer_callchain =1 ; + } +if (function) {pr_info ("Disabling user space callchains for function trace event.\n" ); attr->exclude_callchain_user =1 ;
@@ -2121,6 +2129,8 @@ static int __evsel__prepare_open(struct evsel *evsel, struct perf_cpu_map *cpus,static void evsel__disable_missing_features (struct evsel *evsel) { +if (perf_missing_features.defer_callchain) + evsel->core.attr.defer_callchain =0 ;if (perf_missing_features.inherit_sample_read && evsel->core.attr.inherit && (evsel->core.attr.sample_type &PERF_SAMPLE_READ )) evsel->core.attr.inherit =0 ;
@@ -2395,6 +2405,15 @@ static bool evsel__detect_missing_features(struct evsel *evsel, struct perf_cpu/* Please add new feature detection here. */ + attr.defer_callchain = true; + attr.sample_type =PERF_SAMPLE_CALLCHAIN ; +if (has_attr_feature (&attr,/*flags=*/ 0 )) + goto found; + perf_missing_features.defer_callchain = true; +pr_debug2 ("switching off deferred callchain support\n" ); + attr.defer_callchain = false; + attr.sample_type =0 ; + attr.inherit = true; attr.sample_type =PERF_SAMPLE_READ ;if (has_attr_feature (&attr,/*flags=*/ 0 ))
@@ -2506,6 +2525,11 @@ static bool evsel__detect_missing_features(struct evsel *evsel, struct perf_cpu errno = old_errno; check: +if (evsel->core.attr.defer_callchain && + evsel->core.attr.sample_type &PERF_SAMPLE_CALLCHAIN && + perf_missing_features.defer_callchain) +return true; +if (evsel->core.attr.inherit && (evsel->core.attr.sample_type &PERF_SAMPLE_READ ) && perf_missing_features.inherit_sample_read)
tools/perf/util/evsel.h +1 -0
@@ -221,6 +221,7 @@ struct perf_missing_features {bool branch_counters;bool aux_action;bool inherit_sample_read; +bool defer_callchain; };extern struct perf_missing_features perf_missing_features;
Signature · 1 line
2.50.1
PATCH v15 7/8 perf script: Display PERF_RECORD_CALLCHAIN_DEFERRED
In reply to Steven RostedtFrom: Namhyung Kim <[email protected]>
Handle the deferred callchains in the script output.
$ perf script
perf 801 [000] 18.031793: 1 cycles:P:
ffffffff91a14c36 __intel_pmu_enable_all.isra.0+0x56 ([kernel.kallsyms])
ffffffff91d373e9 perf_ctx_enable+0x39 ([kernel.kallsyms])
ffffffff91d36af7 event_function+0xd7 ([kernel.kallsyms])
ffffffff91d34222 remote_function+0x42 ([kernel.kallsyms])
ffffffff91c1ebe1 generic_exec_single+0x61 ([kernel.kallsyms])
ffffffff91c1edac smp_call_function_single+0xec ([kernel.kallsyms])
ffffffff91d37a9d event_function_call+0x10d ([kernel.kallsyms])
ffffffff91d33557 perf_event_for_each_child+0x37 ([kernel.kallsyms])
ffffffff91d47324 _perf_ioctl+0x204 ([kernel.kallsyms])
ffffffff91d47c43 perf_ioctl+0x33 ([kernel.kallsyms])
ffffffff91e2f216 __x64_sys_ioctl+0x96 ([kernel.kallsyms])
ffffffff9265f1ae do_syscall_64+0x9e ([kernel.kallsyms])
ffffffff92800130 entry_SYSCALL_64+0xb0 ([kernel.kallsyms])perf 801 [000] 18.031814: DEFERRED CALLCHAIN
7fb5fc22034b __GI___ioctl+0x3b (/usr/lib/x86_64-linux-gnu/libc.so.6)
--- tools/perf/builtin-script.c | 89 +++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+)
tools/perf/builtin-script.c +89 -0
@@ -2484,6 +2484,93 @@ static int process_sample_event(const struct perf_tool *tool,return ret; } +static int process_deferred_sample_event (const struct perf_tool *tool, +union perf_event *event, +struct perf_sample *sample, +struct evsel *evsel, +struct machine *machine) +{ + struct perf_script *scr = container_of(tool, struct perf_script, tool); +struct perf_event_attr *attr = &evsel->core.attr; +struct evsel_script *es = evsel->priv; +unsigned int type =output_type (attr->type); +struct addr_location al; +FILE *fp = es->fp; +int ret =0 ; + +if (output[type].fields ==0 ) +return 0 ; + +/* Set thread to NULL to indicate addr_al and al are not initialized */ +addr_location__init (&al); + +if (perf_time__ranges_skip_sample (scr->ptime_range, scr->range_num, + sample->time)) { + goto out_put; + } + +if (debug_mode) { +if (sample->time < last_timestamp) { +pr_err ("Samples misordered, previous: %" PRIu64 +" this: %" PRIu64"\n" , last_timestamp, + sample->time); + nr_unordered++; + } + last_timestamp = sample->time; + goto out_put; + } + +if (filter_cpu (sample)) + goto out_put; + +if (machine__resolve (machine, &al, sample) <0 ) { +pr_err ("problem processing %d event, skipping it.\n" , + event->header.type); + ret =-1 ; + goto out_put; + } + +if (al.filtered) + goto out_put; + +if (!show_event (sample, evsel, al.thread, &al,NULL )) + goto out_put; + +if (evswitch__discard (&scr->evswitch, evsel)) + goto out_put; + +perf_sample__fprintf_start (scr, sample, al.thread, evsel, +PERF_RECORD_CALLCHAIN_DEFERRED , fp); +fprintf (fp,"DEFERRED CALLCHAIN" ); + +if (PRINT_FIELD (IP )) { +struct callchain_cursor *cursor =NULL ; + +if (symbol_conf.use_callchain && sample->callchain) { + cursor =get_tls_callchain_cursor (); +if (thread__resolve_callchain (al.thread, cursor, evsel, + sample,NULL ,NULL , + scripting_max_stack)) { +pr_info ("cannot resolve deferred callchains\n" ); + cursor =NULL ; + } + } + +fputc (cursor ?'\n' :' ' , fp); +sample__fprintf_sym (sample, &al,0 , output[type].print_ip_opts, + cursor, symbol_conf.bt_stop_list, fp); + } + +fprintf (fp,"\n" ); + +if (verbose >0 ) +fflush (fp); + +out_put: +addr_location__exit (&al); +return ret; +} +// Used when scr->per_event_dump is not set static struct evsel_script es_stdout;
@@ -4080,6 +4167,7 @@ int cmd_script(int argc, const char **argv)perf_tool__init (&script.tool, !unsorted_dump); script.tool.sample = process_sample_event; + script.tool.callchain_deferred = process_deferred_sample_event; script.tool.mmap = perf_event__process_mmap; script.tool.mmap2 = perf_event__process_mmap2; script.tool.comm = perf_event__process_comm;
@@ -4106,6 +4194,7 @@ int cmd_script(int argc, const char **argv) script.tool.throttle = process_throttle_event; script.tool.unthrottle = process_throttle_event; script.tool.ordering_requires_timestamps = true; + script.tool.merge_deferred_callchains = false; session =perf_session__new (&data, &script.tool);if (IS_ERR (session))return PTR_ERR (session);
Signature · 1 line
2.50.1
Namhyung Kim
maintainerRe: [PATCH v15 7/8] perf script: Display PERF_RECORD_CALLCHAIN_DEFERRED
Hi Steven,
Quoted 137 lines · Steven Rostedt
From: Namhyung Kim <[email protected]>
Handle the deferred callchains in the script output.
$ perf script perf 801 [000] 18.031793: 1 cycles:P: ffffffff91a14c36 __intel_pmu_enable_all.isra.0+0x56 ([kernel.kallsyms]) ffffffff91d373e9 perf_ctx_enable+0x39 ([kernel.kallsyms]) ffffffff91d36af7 event_function+0xd7 ([kernel.kallsyms]) ffffffff91d34222 remote_function+0x42 ([kernel.kallsyms]) ffffffff91c1ebe1 generic_exec_single+0x61 ([kernel.kallsyms]) ffffffff91c1edac smp_call_function_single+0xec ([kernel.kallsyms]) ffffffff91d37a9d event_function_call+0x10d ([kernel.kallsyms]) ffffffff91d33557 perf_event_for_each_child+0x37 ([kernel.kallsyms]) ffffffff91d47324 _perf_ioctl+0x204 ([kernel.kallsyms]) ffffffff91d47c43 perf_ioctl+0x33 ([kernel.kallsyms]) ffffffff91e2f216 __x64_sys_ioctl+0x96 ([kernel.kallsyms]) ffffffff9265f1ae do_syscall_64+0x9e ([kernel.kallsyms]) ffffffff92800130 entry_SYSCALL_64+0xb0 ([kernel.kallsyms])perf 801 [000] 18.031814: DEFERRED CALLCHAIN
7fb5fc22034b __GI___ioctl+0x3b (/usr/lib/x86_64-linux-gnu/libc.so.6)Signed-off-by Namhyung Kim <[email protected]>Signed-off-by Josh Poimboeuf <[email protected]>Signed-off-by Steven Rostedt (Google) <[email protected]>--- tools/perf/builtin-script.c | 89 +++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+)tools/perf/builtin-script.c +88
-0@@ -2484,6 +2484,93 @@ static int process_sample_event(const struct perf_tool *tool,return ret; } +static int process_deferred_sample_event (const struct perf_tool *tool, +union perf_event *event, +struct perf_sample *sample, +struct evsel *evsel, +struct machine *machine) +{ + struct perf_script *scr = container_of(tool, struct perf_script, tool); +struct perf_event_attr *attr = &evsel->core.attr; +struct evsel_script *es = evsel->priv; +unsigned int type =output_type (attr->type); +struct addr_location al; +FILE *fp = es->fp; +int ret =0 ; + +if (output[type].fields ==0 ) +return 0 ; + +/* Set thread to NULL to indicate addr_al and al are not initialized */ +addr_location__init (&al); + +if (perf_time__ranges_skip_sample (scr->ptime_range, scr->range_num, + sample->time)) { + goto out_put; + } + +if (debug_mode) { +if (sample->time < last_timestamp) { +pr_err ("Samples misordered, previous: %" PRIu64 +" this: %" PRIu64"\n" , last_timestamp, + sample->time); + nr_unordered++; + } + last_timestamp = sample->time; + goto out_put; + } + +if (filter_cpu (sample)) + goto out_put; + +if (machine__resolve (machine, &al, sample) <0 ) { +pr_err ("problem processing %d event, skipping it.\n" , + event->header.type); + ret =-1 ; + goto out_put; + } + +if (al.filtered) + goto out_put; + +if (!show_event (sample, evsel, al.thread, &al,NULL )) + goto out_put; + +if (evswitch__discard (&scr->evswitch, evsel)) + goto out_put; + +perf_sample__fprintf_start (scr, sample, al.thread, evsel, +PERF_RECORD_CALLCHAIN_DEFERRED , fp); +fprintf (fp,"DEFERRED CALLCHAIN" ); + +if (PRINT_FIELD (IP )) { +struct callchain_cursor *cursor =NULL ; + +if (symbol_conf.use_callchain && sample->callchain) { + cursor =get_tls_callchain_cursor (); +if (thread__resolve_callchain (al.thread, cursor, evsel, + sample,NULL ,NULL , + scripting_max_stack)) { +pr_info ("cannot resolve deferred callchains\n" ); + cursor =NULL ; + } + } + +fputc (cursor ?'\n' :' ' , fp); +sample__fprintf_sym (sample, &al,0 , output[type].print_ip_opts, + cursor, symbol_conf.bt_stop_list, fp); + } + +fprintf (fp,"\n" ); + +if (verbose >0 ) +fflush (fp); + +out_put: +addr_location__exit (&al); +return ret; +} +// Used when scr->per_event_dump is not set static struct evsel_script es_stdout;@@ -4080,6 +4167,7 @@ int cmd_script(int argc, const char **argv)perf_tool__init (&script.tool, !unsorted_dump); script.tool.sample = process_sample_event; + script.tool.callchain_deferred = process_deferred_sample_event; script.tool.mmap = perf_event__process_mmap; script.tool.mmap2 = perf_event__process_mmap2; script.tool.comm = perf_event__process_comm;
tools/perf/builtin-script.c int cmd_script(int argc, const char **argv) · line 4197
Quoted 4 lines
tools/perf/builtin-script.c +1
-0@@ -4106,6 +4194,7 @@ int cmd_script(int argc, const char **argv) script.tool.throttle = process_throttle_event; script.tool.unthrottle = process_throttle_event; script.tool.ordering_requires_timestamps = true; + script.tool.merge_deferred_callchains = false;
Oops, this chunk should belong to the next patch which introduces this field. Sorry about that.
Thanks,
Namhyung
Quoted 7 lines
tools/perf/builtin-script.c +0
-0session =perf_session__new (&data, &script.tool);if (IS_ERR (session))return PTR_ERR (session);Signature · 1 line
2.50.1
PATCH v15 8/8 perf tools: Merge deferred user callchains
In reply to Steven RostedtFrom: Namhyung Kim <[email protected]>
Save samples with deferred callchains in a separate list and deliver them after merging the user callchains. If users don't want to merge they can set tool->merge_deferred_callchains to false to prevent the behavior.
With previous result, now perf script will show the merged callchains.
$ perf script
perf 801 [000] 18.031793: 1 cycles:P:
ffffffff91a14c36 __intel_pmu_enable_all.isra.0+0x56 ([kernel.kallsyms])
ffffffff91d373e9 perf_ctx_enable+0x39 ([kernel.kallsyms])
ffffffff91d36af7 event_function+0xd7 ([kernel.kallsyms])
ffffffff91d34222 remote_function+0x42 ([kernel.kallsyms])
ffffffff91c1ebe1 generic_exec_single+0x61 ([kernel.kallsyms])
ffffffff91c1edac smp_call_function_single+0xec ([kernel.kallsyms])
ffffffff91d37a9d event_function_call+0x10d ([kernel.kallsyms])
ffffffff91d33557 perf_event_for_each_child+0x37 ([kernel.kallsyms])
ffffffff91d47324 _perf_ioctl+0x204 ([kernel.kallsyms])
ffffffff91d47c43 perf_ioctl+0x33 ([kernel.kallsyms])
ffffffff91e2f216 __x64_sys_ioctl+0x96 ([kernel.kallsyms])
ffffffff9265f1ae do_syscall_64+0x9e ([kernel.kallsyms])
ffffffff92800130 entry_SYSCALL_64+0xb0 ([kernel.kallsyms])
7fb5fc22034b __GI___ioctl+0x3b (/usr/lib/x86_64-linux-gnu/libc.so.6)
...The old output can be get using --no-merge-callchain option. Also perf report can get the user callchain entry at the end.
$ perf report --no-children --percent-limit=0 --stdio -q -S __intel_pmu_enable_all.isra.0
# symbol: __intel_pmu_enable_all.isra.0
0.00% perf [kernel.kallsyms]
|
---__intel_pmu_enable_all.isra.0
perf_ctx_enable
event_function
remote_function
generic_exec_single
smp_call_function_single
event_function_call
perf_event_for_each_child
_perf_ioctl
perf_ioctl
__x64_sys_ioctl
do_syscall_64
entry_SYSCALL_64
__GI___ioctl---
Changes since v14: https://lore.kernel.org/[email protected]
- Use both TID and cookie to match the request event to the deferred unwind event.
tools/perf/Documentation/perf-script.txt | 5 ++ tools/perf/builtin-script.c | 5 +- tools/perf/util/callchain.c | 24 +++++++++ tools/perf/util/callchain.h | 3 ++ tools/perf/util/evlist.c | 1 + tools/perf/util/evlist.h | 1 + tools/perf/util/session.c | 64 +++++++++++++++++++++++- tools/perf/util/tool.c | 1 + tools/perf/util/tool.h | 1 + 9 files changed, 103 insertions(+), 2 deletions(-)
tools/perf/Documentation/perf-script.txt +5 -0
@@ -527,6 +527,11 @@ include::itrace.txt[] The known limitations include exception handing such as setjmp/longjmp will have calls/returns not match. +--merge-callchains:: + Enable merging deferred user callchains if available. This is the + default behavior. If you want to see separate CALLCHAIN_DEFERRED + records for some reason, use --no-merge-callchains explicitly. + :GMEXAMPLECMD: script :GMEXAMPLESUBCMD: include::guest-files.txt[]
tools/perf/builtin-script.c +4 -1
@@ -3785,6 +3785,7 @@ int cmd_script(int argc, const char **argv)bool header_only = false;bool script_started = false;bool unsorted_dump = false; +bool merge_deferred_callchains = true;char *rec_script_path =NULL ;char *rep_script_path =NULL ;struct perf_session *session;
@@ -3938,6 +3939,8 @@ int cmd_script(int argc, const char **argv) "Guest code can be found in hypervisor process"),OPT_BOOLEAN ('\0' ,"stitch-lbr" , &script.stitch_lbr,"Enable LBR callgraph stitching approach" ), +OPT_BOOLEAN ('\0' ,"merge-callchains" , &merge_deferred_callchains, +"Enable merge deferred user callchains" ),OPTS_EVSWITCH (&script.evswitch), OPT_END() };
@@ -4194,7 +4197,7 @@ int cmd_script(int argc, const char **argv) script.tool.throttle = process_throttle_event; script.tool.unthrottle = process_throttle_event; script.tool.ordering_requires_timestamps = true; - script.tool.merge_deferred_callchains = false; + script.tool.merge_deferred_callchains = merge_deferred_callchains; session =perf_session__new (&data, &script.tool);if (IS_ERR (session))return PTR_ERR (session);
tools/perf/util/callchain.c +24 -0
@@ -1828,3 +1828,27 @@ int sample__for_each_callchain_node(struct thread *thread, struct evsel *evsel, }return 0 ; } + +int sample__merge_deferred_callchain (struct perf_sample *sample_orig, +struct perf_sample *sample_callchain) +{ +u64 nr_orig = sample_orig->callchain->nr -PERF_DEFERRED_ITEMS ; +u64 nr_deferred = sample_callchain->callchain->nr; +struct ip_callchain *callchain; + + callchain =calloc (1 + nr_orig + nr_deferred,sizeof (u64)); +if (callchain ==NULL ) { + sample_orig->deferred_callchain = false; +return -ENOMEM ; + } + + callchain->nr = nr_orig + nr_deferred; +/* copy except for the last PERF_CONTEXT_USER_DEFERRED */ +memcpy (callchain->ips, sample_orig->callchain->ips, nr_orig *sizeof (u64)); +/* copy deferred use callchains */ +memcpy (&callchain->ips[nr_orig], sample_callchain->callchain->ips, + nr_deferred *sizeof (u64)); + + sample_orig->callchain = callchain; +return 0 ; +}
tools/perf/util/callchain.h +3 -0
@@ -317,4 +317,7 @@ int sample__for_each_callchain_node(struct thread *thread, struct evsel *evsel, struct perf_sample *sample, int max_stack, bool symbols, callchain_iter_fn cb, void *data); +int sample__merge_deferred_callchain (struct perf_sample *sample_orig, +struct perf_sample *sample_callchain); +#endif /* __PERF_CALLCHAIN_H */
tools/perf/util/evlist.c +1 -0
@@ -85,6 +85,7 @@ void evlist__init(struct evlist *evlist, struct perf_cpu_map *cpus, evlist->ctl_fd.pos =-1 ; evlist->nr_br_cntr =-1 ;metricgroup__rblist_init (&evlist->metric_events); +INIT_LIST_HEAD (&evlist->deferred_samples); }struct evlist *evlist__new (void )
tools/perf/util/evlist.h +1 -0
@@ -92,6 +92,7 @@ struct evlist {* of struct metric_expr. */ struct rblist metric_events; +struct list_head deferred_samples; };struct evsel_str_handler {
tools/perf/util/session.c +63 -1
82 lines not on this page · raw message
tools/perf/util/tool.c +1 -0
@@ -258,6 +258,7 @@ void perf_tool__init(struct perf_tool *tool, bool ordered_events) tool->cgroup_events = false; tool->no_warn = false; tool->show_feat_hdr =SHOW_FEAT_NO_HEADER ; + tool->merge_deferred_callchains = true; tool->sample = process_event_sample_stub; tool->mmap = process_event_stub;
tools/perf/util/tool.h +1 -0
@@ -88,6 +88,7 @@ struct perf_tool {bool cgroup_events;bool no_warn;bool dont_split_sample_group; +bool merge_deferred_callchains;enum show_feature_header show_feat_hdr; };
Signature · 1 line
2.50.1
Namhyung Kim
maintainerRe: [PATCH v15 8/8] perf tools: Merge deferred user callchains
Quoted 214 lines · Steven Rostedt
From: Namhyung Kim <[email protected]>
Save samples with deferred callchains in a separate list and deliver them after merging the user callchains. If users don't want to merge they can set
tool->merge_deferred_callchainsto false to prevent the behavior.With previous result, now perf script will show the merged callchains.
$ perf script perf 801 [000] 18.031793: 1 cycles:P: ffffffff91a14c36 __intel_pmu_enable_all.isra.0+0x56 ([kernel.kallsyms]) ffffffff91d373e9 perf_ctx_enable+0x39 ([kernel.kallsyms]) ffffffff91d36af7 event_function+0xd7 ([kernel.kallsyms]) ffffffff91d34222 remote_function+0x42 ([kernel.kallsyms]) ffffffff91c1ebe1 generic_exec_single+0x61 ([kernel.kallsyms]) ffffffff91c1edac smp_call_function_single+0xec ([kernel.kallsyms]) ffffffff91d37a9d event_function_call+0x10d ([kernel.kallsyms]) ffffffff91d33557 perf_event_for_each_child+0x37 ([kernel.kallsyms]) ffffffff91d47324 _perf_ioctl+0x204 ([kernel.kallsyms]) ffffffff91d47c43 perf_ioctl+0x33 ([kernel.kallsyms]) ffffffff91e2f216 __x64_sys_ioctl+0x96 ([kernel.kallsyms]) ffffffff9265f1ae do_syscall_64+0x9e ([kernel.kallsyms]) ffffffff92800130 entry_SYSCALL_64+0xb0 ([kernel.kallsyms]) 7fb5fc22034b __GI___ioctl+0x3b (/usr/lib/x86_64-linux-gnu/libc.so.6) ...The old output can be get using --no-merge-callchain option. Also perf report can get the user callchain entry at the end.
$ perf report --no-children --percent-limit=0 --stdio -q -S __intel_pmu_enable_all.isra.0 # symbol: __intel_pmu_enable_all.isra.0 0.00% perf [kernel.kallsyms] | ---__intel_pmu_enable_all.isra.0 perf_ctx_enable event_function remote_function generic_exec_single smp_call_function_single event_function_call perf_event_for_each_child _perf_ioctl perf_ioctl __x64_sys_ioctl do_syscall_64 entry_SYSCALL_64 __GI___ioctlSigned-off-by Namhyung Kim <[email protected]>Signed-off-by Josh Poimboeuf <[email protected]>Signed-off-by Steven Rostedt (Google) <[email protected]>---
Changes since v14: https://lore.kernel.org/[email protected]- Use both TID and cookie to match the request event to the deferred unwind event.
tools/perf/Documentation/perf-script.txt | 5 ++ tools/perf/builtin-script.c | 5 +- tools/perf/util/callchain.c | 24 +++++++++ tools/perf/util/callchain.h | 3 ++ tools/perf/util/evlist.c | 1 + tools/perf/util/evlist.h | 1 + tools/perf/util/session.c | 64 +++++++++++++++++++++++- tools/perf/util/tool.c | 1 + tools/perf/util/tool.h | 1 + 9 files changed, 103 insertions(+), 2 deletions(-)tools/perf/Documentation/perf-script.txt +5
-0@@ -527,6 +527,11 @@ include::itrace.txt[] The known limitations include exception handing such as setjmp/longjmp will have calls/returns not match. +--merge-callchains:: + Enable merging deferred user callchains if available. This is the + default behavior. If you want to see separate CALLCHAIN_DEFERRED + records for some reason, use --no-merge-callchains explicitly. + :GMEXAMPLECMD: script :GMEXAMPLESUBCMD: include::guest-files.txt[]tools/perf/builtin-script.c +4
-1@@ -3785,6 +3785,7 @@ int cmd_script(int argc, const char **argv)bool header_only = false;bool script_started = false;bool unsorted_dump = false; +bool merge_deferred_callchains = true;char *rec_script_path =NULL ;char *rep_script_path =NULL ;struct perf_session *session;@@ -3938,6 +3939,8 @@ int cmd_script(int argc, const char **argv) "Guest code can be found in hypervisor process"),OPT_BOOLEAN ('\0' ,"stitch-lbr" , &script.stitch_lbr,"Enable LBR callgraph stitching approach" ), +OPT_BOOLEAN ('\0' ,"merge-callchains" , &merge_deferred_callchains, +"Enable merge deferred user callchains" ),OPTS_EVSWITCH (&script.evswitch), OPT_END() };@@ -4194,7 +4197,7 @@ int cmd_script(int argc, const char **argv) script.tool.throttle = process_throttle_event; script.tool.unthrottle = process_throttle_event; script.tool.ordering_requires_timestamps = true; - script.tool.merge_deferred_callchains = false; + script.tool.merge_deferred_callchains = merge_deferred_callchains; session =perf_session__new (&data, &script.tool);if (IS_ERR (session))return PTR_ERR (session);tools/perf/util/callchain.c +24
-0@@ -1828,3 +1828,27 @@ int sample__for_each_callchain_node(struct thread *thread, struct evsel *evsel, }return 0 ; } + +int sample__merge_deferred_callchain (struct perf_sample *sample_orig, +struct perf_sample *sample_callchain) +{ +u64 nr_orig = sample_orig->callchain->nr -PERF_DEFERRED_ITEMS ; +u64 nr_deferred = sample_callchain->callchain->nr; +struct ip_callchain *callchain; + + callchain =calloc (1 + nr_orig + nr_deferred,sizeof (u64)); +if (callchain ==NULL ) { + sample_orig->deferred_callchain = false; +return -ENOMEM ; + } + + callchain->nr = nr_orig + nr_deferred; +/* copy except for the last PERF_CONTEXT_USER_DEFERRED */ +memcpy (callchain->ips, sample_orig->callchain->ips, nr_orig *sizeof (u64)); +/* copy deferred use callchains */ +memcpy (&callchain->ips[nr_orig], sample_callchain->callchain->ips, + nr_deferred *sizeof (u64)); + + sample_orig->callchain = callchain; +return 0 ; +}tools/perf/util/callchain.h +3
-0@@ -317,4 +317,7 @@ int sample__for_each_callchain_node(struct thread *thread, struct evsel *evsel, struct perf_sample *sample, int max_stack, bool symbols, callchain_iter_fn cb, void *data); +int sample__merge_deferred_callchain (struct perf_sample *sample_orig, +struct perf_sample *sample_callchain); +#endif /* __PERF_CALLCHAIN_H */ tools/perf/util/evlist.c +1
-0@@ -85,6 +85,7 @@ void evlist__init(struct evlist *evlist, struct perf_cpu_map *cpus, evlist->ctl_fd.pos =-1 ; evlist->nr_br_cntr =-1 ;metricgroup__rblist_init (&evlist->metric_events); +INIT_LIST_HEAD (&evlist->deferred_samples); }struct evlist *evlist__new (void )tools/perf/util/evlist.h +1
-0@@ -92,6 +92,7 @@ struct evlist {* of struct metric_expr. */ struct rblist metric_events; +struct list_head deferred_samples; };struct evsel_str_handler {tools/perf/util/session.c +23
-0@@ -1283,6 +1283,57 @@ static int evlist__deliver_sample(struct evlist *evlist, const struct perf_tool per_thread); } +struct deferred_event { +struct list_head list; +union perf_event *event; +}; + +static int evlist__deliver_deferred_samples (struct evlist *evlist, +const struct perf_tool *tool, +union perf_event *event, +struct perf_sample *sample, +struct machine *machine) +{ +struct deferred_event *de, *tmp; +struct evsel *evsel; +int ret =0 ; + +if (!tool->merge_deferred_callchains) { + evsel =evlist__id2evsel (evlist, sample->id); +return tool->callchain_deferred (tool, event, sample, + evsel, machine); + } + +list_for_each_entry_safe (de, tmp, &evlist->deferred_samples, list) { +struct perf_sample orig_sample;
tools/perf/util/session.c static int evlist__deliver_sample(struct evlist *evlist, const struct perf_tool · line 1318
Quoted 10 lines
tools/perf/util/session.c +10
-0+ + ret =evlist__parse_sample (evlist, de->event, &orig_sample); +if (ret <0 ) { +pr_err ("failed to parse original sample\n" ); +break ; + } + +if (sample->tid != orig_sample.tid || + event->callchain_deferred.cookie != orig_sample.deferred_cookie) +continue ;
I think we should handle original samples with different cookies too. They are before LOST records so we don't merge the callchain though.
tools/perf/util/session.c static int evlist__deliver_sample(struct evlist *evlist, const struct perf_tool · line 1323
Quoted 5 lines
tools/perf/util/session.c +5
-0+ + evsel =evlist__id2evsel (evlist, orig_sample.id); +sample__merge_deferred_callchain (&orig_sample, sample); + ret =evlist__deliver_sample (evlist, tool, de->event, + &orig_sample, evsel, machine);
Something like this.
@@ -1313,12 +1313,13 @@ static int evlist__deliver_deferred_samples(struct evlist *evlist,
break;
}- if (sample->tid != orig_sample.tid ||
- event->callchain_deferred.cookie != orig_sample.deferred_cookie)
+ if (sample->tid != orig_sample.tid)
continue;
+ if (event->callchain_deferred.cookie == orig_sample.deferred_cookie)
+ sample__merge_deferred_callchain(&orig_sample, sample);
+
evsel = evlist__id2evsel(evlist, orig_sample.id);
- sample__merge_deferred_callchain(&orig_sample, sample);
ret = evlist__deliver_sample(evlist, tool, de->event,
&orig_sample, evsel, machine);Thanks,
Namhyung
Quoted 71 lines
tools/perf/util/session.c +25
-1+ +if (orig_sample.deferred_callchain) +free (orig_sample.callchain); + +list_del (&de->list); +free (de); + +if (ret) +break ; + } +return ret; +} + static int machines__deliver_event(struct machines *machines, struct evlist *evlist, union perf_event *event,@@ -1311,6 +1362,16 @@ static int machines__deliver_event(struct machines *machines,return 0 ; }dump_sample (evsel, event, sample,perf_env__arch (machine->env)); +if (sample->deferred_callchain && tool->merge_deferred_callchains) { +struct deferred_event *de =malloc (sizeof (*de)); + +if (de ==NULL ) +return -ENOMEM ; + + de->event = event; +list_add_tail (&de->list, &evlist->deferred_samples); +return 0 ; + }return evlist__deliver_sample (evlist, tool, event, sample, evsel, machine);case PERF_RECORD_MMAP :return tool->mmap (tool, event, sample, machine);@@ -1370,7 +1431,8 @@ static int machines__deliver_event(struct machines *machines,return tool->aux_output_hw_id (tool, event, sample, machine);case PERF_RECORD_CALLCHAIN_DEFERRED :dump_deferred_callchain (evsel, event, sample); -return tool->callchain_deferred (tool, event, sample, evsel, machine); +return evlist__deliver_deferred_samples (evlist, tool, event, + sample, machine);default : ++evlist->stats.nr_unknown_events;return -1 ;tools/perf/util/tool.c +1
-0@@ -258,6 +258,7 @@ void perf_tool__init(struct perf_tool *tool, bool ordered_events) tool->cgroup_events = false; tool->no_warn = false; tool->show_feat_hdr =SHOW_FEAT_NO_HEADER ; + tool->merge_deferred_callchains = true; tool->sample = process_event_sample_stub; tool->mmap = process_event_stub;tools/perf/util/tool.h +1
-0@@ -88,6 +88,7 @@ struct perf_tool {bool cgroup_events;bool no_warn;bool dont_split_sample_group; +bool merge_deferred_callchains;enum show_feature_header show_feat_hdr; };Signature · 1 line
2.50.1