powerpc/perf_events: Implement perf_arch_fetch_caller_regs for powerpc
PATCH 1/1 powerpc/perf_events: Implement perf_arch_fetch_caller_regs for powerpc
This implements a powerpc version of perf_arch_fetch_caller_regs. It's implemented in assembly because that way we can be sure there isn't a stack frame for perf_arch_fetch_caller_regs. If it was in C, gcc might or might not create a stack frame for it, which would affect the number of levels we have to skip. It's not ifdef'd because it is only 14 instructions long.
With this, we see results from perf record -e lock:lock_acquire like this:
# Samples: 24878
#
# Overhead Command Shared Object Symbol
# ........ .............. ................. ......
#
14.99% perf [kernel.kallsyms] [k] ._raw_spin_lock
|
--- ._raw_spin_lock
|
|--25.00%-- .alloc_fd
| (nil)
| |
| |--50.00%-- .anon_inode_getfd
| | .sys_perf_event_open
| | syscall_exit
| | syscall
| | create_counter
| | __cmd_record
| | run_builtin
| | main
| | 0xfd2e704
| | 0xfd2e8c0
| | (nil)... etc.
--- arch/powerpc/include/asm/asm-compat.h | 2 ++ arch/powerpc/kernel/misc.S | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+)
arch/powerpc/include/asm/asm-compat.h +2 -0
@@ -28,6 +28,7 @@#define PPC_LLARX (t, a, b, eh) PPC_LDARX(t, a, b, eh)#define PPC_STLCX stringify_in_c(stdcx.)#define PPC_CNTLZL stringify_in_c(cntlzd) +#define PPC_LR_STKOFF 16/* Move to CR, single-entry optimized version. Only available * on POWER4 and later.
@@ -51,6 +52,7 @@#define PPC_STLCX stringify_in_c(stwcx.)#define PPC_CNTLZL stringify_in_c(cntlzw)#define PPC_MTOCRF stringify_in_c(mtcrf) +#define PPC_LR_STKOFF 4#endif
arch/powerpc/kernel/misc.S +20 -0
@@ -127,3 +127,23 @@ _GLOBAL(__setup_cpu_power7) _GLOBAL(__restore_cpu_power7) /* place holder */ blr + +/* + * Get a minimal set of registers for our caller's nth caller. + * r3 = regs pointer, r5 = n. + */ +_GLOBAL(perf_arch_fetch_caller_regs) + mr r6,r1 + cmpwi r5,0 + mflr r4 + ble 2f + mtctr r5 +1: PPC_LL r6,0(r6) + bdnz 1b + PPC_LL r4,PPC_LR_STKOFF(r6) +2: PPC_LL r7,0(r6) + PPC_LL r7,PPC_LR_STKOFF(r7) + PPC_STL r6,GPR1-STACK_FRAME_OVERHEAD(r3) + PPC_STL r4,_NIP-STACK_FRAME_OVERHEAD(r3) + PPC_STL r7,_LINK-STACK_FRAME_OVERHEAD(r3) + blr
Quoted 5 lines
This implements a powerpc version of
perf_arch_fetch_caller_regs. It's implemented in assembly because that way we can be sure there isn't a stack frame forperf_arch_fetch_caller_regs. If it was in C, gcc might or might not create a stack frame for it, which would affect the number of levels we have to skip.
Should we put this comment in the code as well?
Mikey
arch/powerpc/include/asm
Quoted 39 lines
It's not ifdef'd
because it is only 14 instructions long.With this, we see results from perf record -e lock:lock_acquire like this:
# Samples: 24878
#
# Overhead Command Shared Object Symbol# ........ .............. ................. ...... # 14.99% perf [kernel.kallsyms] [k] ._raw_spin_lock | --- ._raw_spin_lock | |--25.00%-- .alloc_fd | (nil) | | | |--50.00%-- .anon_inode_getfd | | .sys_perf_event_open | | syscall_exit | | syscall | | create_counter | | __cmd_record | | run_builtin | | main | | 0xfd2e704 | | 0xfd2e8c0 | | (nil)... etc.
Signed-off-by Paul Mackerras <[email protected]>--- arch/powerpc/include/asm/asm-compat.h | 2 ++ arch/powerpc/kernel/misc.S | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+)arch/powerpc/include/asm renamed from arch/powerpc/include/asm/asm-compat.h +0
-0
/asm-compat.h
Quoted 52 lines
index c1b475a..a9b91ed 100644
arch/powerpc/include/asm/asm-compat.h +2
-0@@ -28,6 +28,7 @@#define PPC_LLARX (t, a, b, eh) PPC_LDARX(t, a, b, eh)#define PPC_STLCX stringify_in_c(stdcx.)#define PPC_CNTLZL stringify_in_c(cntlzd) +#define PPC_LR_STKOFF 16/* Move to CR, single-entry optimized version. Only available * on POWER4 and later. @@ -51,6 +52,7 @@#define PPC_STLCX stringify_in_c(stwcx.)#define PPC_CNTLZL stringify_in_c(cntlzw)#define PPC_MTOCRF stringify_in_c(mtcrf) +#define PPC_LR_STKOFF 4#endif arch/powerpc/kernel/misc.S +20
-0@@ -127,3 +127,23 @@ _GLOBAL(__setup_cpu_power7) _GLOBAL(__restore_cpu_power7) /* place holder */ blr + +/* + * Get a minimal set of registers for our caller's nth caller. + * r3 = regs pointer, r5 = n. + */ +_GLOBAL(perf_arch_fetch_caller_regs) + mr r6,r1 + cmpwi r5,0 + mflr r4 + ble 2f + mtctr r5 +1: PPC_LL r6,0(r6) + bdnz 1b + PPC_LL r4,PPC_LR_STKOFF(r6) +2: PPC_LL r7,0(r6) + PPC_LL r7,PPC_LR_STKOFF(r7) + PPC_STL r6,GPR1-STACK_FRAME_OVERHEAD(r3) + PPC_STL r4,_NIP-STACK_FRAME_OVERHEAD(r3) + PPC_STL r7,_LINK-STACK_FRAME_OVERHEAD(r3) + blr_______________________________________________
Linuxppc-dev mailing list
[email protected]
https://lists.ozlabs.org/listinfo/linuxppc-dev
Quoted 34 lines · Paul Mackerras
This implements a powerpc version of
perf_arch_fetch_caller_regs. It's implemented in assembly because that way we can be sure there isn't a stack frame forperf_arch_fetch_caller_regs. If it was in C, gcc might or might not create a stack frame for it, which would affect the number of levels we have to skip. It's not ifdef'd because it is only 14 instructions long.With this, we see results from perf record -e lock:lock_acquire like this:
# Samples: 24878
#
# Overhead Command Shared Object Symbol# ........ .............. ................. ...... # 14.99% perf [kernel.kallsyms] [k] ._raw_spin_lock | --- ._raw_spin_lock | |--25.00%-- .alloc_fd | (nil) | | | |--50.00%-- .anon_inode_getfd | | .sys_perf_event_open | | syscall_exit | | syscall | | create_counter | | __cmd_record | | run_builtin | | main | | 0xfd2e704 | | 0xfd2e8c0 | | (nil)
Quoted 3 lines
... etc.
Signed-off-by Paul Mackerras <[email protected]>
Cool!
Quoted 53 lines
--- arch/powerpc/include/asm/asm-compat.h | 2 ++ arch/powerpc/kernel/misc.S | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+)arch/powerpc/include/asm/asm-compat.h +2
-0@@ -28,6 +28,7 @@#define PPC_LLARX (t, a, b, eh) PPC_LDARX(t, a, b, eh)#define PPC_STLCX stringify_in_c(stdcx.)#define PPC_CNTLZL stringify_in_c(cntlzd) +#define PPC_LR_STKOFF 16/* Move to CR, single-entry optimized version. Only available * on POWER4 and later. @@ -51,6 +52,7 @@#define PPC_STLCX stringify_in_c(stwcx.)#define PPC_CNTLZL stringify_in_c(cntlzw)#define PPC_MTOCRF stringify_in_c(mtcrf) +#define PPC_LR_STKOFF 4#endif arch/powerpc/kernel/misc.S +20
-0@@ -127,3 +127,23 @@ _GLOBAL(__setup_cpu_power7) _GLOBAL(__restore_cpu_power7) /* place holder */ blr + +/* + * Get a minimal set of registers for our caller's nth caller. + * r3 = regs pointer, r5 = n. + */ +_GLOBAL(perf_arch_fetch_caller_regs) + mr r6,r1 + cmpwi r5,0 + mflr r4 + ble 2f + mtctr r5 +1: PPC_LL r6,0(r6) + bdnz 1b + PPC_LL r4,PPC_LR_STKOFF(r6) +2: PPC_LL r7,0(r6) + PPC_LL r7,PPC_LR_STKOFF(r7) + PPC_STL r6,GPR1-STACK_FRAME_OVERHEAD(r3) + PPC_STL r4,_NIP-STACK_FRAME_OVERHEAD(r3) + PPC_STL r7,_LINK-STACK_FRAME_OVERHEAD(r3) + blr
Quoted 21 lines · Frederic Weisbecker
Quoted 19 lines · Paul Mackerras
14.99% perf [kernel.kallsyms] [k] ._raw_spin_lock
|
--- ._raw_spin_lock
|
|--25.00%-- .alloc_fd
| (nil)
| |
| |--50.00%-- .anon_inode_getfd
| | .sys_perf_event_open
| |syscall_exit
| | syscall
| |create_counter
| |__cmd_record
| |run_builtin
| | main
| |0xfd2e704
| |0xfd2e8c0
| | (nil)
Quoted 6 lines
Quoted 3 lines
... etc.
Signed-off-by Paul Mackerras <[email protected]>Cool!
By the way, I notice that gcc tends to inline the tracing functions, which means that by going up 2 stack frames we miss some of the functions. For example, for the lock:lock_acquire event, we have _raw_spin_lock() -> lock_acquire() -> trace_lock_acquire() -> perf_trace_lock_acquire() -> perf_trace_templ_lock_acquire() -> perf_fetch_caller_regs() -> perf_arch_fetch_caller_regs().
But in the ppc64 kernel binary I just built, gcc inlined trace_lock_acquire in lock_acquire, and perf_trace_templ_lock_acquire in perf_trace_lock_acquire. Given that perf_fetch_caller_regs is explicitly inlined, going up two levels from perf_fetch_caller_regs gets us to _raw_spin_lock, whereas I think you intended it to get us to trace_lock_acquire. I'm not sure what to do about that - any thoughts?
Paul.
Quoted 36 lines · Paul Mackerras
Quoted 21 lines · Frederic Weisbecker
Quoted 19 lines · Paul Mackerras
14.99% perf [kernel.kallsyms] [k] ._raw_spin_lock
|
--- ._raw_spin_lock
|
|--25.00%-- .alloc_fd
| (nil)
| |
| |--50.00%-- .anon_inode_getfd
| | .sys_perf_event_open
| |syscall_exit
| | syscall
| |create_counter
| |__cmd_record
| |run_builtin
| | main
| |0xfd2e704
| |0xfd2e8c0
| | (nil)Quoted 6 lines
Quoted 3 lines
... etc.
Signed-off-by Paul Mackerras <[email protected]>Cool!
By the way, I notice that gcc tends to inline the tracing functions, which means that by going up 2 stack frames we miss some of the functions. For example, for the lock:lock_acquire event, we have
_raw_spin_lock()->lock_acquire()->trace_lock_acquire()->perf_trace_lock_acquire()->perf_trace_templ_lock_acquire()->perf_fetch_caller_regs()->perf_arch_fetch_caller_regs().
Quoted 7 lines
But in the ppc64 kernel binary I just built, gcc inlined
trace_lock_acquireinlock_acquire, andperf_trace_templ_lock_acquireinperf_trace_lock_acquire. Given thatperf_fetch_caller_regsis explicitly inlined, going up two levels fromperf_fetch_caller_regsgets us to_raw_spin_lock, whereas I think you intended it to get us totrace_lock_acquire. I'm not sure what to do about that - any thoughts?
Yeah I've indeed seen this, and the problem is especially the fact perf_trace_templ_lock_acquire may or may not be inlined.
It is used for trace events that use the TRACE_EVENT_CLASS thing. We define a pattern of event structure that is shared among several events.
For example event A and event B share perf_trace_templ_foo. Both will have a different perf_trace_blah but those perf_trace_blah will both call the same perf_trace_templ_foo(), in this case, it won't be inlined.
Events that don't share a pattern will have their perf_trace_templ inlined, because there will be an exclusive 1:1 relationship between both.
The rewind of 2 is well suited for events sharing a pattern, ip will match the right event source, and not one of its callers.
Unfortunately, the others are more unlucky.
I didn't mind much about this yet because it had no bad effect on lock events. Quite the opposite actually. It's not very interesting to have lock_acquire as the event source unless you have a callchain.
If you have no callchain, you'll see a lot of such in perf report:
sym1 lock_aquire
sym2 lock_acquire
sym3 lock_acquire
What you want here is the function that called lock_acquire.
But if you have a callchain it's fine, because you have the nature of the event (lock_aquire) and the origin as well.
That said, lock events are an exception where the mistake has a lucky result. Other inlined events are harmed as we lose their most important caller. So I'm going to fix that.
I can just fetch the regs from perf_trace_foo() and pass them to perf_trace_templ_foo() and here we are.
Thanks.