Skip to contentSkip to conversation

powerpc/perf_events: Implement perf_arch_fetch_caller_regs for powerpc

PATCH Superseded Paul Mackerras 3 participants 5 messages Mar 15, 2010 – Mar 16, 2010 PowerPC

v1 of 2 Superseded Latest is v2 →

lore
Share & tools
lore
#0 lore raw

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
index c1b475a..a9b91ed 100644
@@ -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
index 2d29752..4459500 100644
@@ -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
#1 lore raw
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 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.

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.

---
 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
index 2d29752..4459500 100644
@@ -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

#2 lore raw
In reply to Paul Mackerras
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 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)
Quoted 3 lines

... etc.

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
index c1b475a..a9b91ed 100644
@@ -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
index 2d29752..4459500 100644
@@ -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
#3 lore raw
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.

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.

#4 lore raw
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.

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_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?

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.