Skip to contentSkip to conversation

libperf and arm64 userspace counter access support

PATCH Superseded Rob Herring 4 participants 29 messages Oct 1, 2020 – Jan 7, 2021

Subsystems (5)

v4 of 13 Superseded Latest is v13 →

lore
Share & tools
lore
#0 lore raw

v4 cover libperf and arm64 userspace counter access support

This is resurrecting Raphael's series[1] to enable userspace counter access on arm64. My previous versions are here[2][3][4]. A git branch is here[5].

Changes in v4:
- Dropped 'arm64: pmu: Add hook to handle pmu-related undefined instructions'. The onus is on userspace to pin itself to a homogeneous subset of CPUs and avoid any aborts on heterogeneous systems, so the hook is not needed.
- Make perf_evsel__mmap() take pages rather than bytes for size
- Fix building arm64 heterogeneous test.

Changes in v3:
- Dropped removing x86 rdpmc test until libperf tests can run via 'perf test'
- Added verbose prints for tests
- Split adding perf_evsel__mmap() to separate patch

The following changes to the arm64 support have been made compared to Raphael's last version:

The major change is support for heterogeneous systems with some restrictions. Specifically, userspace must pin itself to like CPUs, open a specific PMU by type, and use h/w specific events. The tests have been reworked to demonstrate this.

Chained events are not supported. The problem with supporting chained events was there's no way to distinguish between a chained event and a native 64-bit counter. We could add some flag, but do self monitoring processes really need that? Native 64-bit counters are supported if the PMU h/w has support. As there's already an explicit ABI to request 64-bit counters, userspace can request 64-bit counters and if user access is not enabled, then it must retry with 32-bit counters.

Prior versions broke the build on arm32 (surprisingly never caught by 0-day). As a result, event_mapped and event_unmapped implementations have been moved into the arm64 code.

There was a bug in that pmc_width was not set in the user page. The tests now check for this.

The documentation has been converted to rST. I've added sections on chained events and heterogeneous.

The tests have been expanded to test the cycle counter access.

Rob

[1] https://lore.kernel.org/linux-arm-kernel/[email protected]/
[2] https://lore.kernel.org/linux-arm-kernel/[email protected]/
[3] https://lore.kernel.org/linux-arm-kernel/[email protected]/
[4] https://lore.kernel.org/linux-arm-kernel/[email protected]/
[5] git://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git user-perf-event-v4

Raphael Gault (3):
arm64: pmu: Add function implementation to update event index in
userpage
arm64: perf: Enable pmu counter direct access for perf event on armv8
Documentation: arm64: Document PMU counters access from userspace

Rob Herring (6):
tools/include: Add an initial math64.h
libperf: Add libperf_evsel__mmap()
libperf: tests: Add support for verbose printing
libperf: Add support for user space counter access
libperf: Add arm64 support to perf_mmap__read_self()
perf: arm64: Add test for userspace counter access on heterogeneous
systems

 Documentation/arm64/index.rst                 |   1 +
 .../arm64/perf_counter_user_access.rst        |  56 ++++++
 arch/arm64/include/asm/mmu.h                  |   5 +
 arch/arm64/include/asm/mmu_context.h          |   2 +
 arch/arm64/include/asm/perf_event.h           |  14 ++
 arch/arm64/kernel/perf_event.c                |  62 ++++++
 include/linux/perf/arm_pmu.h                  |   2 +
 tools/include/linux/math64.h                  |  75 +++++++
 tools/lib/perf/Documentation/libperf.txt      |   1 +
 tools/lib/perf/evsel.c                        |  34 ++++
 tools/lib/perf/include/internal/evsel.h       |   2 +
 tools/lib/perf/include/internal/mmap.h        |   3 +
 tools/lib/perf/include/internal/tests.h       |  32 +++
 tools/lib/perf/include/perf/evsel.h           |   2 +
 tools/lib/perf/libperf.map                    |   1 +
 tools/lib/perf/mmap.c                         | 188 ++++++++++++++++++
 tools/lib/perf/tests/Makefile                 |   4 +-
 tools/lib/perf/tests/test-evsel.c             |  63 ++++++
 tools/perf/arch/arm64/include/arch-tests.h    |   7 +
 tools/perf/arch/arm64/tests/Build             |   1 +
 tools/perf/arch/arm64/tests/arch-tests.c      |   4 +
 tools/perf/arch/arm64/tests/user-events.c     | 170 ++++++++++++++++
 22 files changed, 728 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/arm64/perf_counter_user_access.rst
 create mode 100644 tools/include/linux/math64.h
 create mode 100644 tools/perf/arch/arm64/tests/user-events.c

--
2.25.1

#1 lore raw

PATCH v4 1/9 arm64: pmu: Add function implementation to update event index in userpage

From: Raphael Gault <[email protected]>

In order to be able to access the counter directly for userspace, we need to provide the index of the counter using the userpage. We thus need to override the event_idx function to retrieve and convert the perf_event index to armv8 hardware index.

Since the arm_pmu driver can be used by any implementation, even if not armv8, two components play a role into making sure the behaviour is correct and consistent with the PMU capabilities:

* the ARMPMU_EL0_RD_CNTR flag which denotes the capability to access counter from userspace.
* the event_idx call back, which is implemented and initialized by the PMU implementation: if no callback is provided, the default behaviour applies, returning 0 as index value.

---
 arch/arm64/kernel/perf_event.c | 21 +++++++++++++++++++++
 include/linux/perf/arm_pmu.h   |  2 ++
 2 files changed, 23 insertions(+)
arch/arm64/kernel/perf_event.c +21 -0
index 462f9a9cc44b..e14f360a7883 100644
@@ -818,6 +818,22 @@ static void armv8pmu_clear_event_idx(struct pmu_hw_events *cpuc,
 		clear_bit(idx - 1, cpuc->used_mask);
 }
 
+static int armv8pmu_access_event_idx(struct perf_event *event)
+{
+	if (!(event->hw.flags & ARMPMU_EL0_RD_CNTR))
+		return 0;
+
+	/*
+	 * We remap the cycle counter index to 32 to
+	 * match the offset applied to the rest of
+	 * the counter indices.
+	 */
+	if (event->hw.idx == ARMV8_IDX_CYCLE_COUNTER)
+		return 32;
+
+	return event->hw.idx;
+}
+
 /*
  * Add an event filter to a given event.
  */
@@ -914,6 +930,9 @@ static int __armv8_pmuv3_map_event(struct perf_event *event,
 	if (armv8pmu_event_is_64bit(event))
 		event->hw.flags |= ARMPMU_EVT_64BIT;
 
+	if (!armv8pmu_event_is_chained(event))
+		event->hw.flags |= ARMPMU_EL0_RD_CNTR;
+
 	/* Only expose micro/arch events supported by this PMU */
 	if ((hw_event_id > 0) && (hw_event_id < ARMV8_PMUV3_MAX_COMMON_EVENTS)
 	    && test_bit(hw_event_id, armpmu->pmceid_bitmap)) {
@@ -1038,6 +1057,8 @@ static int armv8_pmu_init(struct arm_pmu *cpu_pmu, char *name,
 	cpu_pmu->set_event_filter	= armv8pmu_set_event_filter;
 	cpu_pmu->filter_match		= armv8pmu_filter_match;
 
+	cpu_pmu->pmu.event_idx		= armv8pmu_access_event_idx;
+
 	cpu_pmu->name			= name;
 	cpu_pmu->map_event		= map_event;
 	cpu_pmu->attr_groups[ARMPMU_ATTR_GROUP_EVENTS] = events ?
include/linux/perf/arm_pmu.h +2 -0
index 5b616dde9a4c..74fbbbd29dc7 100644
@@ -26,6 +26,8 @@
  */
 /* Event uses a 64bit counter */
 #define ARMPMU_EVT_64BIT		1
+/* Allow access to hardware counter from userspace */
+#define ARMPMU_EL0_RD_CNTR		2
 
 #define HW_OP_UNSUPPORTED		0xFFFF
 #define C(_x)				PERF_COUNT_HW_CACHE_##_x
Signature · 1 line
2.25.1
#2 lore raw

PATCH v4 2/9 arm64: perf: Enable pmu counter direct access for perf event on armv8

In reply to Rob Herring

From: Raphael Gault <[email protected]>

Keep track of event opened with direct access to the hardware counters and modify permissions while they are open.

The strategy used here is the same which x86 uses: everytime an event is mapped, the permissions are set if required. The atomic field added in the mm_context helps keep track of the different event opened and de-activate the permissions when all are unmapped.
We also need to update the permissions in the context switch code so that tasks keep the right permissions.

---
v2:
- Move mapped/unmapped into arm64 code. Fixes arm32.
- Rebase on cap_user_time_short changes

Changes from Raphael's v4:
- Drop homogeneous check
- Disable access for chained counters
- Set pmc_width in user page

---
 arch/arm64/include/asm/mmu.h         |  5 ++++
 arch/arm64/include/asm/mmu_context.h |  2 ++
 arch/arm64/include/asm/perf_event.h  | 14 ++++++++++
 arch/arm64/kernel/perf_event.c       | 41 ++++++++++++++++++++++++++++
 4 files changed, 62 insertions(+)
arch/arm64/include/asm/mmu.h +5 -0
index a7a5ecaa2e83..52cfdb676f06 100644
@@ -19,6 +19,11 @@
 
 typedef struct {
 	atomic64_t	id;
+	/*
+	 * non-zero if userspace have access to hardware
+	 * counters directly.
+	 */
+	atomic_t	pmu_direct_access;
 #ifdef CONFIG_COMPAT
 	void		*sigpage;
 #endif
arch/arm64/include/asm/mmu_context.h +2 -0
index f2d7537d6f83..d24589ecb07a 100644
@@ -21,6 +21,7 @@
 #include <asm/proc-fns.h>
 #include <asm-generic/mm_hooks.h>
 #include <asm/cputype.h>
+#include <asm/perf_event.h>
 #include <asm/sysreg.h>
 #include <asm/tlbflush.h>
 
@@ -224,6 +225,7 @@ static inline void __switch_mm(struct mm_struct *next)
 	}
 
 	check_and_switch_context(next);
+	perf_switch_user_access(next);
 }
 
 static inline void
arch/arm64/include/asm/perf_event.h +14 -0
index 2c2d7dbe8a02..a025d9595d51 100644
@@ -8,6 +8,7 @@
 
 #include <asm/stack_pointer.h>
 #include <asm/ptrace.h>
+#include <linux/mm_types.h>
 
 #define	ARMV8_PMU_MAX_COUNTERS	32
 #define	ARMV8_PMU_COUNTER_MASK	(ARMV8_PMU_MAX_COUNTERS - 1)
@@ -251,4 +252,17 @@ extern unsigned long perf_misc_flags(struct pt_regs *regs);
 	(regs)->pstate = PSR_MODE_EL1h;	\
 }
 
+static inline void perf_switch_user_access(struct mm_struct *mm)
+{
+	if (!IS_ENABLED(CONFIG_PERF_EVENTS))
+		return;
+
+	if (atomic_read(&mm->context.pmu_direct_access)) {
+		write_sysreg(ARMV8_PMU_USERENR_ER|ARMV8_PMU_USERENR_CR,
+			     pmuserenr_el0);
+	} else {
+		write_sysreg(0, pmuserenr_el0);
+	}
+}
+
 #endif
arch/arm64/kernel/perf_event.c +41 -0

59 lines not on this page · raw message

Signature · 1 line
2.25.1
maintainer
#3 lore raw

Re: [PATCH v4 2/9] arm64: perf: Enable pmu counter direct access for perf event on armv8

Hi Rob,

Thanks for this, and sorry for the long delay since this was last reviewed. Overall this is looking pretty good, but I have a couple of remaining concerns.

Will, I have a query for you below.

Quoted 84 lines · Rob Herring

From: Raphael Gault <[email protected]>

Keep track of event opened with direct access to the hardware counters and modify permissions while they are open.

The strategy used here is the same which x86 uses: everytime an event is mapped, the permissions are set if required. The atomic field added in the mm_context helps keep track of the different event opened and de-activate the permissions when all are unmapped.
We also need to update the permissions in the context switch code so that tasks keep the right permissions.

---
v2:
- Move mapped/unmapped into arm64 code. Fixes arm32.
- Rebase on cap_user_time_short changes

Changes from Raphael's v4:
- Drop homogeneous check
- Disable access for chained counters
- Set pmc_width in user page

---
 arch/arm64/include/asm/mmu.h         |  5 ++++
 arch/arm64/include/asm/mmu_context.h |  2 ++
 arch/arm64/include/asm/perf_event.h  | 14 ++++++++++
 arch/arm64/kernel/perf_event.c       | 41 ++++++++++++++++++++++++++++
 4 files changed, 62 insertions(+)
arch/arm64/include/asm/mmu.h +5 -0
index a7a5ecaa2e83..52cfdb676f06 100644
@@ -19,6 +19,11 @@
 
 typedef struct {
 	atomic64_t	id;
+	/*
+	 * non-zero if userspace have access to hardware
+	 * counters directly.
+	 */
+	atomic_t	pmu_direct_access;
 #ifdef CONFIG_COMPAT
 	void		*sigpage;
 #endif
arch/arm64/include/asm/mmu_context.h +2 -0
index f2d7537d6f83..d24589ecb07a 100644
@@ -21,6 +21,7 @@
 #include <asm/proc-fns.h>
 #include <asm-generic/mm_hooks.h>
 #include <asm/cputype.h>
+#include <asm/perf_event.h>
 #include <asm/sysreg.h>
 #include <asm/tlbflush.h>
 
@@ -224,6 +225,7 @@ static inline void __switch_mm(struct mm_struct *next)
 	}
 
 	check_and_switch_context(next);
+	perf_switch_user_access(next);
 }
 
 static inline void
arch/arm64/include/asm/perf_event.h +3 -0
index 2c2d7dbe8a02..a025d9595d51 100644
@@ -8,6 +8,7 @@
 
 #include <asm/stack_pointer.h>
 #include <asm/ptrace.h>
+#include <linux/mm_types.h>
 
 #define	ARMV8_PMU_MAX_COUNTERS	32
 #define	ARMV8_PMU_COUNTER_MASK	(ARMV8_PMU_MAX_COUNTERS - 1)
@@ -251,4 +252,17 @@ extern unsigned long perf_misc_flags(struct pt_regs *regs);
 	(regs)->pstate = PSR_MODE_EL1h;	\
 }
 
+static inline void perf_switch_user_access(struct mm_struct *mm)
+{

arch/arm64/include/asm/perf_event.h extern unsigned long perf_misc_flags(struct pt_regs *regs); · line 266

Quoted 10 lines
arch/arm64/include/asm/perf_event.h +10 -0
+	if (!IS_ENABLED(CONFIG_PERF_EVENTS))
+		return;
+
+	if (atomic_read(&mm->context.pmu_direct_access)) {
+		write_sysreg(ARMV8_PMU_USERENR_ER|ARMV8_PMU_USERENR_CR,
+			     pmuserenr_el0);
+	} else {
+		write_sysreg(0, pmuserenr_el0);
+	}
+}

PMUSERENR.ER gives RW access to PMSELR_EL0. While we no longer use PMSELR_EL0 in the kernel, we can preempt and migrate userspace tasks between homogeneous CPUs, and presumably need to context-switch this with the task (like we do for TPIDR_EL0 and friends), or clear the register on context-switch to prevent it becoming an unintended covert channel.

These bits also enable AArch32 access. Is there any way an AArch32 task can enable this? If so we should probably block that given we do not support this interface on 32-bit arm.

Quoted 34 lines
arch/arm64/include/asm/perf_event.h +1 -0
+
 #endif
arch/arm64/kernel/perf_event.c +24 -0
index e14f360a7883..8344dc030823 100644
@@ -834,6 +834,41 @@ static int armv8pmu_access_event_idx(struct perf_event *event)
 	return event->hw.idx;
 }
 
+static void refresh_pmuserenr(void *mm)
+{
+	perf_switch_user_access(mm);
+}
+
+static void armv8pmu_event_mapped(struct perf_event *event, struct mm_struct *mm)
+{
+	if (!(event->hw.flags & ARMPMU_EL0_RD_CNTR))
+		return;
+
+	/*
+	 * This function relies on not being called concurrently in two
+	 * tasks in the same mm.  Otherwise one task could observe
+	 * pmu_direct_access > 1 and return all the way back to
+	 * userspace with user access disabled while another task is still
+	 * doing on_each_cpu_mask() to enable user access.
+	 *
+	 * For now, this can't happen because all callers hold mmap_lock
+	 * for write.  If this changes, we'll need a different solution.
+	 */
+	lockdep_assert_held_write(&mm->mmap_lock);
+
+	if (atomic_inc_return(&mm->context.pmu_direct_access) == 1)
+		on_each_cpu(refresh_pmuserenr, mm, 1);

arch/arm64/kernel/perf_event.c static int armv8pmu_access_event_idx(struct perf_event *event) · line 870

Quoted 10 lines
arch/arm64/kernel/perf_event.c +10 -0
+}
+
+static void armv8pmu_event_unmapped(struct perf_event *event, struct mm_struct *mm)
+{
+	if (!(event->hw.flags & ARMPMU_EL0_RD_CNTR))
+		return;
+
+	if (atomic_dec_and_test(&mm->context.pmu_direct_access))
+		on_each_cpu_mask(mm_cpumask(mm), refresh_pmuserenr, NULL, 1);
+}

I didn't think we kept our mm_cpumask() up-to-date in all cases on arm64, so I'm not sure we can use it like this.

Will, can you confirm either way?

Thanks,
Mark.

Quoted 27 lines
arch/arm64/kernel/perf_event.c +7 -0
+
 /*
  * Add an event filter to a given event.
  */
@@ -1058,6 +1093,8 @@ static int armv8_pmu_init(struct arm_pmu *cpu_pmu, char *name,
 	cpu_pmu->filter_match		= armv8pmu_filter_match;
 
 	cpu_pmu->pmu.event_idx		= armv8pmu_access_event_idx;
+	cpu_pmu->pmu.event_mapped	= armv8pmu_event_mapped;
+	cpu_pmu->pmu.event_unmapped	= armv8pmu_event_unmapped;
 
 	cpu_pmu->name			= name;
 	cpu_pmu->map_event		= map_event;
@@ -1218,6 +1255,10 @@ void arch_perf_update_userpage(struct perf_event *event,
 	userpg->cap_user_time = 0;
 	userpg->cap_user_time_zero = 0;
 	userpg->cap_user_time_short = 0;
+	userpg->cap_user_rdpmc = !!(event->hw.flags & ARMPMU_EL0_RD_CNTR);
+
+	if (userpg->cap_user_rdpmc)
+		userpg->pmc_width = armv8pmu_event_is_64bit(event) ? 64 : 32;
 
 	do {
 		rd = sched_clock_read_begin(&seq);
Signature · 1 line
2.25.1
#4 lore raw

Re: [PATCH v4 2/9] arm64: perf: Enable pmu counter direct access for perf event on armv8

Quoted 103 lines · Mark Rutland

Hi Rob,

Thanks for this, and sorry for the long delay since this was last reviewed. Overall this is looking pretty good, but I have a couple of remaining concerns.

Will, I have a query for you below.

Quoted 93 lines · Rob Herring

From: Raphael Gault <[email protected]>

Keep track of event opened with direct access to the hardware counters and modify permissions while they are open.

The strategy used here is the same which x86 uses: everytime an event is mapped, the permissions are set if required. The atomic field added in the mm_context helps keep track of the different event opened and de-activate the permissions when all are unmapped.
We also need to update the permissions in the context switch code so that tasks keep the right permissions.

---
v2:
- Move mapped/unmapped into arm64 code. Fixes arm32.
- Rebase on cap_user_time_short changes

Changes from Raphael's v4:
- Drop homogeneous check
- Disable access for chained counters
- Set pmc_width in user page

---
 arch/arm64/include/asm/mmu.h         |  5 ++++
 arch/arm64/include/asm/mmu_context.h |  2 ++
 arch/arm64/include/asm/perf_event.h  | 14 ++++++++++
 arch/arm64/kernel/perf_event.c       | 41 ++++++++++++++++++++++++++++
 4 files changed, 62 insertions(+)
arch/arm64/include/asm/mmu.h +5 -0
index a7a5ecaa2e83..52cfdb676f06 100644
@@ -19,6 +19,11 @@
 
 typedef struct {
      atomic64_t      id;
+     /*
+      * non-zero if userspace have access to hardware
+      * counters directly.
+      */
+     atomic_t        pmu_direct_access;
 #ifdef CONFIG_COMPAT
      void            *sigpage;
 #endif
arch/arm64/include/asm/mmu_context.h +2 -0
index f2d7537d6f83..d24589ecb07a 100644
@@ -21,6 +21,7 @@
 #include <asm/proc-fns.h>
 #include <asm-generic/mm_hooks.h>
 #include <asm/cputype.h>
+#include <asm/perf_event.h>
 #include <asm/sysreg.h>
 #include <asm/tlbflush.h>
 
@@ -224,6 +225,7 @@ static inline void __switch_mm(struct mm_struct *next)
      }
 
      check_and_switch_context(next);
+     perf_switch_user_access(next);
 }
 
 static inline void
arch/arm64/include/asm/perf_event.h +12 -0
index 2c2d7dbe8a02..a025d9595d51 100644
@@ -8,6 +8,7 @@
 
 #include <asm/stack_pointer.h>
 #include <asm/ptrace.h>
+#include <linux/mm_types.h>
 
 #define      ARMV8_PMU_MAX_COUNTERS  32
 #define      ARMV8_PMU_COUNTER_MASK  (ARMV8_PMU_MAX_COUNTERS - 1)
@@ -251,4 +252,17 @@ extern unsigned long perf_misc_flags(struct pt_regs *regs);
      (regs)->pstate = PSR_MODE_EL1h; \
 }
 
+static inline void perf_switch_user_access(struct mm_struct *mm)
+{
+     if (!IS_ENABLED(CONFIG_PERF_EVENTS))
+             return;
+
+     if (atomic_read(&mm->context.pmu_direct_access)) {
+             write_sysreg(ARMV8_PMU_USERENR_ER|ARMV8_PMU_USERENR_CR,
+                          pmuserenr_el0);
+     } else {
+             write_sysreg(0, pmuserenr_el0);
+     }
Quoted 8 lines

arch/arm64/include/asm/perf_event.h extern unsigned long perf_misc_flags(struct pt_regs *regs); · line 266

Quoted 1 line
arch/arm64/include/asm/perf_event.h +1 -0
+}

PMUSERENR.ER gives RW access to PMSELR_EL0. While we no longer use PMSELR_EL0 in the kernel, we can preempt and migrate userspace tasks between homogeneous CPUs, and presumably need to context-switch this with the task (like we do for TPIDR_EL0 and friends), or clear the register on context-switch to prevent it becoming an unintended covert channel.

Humm, now that I've read up on PMSELR_EL0 I'm now wondering if I should be using PMSELR_EL0 in libperf. If you look at patch 7, the counter read is pretty ugly because there's 32 possible mrs instructions. If PMSELR_EL0 is used, we can have a single read path. It's a msr and mrs vs. a function ptr load, branch/ret, and mrs. I'd guess there's no guarantees on system reg access times, but I'd guess typically the former is more optimal? It certainly simplifies the code which I'd rather have given the limited users.

If I go that route and we don't context switch PMSELR_EL0, reads of PMXEVCNTR_EL0 could be stale. But does that matter? No, because reading PMEVCNTR<n>_EL0 can already be stale and the seq counter will catch that.

Quoted 3 lines

These bits also enable AArch32 access. Is there any way an AArch32 task can enable this? If so we should probably block that given we do not support this interface on 32-bit arm.

I'd assume this works for AArch32 given we don't do anything here to prevent it. I suppose we could look at MMCF_AARCH32 flag in mm_context_t? But is not implemented for arch/arm/ really a reason to disable?

Rob

maintainer
#5 lore raw

Re: [PATCH v4 2/9] arm64: perf: Enable pmu counter direct access for perf event on armv8

In reply to Mark Rutland
Quoted 13 lines · Mark Rutland

arch/arm64/kernel/perf_event.c static int armv8pmu_access_event_idx(struct perf_event *event) · line 870

Quoted 8 lines · Rob Herring
arch/arm64/kernel/perf_event.c +8 -0
+static void armv8pmu_event_unmapped(struct perf_event *event, struct mm_struct *mm)
+{
+	if (!(event->hw.flags & ARMPMU_EL0_RD_CNTR))
+		return;
+
+	if (atomic_dec_and_test(&mm->context.pmu_direct_access))
+		on_each_cpu_mask(mm_cpumask(mm), refresh_pmuserenr, NULL, 1);
+}

I didn't think we kept our mm_cpumask() up-to-date in all cases on arm64, so I'm not sure we can use it like this.

Quoted 1 line

Will, can you confirm either way?

We don't update mm_cpumask() as the cost of the atomic showed up in some benchmarks I did years ago and we've never had any need for the thing anyway because out TLB invalidation is one or all.

Will

#6 lore raw

Re: [PATCH v4 2/9] arm64: perf: Enable pmu counter direct access for perf event on armv8

Quoted 14 lines · Will Deacon
Quoted 13 lines · Mark Rutland

arch/arm64/kernel/perf_event.c static int armv8pmu_access_event_idx(struct perf_event *event) · line 870

Quoted 8 lines · Rob Herring
arch/arm64/kernel/perf_event.c +8 -0
+static void armv8pmu_event_unmapped(struct perf_event *event, struct mm_struct *mm)
+{
+	if (!(event->hw.flags & ARMPMU_EL0_RD_CNTR))
+		return;
+
+	if (atomic_dec_and_test(&mm->context.pmu_direct_access))
+		on_each_cpu_mask(mm_cpumask(mm), refresh_pmuserenr, NULL, 1);
+}

I didn't think we kept our mm_cpumask() up-to-date in all cases on arm64, so I'm not sure we can use it like this.

Quoted 5 lines
Quoted 1 line

Will, can you confirm either way?

We don't update mm_cpumask() as the cost of the atomic showed up in some benchmarks I did years ago and we've never had any need for the thing anyway because out TLB invalidation is one or all.

That's good because we're also passing NULL instead of mm which would crash. So it must be more than it's not up to date, but it's always 0. It looks like event_mapped on x86 uses mm_cpumask(mm) which I guess was dropped when copying this code as it didn't work... For reference, the x86 version of this originated in commit 7911d3f7af14a6.

I'm not clear on why we need to update pmuserenr_el0 here anyways. To get here userspace has to mmap the event and then unmmap it. If we did nothing, then counter accesses would not fault until the next context switch.

If you all have any ideas, I'm all ears. I'm not a scheduler nor perf hacker. ;)

Rob

#7 lore raw

Re: [PATCH v4 2/9] arm64: perf: Enable pmu counter direct access for perf event on armv8

Quoted 27 lines · Rob Herring
Quoted 14 lines · Will Deacon
Quoted 13 lines · Mark Rutland

arch/arm64/kernel/perf_event.c static int armv8pmu_access_event_idx(struct perf_event *event) · line 870

Quoted 8 lines · Rob Herring
arch/arm64/kernel/perf_event.c +8 -0
+static void armv8pmu_event_unmapped(struct perf_event *event, struct mm_struct *mm)
+{
+	if (!(event->hw.flags & ARMPMU_EL0_RD_CNTR))
+		return;
+
+	if (atomic_dec_and_test(&mm->context.pmu_direct_access))
+		on_each_cpu_mask(mm_cpumask(mm), refresh_pmuserenr, NULL, 1);
+}

I didn't think we kept our mm_cpumask() up-to-date in all cases on arm64, so I'm not sure we can use it like this.

Quoted 5 lines
Quoted 1 line

Will, can you confirm either way?

We don't update mm_cpumask() as the cost of the atomic showed up in some benchmarks I did years ago and we've never had any need for the thing anyway because out TLB invalidation is one or all.

That's good because we're also passing NULL instead of mm which would crash. So it must be more than it's not up to date, but it's always 0. It looks like event_mapped on x86 uses mm_cpumask(mm) which I guess was dropped when copying this code as it didn't work... For reference, the x86 version of this originated in commit 7911d3f7af14a6.

Quoted 7 lines

I'm not clear on why we need to update pmuserenr_el0 here anyways. To get here userspace has to mmap the event and then unmmap it. If we did nothing, then counter accesses would not fault until the next context switch.

If you all have any ideas, I'm all ears. I'm not a scheduler nor perf hacker. ;)

Here's another issue that needs addressing:

https://lore.kernel.org/lkml/[email protected]/

Rob

#8 lore raw

Re: [PATCH v4 2/9] arm64: perf: Enable pmu counter direct access for perf event on armv8

In reply to Rob Herring
Quoted 27 lines · Rob Herring
Quoted 14 lines · Will Deacon
Quoted 13 lines · Mark Rutland

arch/arm64/kernel/perf_event.c static int armv8pmu_access_event_idx(struct perf_event *event) · line 870

Quoted 8 lines · Rob Herring
arch/arm64/kernel/perf_event.c +8 -0
+static void armv8pmu_event_unmapped(struct perf_event *event, struct mm_struct *mm)
+{
+	if (!(event->hw.flags & ARMPMU_EL0_RD_CNTR))
+		return;
+
+	if (atomic_dec_and_test(&mm->context.pmu_direct_access))
+		on_each_cpu_mask(mm_cpumask(mm), refresh_pmuserenr, NULL, 1);
+}

I didn't think we kept our mm_cpumask() up-to-date in all cases on arm64, so I'm not sure we can use it like this.

Quoted 5 lines
Quoted 1 line

Will, can you confirm either way?

We don't update mm_cpumask() as the cost of the atomic showed up in some benchmarks I did years ago and we've never had any need for the thing anyway because out TLB invalidation is one or all.

That's good because we're also passing NULL instead of mm which would crash. So it must be more than it's not up to date, but it's always 0. It looks like event_mapped on x86 uses mm_cpumask(mm) which I guess was dropped when copying this code as it didn't work... For reference, the x86 version of this originated in commit 7911d3f7af14a6.

Quoted 7 lines

I'm not clear on why we need to update pmuserenr_el0 here anyways. To get here userspace has to mmap the event and then unmmap it. If we did nothing, then counter accesses would not fault until the next context switch.

If you all have any ideas, I'm all ears. I'm not a scheduler nor perf hacker. ;)

Mark, Will, any thoughts on this?

Rob

#9 lore raw

Re: [PATCH v4 2/9] arm64: perf: Enable pmu counter direct access for perf event on armv8

Quoted 4 lines · Rob Herring
Quoted 3 lines · Rob Herring
Quoted 2 lines · Will Deacon
Quoted 1 line · Mark Rutland

On Thu, Oct 01, 2020 at 09:01:09AM -0500, Rob Herring wrote:

Quoted 8 lines
Quoted 8 lines
Quoted 8 lines
Quoted 8 lines

arch/arm64/kernel/perf_event.c static int armv8pmu_access_event_idx(struct perf_event *event) · line 870

Quoted 8 lines
arch/arm64/kernel/perf_event.c +8 -0
+static void armv8pmu_event_unmapped(struct perf_event *event, struct mm_struct *mm)
+{
+	if (!(event->hw.flags & ARMPMU_EL0_RD_CNTR))
+		return;
+
+	if (atomic_dec_and_test(&mm->context.pmu_direct_access))
+		on_each_cpu_mask(mm_cpumask(mm), refresh_pmuserenr, NULL, 1);
+}

Bump on this again... :)

Quoted 16 lines
Quoted 16 lines
Quoted 9 lines
Quoted 5 lines

I didn't think we kept our mm_cpumask() up-to-date in all cases on arm64, so I'm not sure we can use it like this.

Will, can you confirm either way?

We don't update mm_cpumask() as the cost of the atomic showed up in some benchmarks I did years ago and we've never had any need for the thing anyway because out TLB invalidation is one or all.

That's good because we're also passing NULL instead of mm which would crash. So it must be more than it's not up to date, but it's always 0. It looks like event_mapped on x86 uses mm_cpumask(mm) which I guess was dropped when copying this code as it didn't work... For reference, the x86 version of this originated in commit 7911d3f7af14a6.

Quoted 4 lines
Quoted 4 lines

I'm not clear on why we need to update pmuserenr_el0 here anyways. To get here userspace has to mmap the event and then unmmap it. If we did nothing, then counter accesses would not fault until the next context switch.

Okay, I've come up with a test case where I can trigger this. It's a bit convoluted IMO where the thread doing the mmap is different thread from reading the counter. Seems like it would be better if we just disabled user access if we're not doing calling thread monitoring. We could always loosen that restriction later. x86 OTOH was wide open with access globally enabled and this hunk of code was part of restricting it some.

Quoted 5 lines
Quoted 3 lines

If you all have any ideas, I'm all ears. I'm not a scheduler nor perf hacker. ;)

Mark, Will, any thoughts on this?

Any reason, this would not work:

static void refresh_pmuserenr(void *mm)

{
	if (mm == current->active_mm)
		perf_switch_user_access(mm);
}

The downside is we'd be doing an IPI on *all* cores for a PMU, not just the ones in mm_cpumask() (if that was accurate). But this isn't a fast path.

Rob

#10 lore raw

PATCH v4 3/9 tools/include: Add an initial math64.h

In reply to Rob Herring

Add an initial math64.h similar to linux/math64.h with functions mul_u64_u64_div64() and mul_u64_u32_shr(). This isn't a direct copy of include/linux/math64.h as that doesn't define mul_u64_u64_div64().

Implementation was written by Peter Zilkstra based on linux/math64.h and div64.h[1]. The original implementation was not optimal on arm64 as __int128 division is not optimal with a call out to __udivti3, so I dropped the __int128 variant of mul_u64_u64_div64().

[1] https://lore.kernel.org/lkml/[email protected]/

Cc Peter Zijlstra <[email protected]>
---
 tools/include/linux/math64.h | 75 ++++++++++++++++++++++++++++++++++++
 1 file changed, 75 insertions(+)
 create mode 100644 tools/include/linux/math64.h
tools/include/linux/math64.h new file +75 -0
new file mode 100644
index 000000000000..4ad45d5943dc
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_MATH64_H
+#define _LINUX_MATH64_H
+
+#include <linux/types.h>
+
+#ifdef __x86_64__
+static inline u64 mul_u64_u64_div64(u64 a, u64 b, u64 c)
+{
+	u64 q;
+
+	asm ("mulq %2; divq %3" : "=a" (q)
+				: "a" (a), "rm" (b), "rm" (c)
+				: "rdx");
+
+	return q;
+}
+#define mul_u64_u64_div64 mul_u64_u64_div64
+#endif
+
+#ifdef __SIZEOF_INT128__
+static inline u64 mul_u64_u32_shr(u64 a, u32 b, unsigned int shift)
+{
+	return (u64)(((unsigned __int128)a * b) >> shift);
+}
+
+#else
+
+#ifdef __i386__
+static inline u64 mul_u32_u32(u32 a, u32 b)
+{
+	u32 high, low;
+
+	asm ("mull %[b]" : "=a" (low), "=d" (high)
+			 : [a] "a" (a), [b] "rm" (b) );
+
+	return low | ((u64)high) << 32;
+}
+#else
+static inline u64 mul_u32_u32(u32 a, u32 b)
+{
+	return (u64)a * b;
+}
+#endif
+
+static inline u64 mul_u64_u32_shr(u64 a, u32 b, unsigned int shift)
+{
+	u32 ah, al;
+	u64 ret;
+
+	al = a;
+	ah = a >> 32;
+
+	ret = mul_u32_u32(al, b) >> shift;
+	if (ah)
+		ret += mul_u32_u32(ah, b) << (32 - shift);
+
+	return ret;
+}
+
+#endif	/* __SIZEOF_INT128__ */
+
+#ifndef mul_u64_u64_div64
+static inline u64 mul_u64_u64_div64(u64 a, u64 b, u64 c)
+{
+	u64 quot, rem;
+
+	quot = a / c;
+	rem = a % c;
+
+	return quot * b + (rem * b) / c;
+}
+#endif
+
+#endif /* _LINUX_MATH64_H */
Signature · 1 line
2.25.1
#11 lore raw

PATCH v4 4/9 libperf: Add libperf_evsel__mmap()

In reply to Rob Herring

In order to support usersapce access, an event must be mmapped. While there's already mmap support for evlist, the usecase is a bit different than the self monitoring with userspace access. So let's add a new perf_evsel__mmap() function to mmap an evsel. This allows implementing userspace access as a fastpath for perf_evsel__read().

The mmapped address is returned by perf_evsel__mmap() primarily for users/tests to check if userspace access is enabled.

---
v4:
- Change perf_evsel__mmap size to pages instead of bytes
v3:
- New patch split out from user access patch

mmap fix

---
 tools/lib/perf/Documentation/libperf.txt |  1 +
 tools/lib/perf/evsel.c                   | 31 ++++++++++++++++++++++++
 tools/lib/perf/include/internal/evsel.h  |  2 ++
 tools/lib/perf/include/perf/evsel.h      |  2 ++
 tools/lib/perf/libperf.map               |  1 +
 5 files changed, 37 insertions(+)
tools/lib/perf/Documentation/libperf.txt +1 -0
index 0c74c30ed23a..d2a541e7a7ec 100644
@@ -136,6 +136,7 @@ SYNOPSIS
                        struct perf_thread_map *threads);
   void perf_evsel__close(struct perf_evsel *evsel);
   void perf_evsel__close_cpu(struct perf_evsel *evsel, int cpu);
+  void *perf_evsel__mmap(struct perf_evsel *evsel, int pages);
   int perf_evsel__read(struct perf_evsel *evsel, int cpu, int thread,
                        struct perf_counts_values *count);
   int perf_evsel__enable(struct perf_evsel *evsel);
tools/lib/perf/evsel.c +31 -0
index 4dc06289f4c7..42eaf3c18981 100644
@@ -11,10 +11,12 @@
 #include <stdlib.h>
 #include <internal/xyarray.h>
 #include <internal/cpumap.h>
+#include <internal/mmap.h>
 #include <internal/threadmap.h>
 #include <internal/lib.h>
 #include <linux/string.h>
 #include <sys/ioctl.h>
+#include <sys/mman.h>
 
 void perf_evsel__init(struct perf_evsel *evsel, struct perf_event_attr *attr)
 {
@@ -156,6 +158,35 @@ void perf_evsel__close_cpu(struct perf_evsel *evsel, int cpu)
 	perf_evsel__close_fd_cpu(evsel, cpu);
 }
 
+void *perf_evsel__mmap(struct perf_evsel *evsel, int pages)
+{
+	int ret;
+	struct perf_mmap *map;
+	struct perf_mmap_param mp = {
+		.prot = PROT_READ | PROT_WRITE,
+	};
+
+	if (FD(evsel, 0, 0) < 0)
+		return NULL;
+
+	mp.mask = (pages * page_size) - 1;
+
+	map = zalloc(sizeof(*map));
+	if (!map)
+		return NULL;
+
+	perf_mmap__init(map, NULL, false, NULL);
+
+	ret = perf_mmap__mmap(map, &mp, FD(evsel, 0, 0), 0);
+	if (ret) {
+		free(map);
+		return NULL;
+	}
+
+	evsel->mmap = map;
+	return map->base;
+}
+
 int perf_evsel__read_size(struct perf_evsel *evsel)
 {
 	u64 read_format = evsel->attr.read_format;
tools/lib/perf/include/internal/evsel.h +2 -0
index 1ffd083b235e..a7985dbb68ff 100644
@@ -9,6 +9,7 @@
 
 struct perf_cpu_map;
 struct perf_thread_map;
+struct perf_mmap;
 struct xyarray;
 
 /*
@@ -40,6 +41,7 @@ struct perf_evsel {
 	struct perf_cpu_map	*cpus;
 	struct perf_cpu_map	*own_cpus;
 	struct perf_thread_map	*threads;
+	struct perf_mmap	*mmap;
 	struct xyarray		*fd;
 	struct xyarray		*sample_id;
 	u64			*id;
tools/lib/perf/include/perf/evsel.h +2 -0
index c82ec39a4ad0..1b1534439334 100644
@@ -3,6 +3,7 @@
 #define __LIBPERF_EVSEL_H
 
 #include <stdint.h>
+#include <stddef.h>
 #include <perf/core.h>
 
 struct perf_evsel;
@@ -27,6 +28,7 @@ LIBPERF_API int perf_evsel__open(struct perf_evsel *evsel, struct perf_cpu_map *
 				 struct perf_thread_map *threads);
 LIBPERF_API void perf_evsel__close(struct perf_evsel *evsel);
 LIBPERF_API void perf_evsel__close_cpu(struct perf_evsel *evsel, int cpu);
+LIBPERF_API void *perf_evsel__mmap(struct perf_evsel *evsel, int pages);
 LIBPERF_API int perf_evsel__read(struct perf_evsel *evsel, int cpu, int thread,
 				 struct perf_counts_values *count);
 LIBPERF_API int perf_evsel__enable(struct perf_evsel *evsel);
tools/lib/perf/libperf.map +1 -0
index 7be1af8a546c..733a0647be8b 100644
@@ -23,6 +23,7 @@ LIBPERF_0.0.1 {
 		perf_evsel__disable;
 		perf_evsel__open;
 		perf_evsel__close;
+		perf_evsel__mmap;
 		perf_evsel__read;
 		perf_evsel__cpus;
 		perf_evsel__threads;
Signature · 1 line
2.25.1
#12 lore raw

Re: [PATCH v4 4/9] libperf: Add libperf_evsel__mmap()

On Thu, Oct 01, 2020 at 09:01:11AM -0500, Rob Herring wrote:

SNIP

Quoted 11 lines
tools/lib/perf/evsel.c +10 -0
+void *perf_evsel__mmap(struct perf_evsel *evsel, int pages)
+{
+	int ret;
+	struct perf_mmap *map;
+	struct perf_mmap_param mp = {
+		.prot = PROT_READ | PROT_WRITE,
+	};
+
+	if (FD(evsel, 0, 0) < 0)
+		return NULL;

tools/lib/perf/evsel.c void perf_evsel__close_cpu(struct perf_evsel *evsel, int cpu) · line 180

Quoted 10 lines
tools/lib/perf/evsel.c +10 -0
+
+	mp.mask = (pages * page_size) - 1;
+
+	map = zalloc(sizeof(*map));
+	if (!map)
+		return NULL;
+
+	perf_mmap__init(map, NULL, false, NULL);
+
+	ret = perf_mmap__mmap(map, &mp, FD(evsel, 0, 0), 0);

hum, so you map event for FD(0,0) but later in perf_evsel__read you allow to read any cpu/thread combination ending up reading data from FD(0,0) map:

	int perf_evsel__read(struct perf_evsel *evsel, int cpu, int thread,
			     struct perf_counts_values *count)
	{
		size_t size = perf_evsel__read_size(evsel);

		memset(count, 0, sizeof(*count));

		if (FD(evsel, cpu, thread) < 0)
			return -EINVAL;

		if (evsel->mmap && !perf_mmap__read_self(evsel->mmap, count))
			return 0;

I think we should either check cpu == 0, thread == 0, or make it general and store perf_evsel::mmap in xyarray as we do for fds

thanks,
jirka

#13 lore raw

Re: [PATCH v4 4/9] libperf: Add libperf_evsel__mmap()

Quoted 45 lines · Jiri Olsa

On Thu, Oct 01, 2020 at 09:01:11AM -0500, Rob Herring wrote:

SNIP

Quoted 11 lines
tools/lib/perf/evsel.c +10 -0
+void *perf_evsel__mmap(struct perf_evsel *evsel, int pages)
+{
+     int ret;
+     struct perf_mmap *map;
+     struct perf_mmap_param mp = {
+             .prot = PROT_READ | PROT_WRITE,
+     };
+
+     if (FD(evsel, 0, 0) < 0)
+             return NULL;

tools/lib/perf/evsel.c void perf_evsel__close_cpu(struct perf_evsel *evsel, int cpu) · line 180

Quoted 10 lines
tools/lib/perf/evsel.c +10 -0
+
+     mp.mask = (pages * page_size) - 1;
+
+     map = zalloc(sizeof(*map));
+     if (!map)
+             return NULL;
+
+     perf_mmap__init(map, NULL, false, NULL);
+
+     ret = perf_mmap__mmap(map, &mp, FD(evsel, 0, 0), 0);

hum, so you map event for FD(0,0) but later in perf_evsel__read you allow to read any cpu/thread combination ending up reading data from FD(0,0) map:

        int perf_evsel__read(struct perf_evsel *evsel, int cpu, int thread,
                             struct perf_counts_values *count)
        {
                size_t size = perf_evsel__read_size(evsel);

                memset(count, 0, sizeof(*count));

                if (FD(evsel, cpu, thread) < 0)
                        return -EINVAL;

                if (evsel->mmap && !perf_mmap__read_self(evsel->mmap, count))
                        return 0;
Quoted 2 lines

I think we should either check cpu == 0, thread == 0, or make it general and store perf_evsel::mmap in xyarray as we do for fds

The mmapped read will actually fail and then we fallback here. My main concern though is adding more overhead on a feature that's meant to be low overhead (granted, it's not much). Maybe we could add checks on the mmap that we've opened the event with pid == 0 and cpu == -1 (so only 1 FD)?

Rob

#14 lore raw

Re: [PATCH v4 4/9] libperf: Add libperf_evsel__mmap()

Quoted 46 lines · Rob Herring
Quoted 45 lines · Jiri Olsa

On Thu, Oct 01, 2020 at 09:01:11AM -0500, Rob Herring wrote:

SNIP

Quoted 11 lines
tools/lib/perf/evsel.c +10 -0
+void *perf_evsel__mmap(struct perf_evsel *evsel, int pages)
+{
+     int ret;
+     struct perf_mmap *map;
+     struct perf_mmap_param mp = {
+             .prot = PROT_READ | PROT_WRITE,
+     };
+
+     if (FD(evsel, 0, 0) < 0)
+             return NULL;

tools/lib/perf/evsel.c void perf_evsel__close_cpu(struct perf_evsel *evsel, int cpu) · line 180

Quoted 10 lines
tools/lib/perf/evsel.c +10 -0
+
+     mp.mask = (pages * page_size) - 1;
+
+     map = zalloc(sizeof(*map));
+     if (!map)
+             return NULL;
+
+     perf_mmap__init(map, NULL, false, NULL);
+
+     ret = perf_mmap__mmap(map, &mp, FD(evsel, 0, 0), 0);

hum, so you map event for FD(0,0) but later in perf_evsel__read you allow to read any cpu/thread combination ending up reading data from FD(0,0) map:

        int perf_evsel__read(struct perf_evsel *evsel, int cpu, int thread,
                             struct perf_counts_values *count)
        {
                size_t size = perf_evsel__read_size(evsel);

                memset(count, 0, sizeof(*count));

                if (FD(evsel, cpu, thread) < 0)
                        return -EINVAL;

                if (evsel->mmap && !perf_mmap__read_self(evsel->mmap, count))
                        return 0;
Quoted 8 lines
Quoted 2 lines

I think we should either check cpu == 0, thread == 0, or make it general and store perf_evsel::mmap in xyarray as we do for fds

The mmapped read will actually fail and then we fallback here. My main concern though is adding more overhead on a feature that's meant to be low overhead (granted, it's not much). Maybe we could add checks on the mmap that we've opened the event with pid == 0 and cpu == -1 (so only 1 FD)?

but then you limit this just for single fd.. having mmap as xyarray would not be that bad and perf_evsel__mmap will call perf_mmap__mmap for each defined cpu/thread .. so it depends on user how fast this will be - how many maps needs to be created/mmaped

jirka

#15 lore raw

Re: [PATCH v4 4/9] libperf: Add libperf_evsel__mmap()

Quoted 57 lines · Jiri Olsa
Quoted 46 lines · Rob Herring
Quoted 45 lines · Jiri Olsa

On Thu, Oct 01, 2020 at 09:01:11AM -0500, Rob Herring wrote:

SNIP

Quoted 11 lines
tools/lib/perf/evsel.c +10 -0
+void *perf_evsel__mmap(struct perf_evsel *evsel, int pages)
+{
+     int ret;
+     struct perf_mmap *map;
+     struct perf_mmap_param mp = {
+             .prot = PROT_READ | PROT_WRITE,
+     };
+
+     if (FD(evsel, 0, 0) < 0)
+             return NULL;

tools/lib/perf/evsel.c void perf_evsel__close_cpu(struct perf_evsel *evsel, int cpu) · line 180

Quoted 10 lines
tools/lib/perf/evsel.c +10 -0
+
+     mp.mask = (pages * page_size) - 1;
+
+     map = zalloc(sizeof(*map));
+     if (!map)
+             return NULL;
+
+     perf_mmap__init(map, NULL, false, NULL);
+
+     ret = perf_mmap__mmap(map, &mp, FD(evsel, 0, 0), 0);

hum, so you map event for FD(0,0) but later in perf_evsel__read you allow to read any cpu/thread combination ending up reading data from FD(0,0) map:

        int perf_evsel__read(struct perf_evsel *evsel, int cpu, int thread,
                             struct perf_counts_values *count)
        {
                size_t size = perf_evsel__read_size(evsel);

                memset(count, 0, sizeof(*count));

                if (FD(evsel, cpu, thread) < 0)
                        return -EINVAL;

                if (evsel->mmap && !perf_mmap__read_self(evsel->mmap, count))
                        return 0;
Quoted 8 lines
Quoted 2 lines

I think we should either check cpu == 0, thread == 0, or make it general and store perf_evsel::mmap in xyarray as we do for fds

The mmapped read will actually fail and then we fallback here. My main concern though is adding more overhead on a feature that's meant to be low overhead (granted, it's not much). Maybe we could add checks on the mmap that we've opened the event with pid == 0 and cpu == -1 (so only 1 FD)?

Quoted 4 lines

but then you limit this just for single fd.. having mmap as xyarray would not be that bad and perf_evsel__mmap will call perf_mmap__mmap for each defined cpu/thread .. so it depends on user how fast this will be - how many maps needs to be created/mmaped

Given userspace access fails for anything other than the calling thread and all cpus, how would more than 1 mmap be useful here?

If we did want multiple mmaps, wouldn't we just use the evlist API in that case? It already does all that.

Rob

#16 lore raw

Re: [PATCH v4 4/9] libperf: Add libperf_evsel__mmap()

Quoted 58 lines · Rob Herring
Quoted 57 lines · Jiri Olsa
Quoted 54 lines · Rob Herring
Quoted 45 lines · Jiri Olsa

On Thu, Oct 01, 2020 at 09:01:11AM -0500, Rob Herring wrote:

SNIP

Quoted 11 lines
tools/lib/perf/evsel.c +10 -0
+void *perf_evsel__mmap(struct perf_evsel *evsel, int pages)
+{
+     int ret;
+     struct perf_mmap *map;
+     struct perf_mmap_param mp = {
+             .prot = PROT_READ | PROT_WRITE,
+     };
+
+     if (FD(evsel, 0, 0) < 0)
+             return NULL;

tools/lib/perf/evsel.c void perf_evsel__close_cpu(struct perf_evsel *evsel, int cpu) · line 180

Quoted 10 lines
tools/lib/perf/evsel.c +10 -0
+
+     mp.mask = (pages * page_size) - 1;
+
+     map = zalloc(sizeof(*map));
+     if (!map)
+             return NULL;
+
+     perf_mmap__init(map, NULL, false, NULL);
+
+     ret = perf_mmap__mmap(map, &mp, FD(evsel, 0, 0), 0);

hum, so you map event for FD(0,0) but later in perf_evsel__read you allow to read any cpu/thread combination ending up reading data from FD(0,0) map:

        int perf_evsel__read(struct perf_evsel *evsel, int cpu, int thread,
                             struct perf_counts_values *count)
        {
                size_t size = perf_evsel__read_size(evsel);

                memset(count, 0, sizeof(*count));

                if (FD(evsel, cpu, thread) < 0)
                        return -EINVAL;

                if (evsel->mmap && !perf_mmap__read_self(evsel->mmap, count))
                        return 0;
Quoted 2 lines

I think we should either check cpu == 0, thread == 0, or make it general and store perf_evsel::mmap in xyarray as we do for fds

The mmapped read will actually fail and then we fallback here. My main concern though is adding more overhead on a feature that's meant to be low overhead (granted, it's not much). Maybe we could add checks on the mmap that we've opened the event with pid == 0 and cpu == -1 (so only 1 FD)?

Quoted 7 lines
Quoted 4 lines

but then you limit this just for single fd.. having mmap as xyarray would not be that bad and perf_evsel__mmap will call perf_mmap__mmap for each defined cpu/thread .. so it depends on user how fast this will be - how many maps needs to be created/mmaped

Given userspace access fails for anything other than the calling thread and all cpus, how would more than 1 mmap be useful here?

I'm not sure what you mean by fail in here.. you need mmap for each event fd you want to read from

in the example below we read stats from all cpus via perf_evsel__read, if we insert this call after perf_evsel__open:

  perf_evsel__mmap(cpus, NULL);

that maps page for each event, then perf_evsel__read could go through the fast code, no?

Quoted 3 lines

If we did want multiple mmaps, wouldn't we just use the evlist API in that case? It already does all that.

we could, but I see this as a separate perf_evsel interface, and if we allow to have perf_evsel__mmap I think it should follow the cpus/threads it's open for

jirka

---
static int test_stat_cpu(void)

{
        struct perf_cpu_map *cpus;
        struct perf_evsel *evsel;
        struct perf_event_attr attr = {
                .type   = PERF_TYPE_SOFTWARE,
                .config = PERF_COUNT_SW_CPU_CLOCK,
        };
        int err, cpu, tmp;

        cpus = perf_cpu_map__new(NULL);
        __T("failed to create cpus", cpus);

        evsel = perf_evsel__new(&attr);
        __T("failed to create evsel", evsel);

        err = perf_evsel__open(evsel, cpus, NULL);
        __T("failed to open evsel", err == 0);

        perf_cpu_map__for_each_cpu(cpu, tmp, cpus) {
                struct perf_counts_values counts = { .val = 0 };

                perf_evsel__read(evsel, cpu, 0, &counts);
                __T("failed to read value for evsel", counts.val != 0);
        }

        perf_evsel__close(evsel);
        perf_evsel__delete(evsel);

        perf_cpu_map__put(cpus);
        return 0;
}
#17 lore raw

Re: [PATCH v4 4/9] libperf: Add libperf_evsel__mmap()

Quoted 65 lines · Jiri Olsa
Quoted 63 lines · Rob Herring
Quoted 61 lines · Jiri Olsa
Quoted 46 lines · Rob Herring
Quoted 45 lines · Jiri Olsa

On Thu, Oct 01, 2020 at 09:01:11AM -0500, Rob Herring wrote:

SNIP

Quoted 11 lines
(quoted diff) +10 -0
+void *perf_evsel__mmap(struct perf_evsel *evsel, int pages)
+{
+     int ret;
+     struct perf_mmap *map;
+     struct perf_mmap_param mp = {
+             .prot = PROT_READ | PROT_WRITE,
+     };
+
+     if (FD(evsel, 0, 0) < 0)
+             return NULL;

Review

Quoted 10 lines
(quoted diff) +10 -0
+
+     mp.mask = (pages * page_size) - 1;
+
+     map = zalloc(sizeof(*map));
+     if (!map)
+             return NULL;
+
+     perf_mmap__init(map, NULL, false, NULL);
+
+     ret = perf_mmap__mmap(map, &mp, FD(evsel, 0, 0), 0);

hum, so you map event for FD(0,0) but later in perf_evsel__read you allow to read any cpu/thread combination ending up reading data from FD(0,0) map:

        int perf_evsel__read(struct perf_evsel *evsel, int cpu, int thread,
                             struct perf_counts_values *count)
        {
                size_t size = perf_evsel__read_size(evsel);

                memset(count, 0, sizeof(*count));

                if (FD(evsel, cpu, thread) < 0)
                        return -EINVAL;

                if (evsel->mmap && !perf_mmap__read_self(evsel->mmap, count))
                        return 0;
Quoted 8 lines
Quoted 2 lines

I think we should either check cpu == 0, thread == 0, or make it general and store perf_evsel::mmap in xyarray as we do for fds

The mmapped read will actually fail and then we fallback here. My main concern though is adding more overhead on a feature that's meant to be low overhead (granted, it's not much). Maybe we could add checks on the mmap that we've opened the event with pid == 0 and cpu == -1 (so only 1 FD)?

but then you limit this just for single fd.. having mmap as xyarray would not be that bad and perf_evsel__mmap will call perf_mmap__mmap for each defined cpu/thread .. so it depends on user how fast this will be - how many maps needs to be created/mmaped

Quoted 5 lines
Quoted 2 lines

Given userspace access fails for anything other than the calling thread and all cpus, how would more than 1 mmap be useful here?

I'm not sure what you mean by fail in here.. you need mmap for each event fd you want to read from

Yes, but that's one mmap per event (evsel) which is different than per cpu/thread.

Quoted 7 lines

in the example below we read stats from all cpus via perf_evsel__read, if we insert this call after perf_evsel__open:

  perf_evsel__mmap(cpus, NULL);

that maps page for each event, then perf_evsel__read could go through the fast code, no?

No, because we're not self-monitoring (pid == 0 and cpu == -1). With the following change:

(quoted diff) +0 -0

b/tools/lib/perf/tests/test-evsel.c
index eeca8203d73d..1fca9c121f7c 100644

tools/lib/perf/tests/test-evsel.c +10 -0
@@ -17,6 +17,7 @@ static int test_stat_cpu(void)
 {
        struct perf_cpu_map *cpus;
        struct perf_evsel *evsel;
+       struct perf_event_mmap_page *pc;
        struct perf_event_attr attr = {
                .type   = PERF_TYPE_SOFTWARE,
                .config = PERF_COUNT_SW_CPU_CLOCK,
@@ -32,6 +33,15 @@ static int test_stat_cpu(void)
        err = perf_evsel__open(evsel, cpus, NULL);
        __T("failed to open evsel", err == 0);
 
+       pc = perf_evsel__mmap(evsel, 0);
+       __T("failed to mmap evsel", pc);
+
+#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__)
+       __T("userspace counter access not supported", pc->cap_user_rdpmc);
+       __T("userspace counter access not enabled", pc->index);
+       __T("userspace counter width not set", pc->pmc_width >= 32);
+#endif
+
        perf_cpu_map__for_each_cpu(cpu, tmp, cpus) {
                struct perf_counts_values counts = { .val = 0 };
 

I get:

- running test-evsel.c...FAILED test-evsel.c:40 userspace counter access not supported

If I set it to pid==0, userspace counter access is also disabled.

Maybe there is some use for mmap beyond fast path read for self-monitoring or what evlist mmap does, but I don't know what that would be.

Note that we could get rid of the mmap API and just do the mmap behind the scenes whenever we get the magic setup that works. The main downside with that is you can't check if the fast path is enabled or not (though we could have a perf_evsel__is_fast_read(evsel, cpu, thread) instead).

Rob

#18 lore raw

Re: [PATCH v4 4/9] libperf: Add libperf_evsel__mmap()

On Tue, Oct 20, 2020 at 12:11:47PM -0500, Rob Herring wrote:

SNIP

Quoted 12 lines
Quoted 12 lines
Quoted 12 lines
Quoted 11 lines
Quoted 6 lines

The mmapped read will actually fail and then we fallback here. My main concern though is adding more overhead on a feature that's meant to be low overhead (granted, it's not much). Maybe we could add checks on the mmap that we've opened the event with pid == 0 and cpu == -1 (so only 1 FD)?

but then you limit this just for single fd.. having mmap as xyarray would not be that bad and perf_evsel__mmap will call perf_mmap__mmap for each defined cpu/thread .. so it depends on user how fast this will be - how many maps needs to be created/mmaped

Quoted 8 lines
Quoted 5 lines
Quoted 2 lines

Given userspace access fails for anything other than the calling thread and all cpus, how would more than 1 mmap be useful here?

I'm not sure what you mean by fail in here.. you need mmap for each event fd you want to read from

Yes, but that's one mmap per event (evsel) which is different than per cpu/thread.

right, and you need mmap per fd IIUC

Quoted 27 lines
Quoted 8 lines

in the example below we read stats from all cpus via perf_evsel__read, if we insert this call after perf_evsel__open:

  perf_evsel__mmap(cpus, NULL);

that maps page for each event, then perf_evsel__read could go through the fast code, no?

No, because we're not self-monitoring (pid == 0 and cpu == -1). With the following change:

tools/lib/perf/tests/test-evsel.c +1 -0
index eeca8203d73d..1fca9c121f7c 100644
@@ -17,6 +17,7 @@ static int test_stat_cpu(void)
 {
        struct perf_cpu_map *cpus;
        struct perf_evsel *evsel;
+       struct perf_event_mmap_page *pc;
        struct perf_event_attr attr = {
                .type   = PERF_TYPE_SOFTWARE,
                .config = PERF_COUNT_SW_CPU_CLOCK,
@@ -32,6 +33,15 @@ static int test_stat_cpu(void)
        err = perf_evsel__open(evsel, cpus, NULL);

tools/lib/perf/tests/test-evsel.c static int test_stat_cpu(void) · line 43

Quoted 10 lines
tools/lib/perf/tests/test-evsel.c +8 -0
        __T("failed to open evsel", err == 0);
 
+       pc = perf_evsel__mmap(evsel, 0);
+       __T("failed to mmap evsel", pc);
+
+#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__)
+       __T("userspace counter access not supported", pc->cap_user_rdpmc);
+       __T("userspace counter access not enabled", pc->index);
+       __T("userspace counter width not set", pc->pmc_width >= 32);
+#endif

I'll need to check, I'm surprised this would depend on the way you open the event

jirka

Quoted 23 lines
tools/lib/perf/tests/test-evsel.c +1 -0
+
        perf_cpu_map__for_each_cpu(cpu, tmp, cpus) {
                struct perf_counts_values counts = { .val = 0 };
 

I get:

- running test-evsel.c...FAILED test-evsel.c:40 userspace counter access not supported

If I set it to pid==0, userspace counter access is also disabled.

Maybe there is some use for mmap beyond fast path read for self-monitoring or what evlist mmap does, but I don't know what that would be.

Note that we could get rid of the mmap API and just do the mmap behind the scenes whenever we get the magic setup that works. The main downside with that is you can't check if the fast path is enabled or not (though we could have a perf_evsel__is_fast_read(evsel, cpu, thread) instead).

Rob

#19 lore raw

Re: [PATCH v4 4/9] libperf: Add libperf_evsel__mmap()

Quoted 57 lines · Jiri Olsa

On Tue, Oct 20, 2020 at 12:11:47PM -0500, Rob Herring wrote:

SNIP

Quoted 12 lines
Quoted 12 lines
Quoted 12 lines
Quoted 11 lines
Quoted 6 lines

The mmapped read will actually fail and then we fallback here. My main concern though is adding more overhead on a feature that's meant to be low overhead (granted, it's not much). Maybe we could add checks on the mmap that we've opened the event with pid == 0 and cpu == -1 (so only 1 FD)?

but then you limit this just for single fd.. having mmap as xyarray would not be that bad and perf_evsel__mmap will call perf_mmap__mmap for each defined cpu/thread .. so it depends on user how fast this will be - how many maps needs to be created/mmaped

Quoted 8 lines
Quoted 5 lines
Quoted 2 lines

Given userspace access fails for anything other than the calling thread and all cpus, how would more than 1 mmap be useful here?

I'm not sure what you mean by fail in here.. you need mmap for each event fd you want to read from

Yes, but that's one mmap per event (evsel) which is different than per cpu/thread.

right, and you need mmap per fd IIUC

Quoted 29 lines
Quoted 8 lines

in the example below we read stats from all cpus via perf_evsel__read, if we insert this call after perf_evsel__open:

  perf_evsel__mmap(cpus, NULL);

that maps page for each event, then perf_evsel__read could go through the fast code, no?

No, because we're not self-monitoring (pid == 0 and cpu == -1). With the following change:

tools/lib/perf/tests/test-evsel.c +1 -0
index eeca8203d73d..1fca9c121f7c 100644
@@ -17,6 +17,7 @@ static int test_stat_cpu(void)
 {
        struct perf_cpu_map *cpus;
        struct perf_evsel *evsel;
+       struct perf_event_mmap_page *pc;
        struct perf_event_attr attr = {
                .type   = PERF_TYPE_SOFTWARE,
                .config = PERF_COUNT_SW_CPU_CLOCK,
@@ -32,6 +33,15 @@ static int test_stat_cpu(void)
        err = perf_evsel__open(evsel, cpus, NULL);
        __T("failed to open evsel", err == 0);
 
Quoted 11 lines

tools/lib/perf/tests/test-evsel.c static int test_stat_cpu(void) · line 43

Quoted 8 lines
tools/lib/perf/tests/test-evsel.c +8 -0
+       pc = perf_evsel__mmap(evsel, 0);
+       __T("failed to mmap evsel", pc);
+
+#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__)
+       __T("userspace counter access not supported", pc->cap_user_rdpmc);
+       __T("userspace counter access not enabled", pc->index);
+       __T("userspace counter width not set", pc->pmc_width >= 32);
+#endif

I'll need to check, I'm surprised this would depend on the way you open the event

Any more thoughts on this?

Rob

#20 lore raw

Re: [PATCH v4 4/9] libperf: Add libperf_evsel__mmap()

On Thu, Nov 05, 2020 at 10:19:24AM -0600, Rob Herring wrote:

SNIP

Quoted 36 lines
Quoted 24 lines
Quoted 24 lines
Quoted 3 lines

that maps page for each event, then perf_evsel__read could go through the fast code, no?

No, because we're not self-monitoring (pid == 0 and cpu == -1). With the following change:

tools/lib/perf/tests/test-evsel.c +1 -0
index eeca8203d73d..1fca9c121f7c 100644
@@ -17,6 +17,7 @@ static int test_stat_cpu(void)
 {
        struct perf_cpu_map *cpus;
        struct perf_evsel *evsel;
+       struct perf_event_mmap_page *pc;
        struct perf_event_attr attr = {
                .type   = PERF_TYPE_SOFTWARE,
                .config = PERF_COUNT_SW_CPU_CLOCK,
@@ -32,6 +33,15 @@ static int test_stat_cpu(void)
        err = perf_evsel__open(evsel, cpus, NULL);
        __T("failed to open evsel", err == 0);
 
Quoted 11 lines

tools/lib/perf/tests/test-evsel.c static int test_stat_cpu(void) · line 43

Quoted 8 lines
tools/lib/perf/tests/test-evsel.c +8 -0
+       pc = perf_evsel__mmap(evsel, 0);
+       __T("failed to mmap evsel", pc);
+
+#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__)
+       __T("userspace counter access not supported", pc->cap_user_rdpmc);
+       __T("userspace counter access not enabled", pc->index);
+       __T("userspace counter width not set", pc->pmc_width >= 32);
+#endif

I'll need to check, I'm surprised this would depend on the way you open the event

Quoted 1 line

Any more thoughts on this?

sry I got stuck with other stuff.. I tried your change and pc->cap_user_rdpmc is 0 because the test creates software event, which does not support that

when I change that to:

.type = PERF_TYPE_HARDWARE,
.config = PERF_COUNT_HW_CPU_CYCLES,

I don't see any of those warning you added

jirka

#21 lore raw

Re: [PATCH v4 4/9] libperf: Add libperf_evsel__mmap()

Quoted 41 lines · Jiri Olsa

On Thu, Nov 05, 2020 at 10:19:24AM -0600, Rob Herring wrote:

SNIP

Quoted 36 lines
Quoted 35 lines
Quoted 22 lines
Quoted 3 lines

that maps page for each event, then perf_evsel__read could go through the fast code, no?

No, because we're not self-monitoring (pid == 0 and cpu == -1). With the following change:

tools/lib/perf/tests/test-evsel.c +1 -0
index eeca8203d73d..1fca9c121f7c 100644
@@ -17,6 +17,7 @@ static int test_stat_cpu(void)
 {
        struct perf_cpu_map *cpus;
        struct perf_evsel *evsel;
+       struct perf_event_mmap_page *pc;
        struct perf_event_attr attr = {
                .type   = PERF_TYPE_SOFTWARE,
                .config = PERF_COUNT_SW_CPU_CLOCK,
@@ -32,6 +33,15 @@ static int test_stat_cpu(void)
        err = perf_evsel__open(evsel, cpus, NULL);

tools/lib/perf/tests/test-evsel.c static int test_stat_cpu(void) · line 43

Quoted 10 lines
tools/lib/perf/tests/test-evsel.c +8 -0
        __T("failed to open evsel", err == 0);
 
+       pc = perf_evsel__mmap(evsel, 0);
+       __T("failed to mmap evsel", pc);
+
+#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__)
+       __T("userspace counter access not supported", pc->cap_user_rdpmc);
+       __T("userspace counter access not enabled", pc->index);
+       __T("userspace counter width not set", pc->pmc_width >= 32);
+#endif

I'll need to check, I'm surprised this would depend on the way you open the event

Quoted 5 lines
Quoted 1 line

Any more thoughts on this?

sry I got stuck with other stuff.. I tried your change and pc->cap_user_rdpmc is 0 because the test creates software event, which does not support that

Sigh, yes, of course.

Quoted 6 lines

when I change that to:

.type = PERF_TYPE_HARDWARE,
.config = PERF_COUNT_HW_CPU_CYCLES,

I don't see any of those warning you added

So I've now implemented the per fd mmap. It seems to run and get some data, but for the above case the counts don't look right.

cpu0: count = 0x10883, ena = 0xbf42, run = 0xbf42
cpu1: count = 0x1bc65, ena = 0xa278, run = 0xa278
cpu2: count = 0x1fab2, ena = 0x91ea, run = 0x91ea
cpu3: count = 0x23d61, ena = 0x81ac, run = 0x81ac
cpu4: count = 0x2936a, ena = 0x7149, run = 0x7149
cpu5: count = 0x2cd4e, ena = 0x634f, run = 0x634f
cpu6: count = 0x3139f, ena = 0x53e7, run = 0x53e7
cpu7: count = 0x35350, ena = 0x4690, run = 0x4690

For comparison, this is what I get using the slow path read():
cpu0: count = 0x1c40, ena = 0x188b5, run = 0x188b5
cpu1: count = 0x18e0, ena = 0x1b8f4, run = 0x1b8f4
cpu2: count = 0x745e, ena = 0x1ab9e, run = 0x1ab9e
cpu3: count = 0x2416, ena = 0x1a280, run = 0x1a280
cpu4: count = 0x19c7, ena = 0x19b00, run = 0x19b00
cpu5: count = 0x1737, ena = 0x19262, run = 0x19262
cpu6: count = 0x11d0e, ena = 0x18944, run = 0x18944
cpu7: count = 0x20dbe, ena = 0x181f4, run = 0x181f4

The difference is we get a sequentially increasing count rather than 1 random CPU (the one running the test) with a much higher count. That seems to me we're just reading the count for the calling process, not each CPU.

For this to work correctly, cap_user_rdpmc would have to be set only for the CPU's mmap that matches the calling process's CPU. I'm not sure whether that can be done. Even if it can, is it really worth doing so? You're accelerating reading an event on 1 out of N CPUs. And what do we do on every kernel up til now this won't work on? Another cap bit?

Rob

P.S. I did find one bug with all this. The shifts by pmc_width in the read seq need to be a signed count. This test happens to have raw counter values starting at 2^47.

#22 lore raw

Re: [PATCH v4 4/9] libperf: Add libperf_evsel__mmap()

Quoted 69 lines · Rob Herring
Quoted 41 lines · Jiri Olsa

On Thu, Nov 05, 2020 at 10:19:24AM -0600, Rob Herring wrote:

SNIP

Quoted 36 lines
Quoted 35 lines
Quoted 22 lines
Quoted 3 lines

that maps page for each event, then perf_evsel__read could go through the fast code, no?

No, because we're not self-monitoring (pid == 0 and cpu == -1). With the following change:

tools/lib/perf/tests/test-evsel.c +1 -0
index eeca8203d73d..1fca9c121f7c 100644
@@ -17,6 +17,7 @@ static int test_stat_cpu(void)
 {
        struct perf_cpu_map *cpus;
        struct perf_evsel *evsel;
+       struct perf_event_mmap_page *pc;
        struct perf_event_attr attr = {
                .type   = PERF_TYPE_SOFTWARE,
                .config = PERF_COUNT_SW_CPU_CLOCK,
@@ -32,6 +33,15 @@ static int test_stat_cpu(void)
        err = perf_evsel__open(evsel, cpus, NULL);

tools/lib/perf/tests/test-evsel.c static int test_stat_cpu(void) · line 43

Quoted 10 lines
tools/lib/perf/tests/test-evsel.c +8 -0
        __T("failed to open evsel", err == 0);
 
+       pc = perf_evsel__mmap(evsel, 0);
+       __T("failed to mmap evsel", pc);
+
+#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__)
+       __T("userspace counter access not supported", pc->cap_user_rdpmc);
+       __T("userspace counter access not enabled", pc->index);
+       __T("userspace counter width not set", pc->pmc_width >= 32);
+#endif

I'll need to check, I'm surprised this would depend on the way you open the event

Quoted 5 lines
Quoted 1 line

Any more thoughts on this?

sry I got stuck with other stuff.. I tried your change and pc->cap_user_rdpmc is 0 because the test creates software event, which does not support that

Sigh, yes, of course.

Quoted 6 lines

when I change that to:

.type = PERF_TYPE_HARDWARE,
.config = PERF_COUNT_HW_CPU_CYCLES,

I don't see any of those warning you added

So I've now implemented the per fd mmap. It seems to run and get some data, but for the above case the counts don't look right.

cpu0: count = 0x10883, ena = 0xbf42, run = 0xbf42
cpu1: count = 0x1bc65, ena = 0xa278, run = 0xa278
cpu2: count = 0x1fab2, ena = 0x91ea, run = 0x91ea
cpu3: count = 0x23d61, ena = 0x81ac, run = 0x81ac
cpu4: count = 0x2936a, ena = 0x7149, run = 0x7149
cpu5: count = 0x2cd4e, ena = 0x634f, run = 0x634f
cpu6: count = 0x3139f, ena = 0x53e7, run = 0x53e7
cpu7: count = 0x35350, ena = 0x4690, run = 0x4690

Quoted 9 lines

For comparison, this is what I get using the slow path read():
cpu0: count = 0x1c40, ena = 0x188b5, run = 0x188b5
cpu1: count = 0x18e0, ena = 0x1b8f4, run = 0x1b8f4
cpu2: count = 0x745e, ena = 0x1ab9e, run = 0x1ab9e
cpu3: count = 0x2416, ena = 0x1a280, run = 0x1a280
cpu4: count = 0x19c7, ena = 0x19b00, run = 0x19b00
cpu5: count = 0x1737, ena = 0x19262, run = 0x19262
cpu6: count = 0x11d0e, ena = 0x18944, run = 0x18944
cpu7: count = 0x20dbe, ena = 0x181f4, run = 0x181f4

hum, could you please send/push changes with that test? I can try it and check

jirka

Quoted 19 lines

The difference is we get a sequentially increasing count rather than 1 random CPU (the one running the test) with a much higher count. That seems to me we're just reading the count for the calling process, not each CPU.

For this to work correctly, cap_user_rdpmc would have to be set only for the CPU's mmap that matches the calling process's CPU. I'm not sure whether that can be done. Even if it can, is it really worth doing so? You're accelerating reading an event on 1 out of N CPUs. And what do we do on every kernel up til now this won't work on? Another cap bit?

Rob

P.S. I did find one bug with all this. The shifts by pmc_width in the read seq need to be a signed count. This test happens to have raw counter values starting at 2^47.

#23 lore raw

Re: [PATCH v4 4/9] libperf: Add libperf_evsel__mmap()

Quoted 81 lines · Jiri Olsa
Quoted 69 lines · Rob Herring
Quoted 41 lines · Jiri Olsa

On Thu, Nov 05, 2020 at 10:19:24AM -0600, Rob Herring wrote:

SNIP

Quoted 36 lines
Quoted 35 lines
Quoted 22 lines
Quoted 3 lines

that maps page for each event, then perf_evsel__read could go through the fast code, no?

No, because we're not self-monitoring (pid == 0 and cpu == -1). With the following change:

tools/lib/perf/tests/test-evsel.c +1 -0
index eeca8203d73d..1fca9c121f7c 100644
@@ -17,6 +17,7 @@ static int test_stat_cpu(void)
 {
        struct perf_cpu_map *cpus;
        struct perf_evsel *evsel;
+       struct perf_event_mmap_page *pc;
        struct perf_event_attr attr = {
                .type   = PERF_TYPE_SOFTWARE,
                .config = PERF_COUNT_SW_CPU_CLOCK,
@@ -32,6 +33,15 @@ static int test_stat_cpu(void)
        err = perf_evsel__open(evsel, cpus, NULL);

tools/lib/perf/tests/test-evsel.c static int test_stat_cpu(void) · line 43

Quoted 10 lines
tools/lib/perf/tests/test-evsel.c +8 -0
        __T("failed to open evsel", err == 0);
 
+       pc = perf_evsel__mmap(evsel, 0);
+       __T("failed to mmap evsel", pc);
+
+#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__)
+       __T("userspace counter access not supported", pc->cap_user_rdpmc);
+       __T("userspace counter access not enabled", pc->index);
+       __T("userspace counter width not set", pc->pmc_width >= 32);
+#endif

I'll need to check, I'm surprised this would depend on the way you open the event

Quoted 5 lines
Quoted 1 line

Any more thoughts on this?

sry I got stuck with other stuff.. I tried your change and pc->cap_user_rdpmc is 0 because the test creates software event, which does not support that

Sigh, yes, of course.

Quoted 6 lines

when I change that to:

.type = PERF_TYPE_HARDWARE,
.config = PERF_COUNT_HW_CPU_CYCLES,

I don't see any of those warning you added

So I've now implemented the per fd mmap. It seems to run and get some data, but for the above case the counts don't look right.

cpu0: count = 0x10883, ena = 0xbf42, run = 0xbf42
cpu1: count = 0x1bc65, ena = 0xa278, run = 0xa278
cpu2: count = 0x1fab2, ena = 0x91ea, run = 0x91ea
cpu3: count = 0x23d61, ena = 0x81ac, run = 0x81ac
cpu4: count = 0x2936a, ena = 0x7149, run = 0x7149
cpu5: count = 0x2cd4e, ena = 0x634f, run = 0x634f
cpu6: count = 0x3139f, ena = 0x53e7, run = 0x53e7
cpu7: count = 0x35350, ena = 0x4690, run = 0x4690

Quoted 9 lines

For comparison, this is what I get using the slow path read():
cpu0: count = 0x1c40, ena = 0x188b5, run = 0x188b5
cpu1: count = 0x18e0, ena = 0x1b8f4, run = 0x1b8f4
cpu2: count = 0x745e, ena = 0x1ab9e, run = 0x1ab9e
cpu3: count = 0x2416, ena = 0x1a280, run = 0x1a280
cpu4: count = 0x19c7, ena = 0x19b00, run = 0x19b00
cpu5: count = 0x1737, ena = 0x19262, run = 0x19262
cpu6: count = 0x11d0e, ena = 0x18944, run = 0x18944
cpu7: count = 0x20dbe, ena = 0x181f4, run = 0x181f4

Quoted 2 lines

hum, could you please send/push changes with that test? I can try it and check

Here you go:

git://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git user-perf-event-v5

Just comment out the mmap parts to get original behavior.

Rob

#24 lore raw

PATCH v4 5/9 libperf: tests: Add support for verbose printing

In reply to Rob Herring

Add __T_VERBOSE() so tests can add verbose output. The verbose output is enabled with the '-v' command line option.

---
v3:
- New patch

---
 tools/lib/perf/include/internal/tests.h | 32 +++++++++++++++++++++++++
 tools/lib/perf/tests/Makefile           |  4 +++-
 2 files changed, 35 insertions(+), 1 deletion(-)
tools/lib/perf/include/internal/tests.h +32 -0
index 2093e8868a67..27b6e64299e2 100644
@@ -3,11 +3,32 @@
 #define __LIBPERF_INTERNAL_TESTS_H
 
 #include <stdio.h>
+#include <unistd.h>
 
 int tests_failed;
+int tests_verbose;
+
+static inline int get_verbose(char **argv, int argc)
+{
+	char c;
+	int verbose = 0;
+
+	while ((c = getopt(argc, argv, "v")) != -1) {
+		switch (c)
+		{
+		case 'v':
+			verbose = 1;
+			break;
+		default:
+			break;
+		}
+	}
+	return verbose;
+}
 
 #define __T_START					\
 do {							\
+	tests_verbose = get_verbose(argv, argc);	\
 	fprintf(stdout, "- running %s...", __FILE__);	\
 	fflush(NULL);					\
 	tests_failed = 0;				\
@@ -30,4 +51,15 @@ do {
 	}                                                                        \
 } while (0)
 
+#define __T_VERBOSE(...)						\
+do {									\
+	if (tests_verbose) {						\
+		if (tests_verbose == 1) {				\
+			fputc('\n', stderr);				\
+			tests_verbose++;				\
+		}							\
+		fprintf(stderr, ##__VA_ARGS__);				\
+	}								\
+} while (0)
+
 #endif /* __LIBPERF_INTERNAL_TESTS_H */
tools/lib/perf/tests/Makefile +3 -1
index 96841775feaf..9438b385d489 100644
@@ -5,6 +5,8 @@ TESTS = test-cpumap test-threadmap test-evlist test-evsel
 TESTS_SO := $(addsuffix -so,$(TESTS))
 TESTS_A  := $(addsuffix -a,$(TESTS))
 
+TEST_ARGS := $(if $(V),-v)
+
 # Set compile option CFLAGS
 ifdef EXTRA_CFLAGS
   CFLAGS := $(EXTRA_CFLAGS)
@@ -30,7 +32,7 @@ run:
 	@echo "running static:"
 	@for i in $(TESTS_A); do ./$$i; done
 	@echo "running dynamic:"
-	@for i in $(TESTS_SO); do LD_LIBRARY_PATH=../ ./$$i; done
+	@for i in $(TESTS_SO); do LD_LIBRARY_PATH=../ ./$$i $(TEST_ARGS); done
 
 clean:
 	$(call QUIET_CLEAN, tests)$(RM) $(TESTS_A) $(TESTS_SO)
Signature · 1 line
2.25.1
#25 lore raw

PATCH v4 6/9 libperf: Add support for user space counter access

In reply to Rob Herring

x86 and arm64 can both support direct access of event counters in userspace. The access sequence is less than trivial and currently exists in perf test code (tools/perf/arch/x86/tests/rdpmc.c) with copies in projects such as PAPI and libpfm4.

In order to support usersapce access, an event must be mmapped first with perf_evsel__mmap(). Then subsequent calls to perf_evsel__read() will use the fast path (assuming the arch supports it).

---
v4:
- Update perf_evsel__mmap size to pages
v3:
- Split out perf_evsel__mmap() to separate patch

---
 tools/lib/perf/evsel.c                 |  3 +
 tools/lib/perf/include/internal/mmap.h |  3 +
 tools/lib/perf/mmap.c                  | 90 ++++++++++++++++++++++++++
 tools/lib/perf/tests/test-evsel.c      | 63 ++++++++++++++++++
 4 files changed, 159 insertions(+)
tools/lib/perf/evsel.c +3 -0
index 42eaf3c18981..e1f18d65f604 100644
@@ -222,6 +222,9 @@ int perf_evsel__read(struct perf_evsel *evsel, int cpu, int thread,
 	if (FD(evsel, cpu, thread) < 0)
 		return -EINVAL;
 
+	if (evsel->mmap && !perf_mmap__read_self(evsel->mmap, count))
+		return 0;
+
 	if (readn(FD(evsel, cpu, thread), count->values, size) <= 0)
 		return -errno;
 
tools/lib/perf/include/internal/mmap.h +3 -0
index be7556e0a2b2..5e3422f40ed5 100644
@@ -11,6 +11,7 @@
 #define PERF_SAMPLE_MAX_SIZE (1 << 16)
 
 struct perf_mmap;
+struct perf_counts_values;
 
 typedef void (*libperf_unmap_cb_t)(struct perf_mmap *map);
 
@@ -52,4 +53,6 @@ void perf_mmap__put(struct perf_mmap *map);
 
 u64 perf_mmap__read_head(struct perf_mmap *map);
 
+int perf_mmap__read_self(struct perf_mmap *map, struct perf_counts_values *count);
+
 #endif /* __LIBPERF_INTERNAL_MMAP_H */
tools/lib/perf/mmap.c +90 -0

102 lines not on this page · raw message

tools/lib/perf/tests/test-evsel.c +63 -0

75 lines not on this page · raw message

Signature · 1 line
2.25.1
#26 lore raw

PATCH v4 7/9 libperf: Add arm64 support to perf_mmap__read_self()

In reply to Rob Herring

Add the arm64 variants for read_perf_counter() and read_timestamp(). Unfortunately the counter number is encoded into the instruction, so the code is a bit verbose to enumerate all possible counters.

---
 tools/lib/perf/mmap.c | 98 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 98 insertions(+)
tools/lib/perf/mmap.c +98 -0
index cb07969cfdbf..6cf939aae6b6 100644
@@ -13,6 +13,7 @@
 #include <internal/lib.h>
 #include <linux/kernel.h>
 #include <linux/math64.h>
+#include <linux/stringify.h>
 #include "internal.h"
 
 void perf_mmap__init(struct perf_mmap *map, struct perf_mmap *prev,
@@ -294,6 +295,103 @@ static u64 read_timestamp(void)
 
 	return low | ((u64)high) << 32;
 }
+#elif defined(__aarch64__)
+#define read_sysreg(r) ({						\
+	u64 __val;							\
+	asm volatile("mrs %0, " __stringify(r) : "=r" (__val));		\
+	__val;								\
+})
+
+static u64 read_pmccntr(void)
+{
+	return read_sysreg(pmccntr_el0);
+}
+
+#define PMEVCNTR_READ(idx)					\
+	static u64 read_pmevcntr_##idx(void) {			\
+		return read_sysreg(pmevcntr##idx##_el0);	\
+	}
+
+PMEVCNTR_READ(0);
+PMEVCNTR_READ(1);
+PMEVCNTR_READ(2);
+PMEVCNTR_READ(3);
+PMEVCNTR_READ(4);
+PMEVCNTR_READ(5);
+PMEVCNTR_READ(6);
+PMEVCNTR_READ(7);
+PMEVCNTR_READ(8);
+PMEVCNTR_READ(9);
+PMEVCNTR_READ(10);
+PMEVCNTR_READ(11);
+PMEVCNTR_READ(12);
+PMEVCNTR_READ(13);
+PMEVCNTR_READ(14);
+PMEVCNTR_READ(15);
+PMEVCNTR_READ(16);
+PMEVCNTR_READ(17);
+PMEVCNTR_READ(18);
+PMEVCNTR_READ(19);
+PMEVCNTR_READ(20);
+PMEVCNTR_READ(21);
+PMEVCNTR_READ(22);
+PMEVCNTR_READ(23);
+PMEVCNTR_READ(24);
+PMEVCNTR_READ(25);
+PMEVCNTR_READ(26);
+PMEVCNTR_READ(27);
+PMEVCNTR_READ(28);
+PMEVCNTR_READ(29);
+PMEVCNTR_READ(30);
+
+/*
+ * Read a value direct from PMEVCNTR<idx>
+ */
+static u64 read_perf_counter(unsigned int counter)
+{
+	static u64 (* const read_f[])(void) = {
+		read_pmevcntr_0,
+		read_pmevcntr_1,
+		read_pmevcntr_2,
+		read_pmevcntr_3,
+		read_pmevcntr_4,
+		read_pmevcntr_5,
+		read_pmevcntr_6,
+		read_pmevcntr_7,
+		read_pmevcntr_8,
+		read_pmevcntr_9,
+		read_pmevcntr_10,
+		read_pmevcntr_11,
+		read_pmevcntr_13,
+		read_pmevcntr_12,
+		read_pmevcntr_14,
+		read_pmevcntr_15,
+		read_pmevcntr_16,
+		read_pmevcntr_17,
+		read_pmevcntr_18,
+		read_pmevcntr_19,
+		read_pmevcntr_20,
+		read_pmevcntr_21,
+		read_pmevcntr_22,
+		read_pmevcntr_23,
+		read_pmevcntr_24,
+		read_pmevcntr_25,
+		read_pmevcntr_26,
+		read_pmevcntr_27,
+		read_pmevcntr_28,
+		read_pmevcntr_29,
+		read_pmevcntr_30,
+		read_pmccntr
+	};
+
+	if (counter < ARRAY_SIZE(read_f))
+		return (read_f[counter])();
+
+	return 0;
+}
+
+static u64 read_timestamp(void) { return read_sysreg(cntvct_el0); }
+
 #else
 static u64 read_perf_counter(unsigned int counter) { return 0; }
 static u64 read_timestamp(void) { return 0; }
Signature · 1 line
2.25.1
#27 lore raw

PATCH v4 8/9 perf: arm64: Add test for userspace counter access on heterogeneous systems

In reply to Rob Herring

Userspace counter access only works on heterogeneous systems with some restrictions. The userspace process must be pinned to a homogeneous subset of CPUs and must open the corresponding PMU for those CPUs. This commit adds a test implementing these requirements.

---
v4:
- Update perf_evsel__mmap params
v2:
- Drop all but heterogeneous test as others covered by libperf tests
- Rework to use libperf

---
 tools/perf/arch/arm64/include/arch-tests.h |   7 +
 tools/perf/arch/arm64/tests/Build          |   1 +
 tools/perf/arch/arm64/tests/arch-tests.c   |   4 +
 tools/perf/arch/arm64/tests/user-events.c  | 170 +++++++++++++++++++++
 4 files changed, 182 insertions(+)
 create mode 100644 tools/perf/arch/arm64/tests/user-events.c
tools/perf/arch/arm64/include/arch-tests.h +7 -0
index 90ec4c8cb880..380ad34a3f09 100644
@@ -2,11 +2,18 @@
 #ifndef ARCH_TESTS_H
 #define ARCH_TESTS_H
 
+#include <linux/compiler.h>
+
 #ifdef HAVE_DWARF_UNWIND_SUPPORT
 struct thread;
 struct perf_sample;
+int test__arch_unwind_sample(struct perf_sample *sample,
+			     struct thread *thread);
 #endif
 
 extern struct test arch_tests[];
+int test__rd_pinned(struct test __maybe_unused *test,
+		       int __maybe_unused subtest);
+
 
 #endif
tools/perf/arch/arm64/tests/Build +1 -0
index a61c06bdb757..3f9a20c17fc6 100644
@@ -1,4 +1,5 @@
 perf-y += regs_load.o
 perf-$(CONFIG_DWARF_UNWIND) += dwarf-unwind.o
 
+perf-y += user-events.o
 perf-y += arch-tests.o
tools/perf/arch/arm64/tests/arch-tests.c +4 -0
index 5b1543c98022..80ce7bd3c16d 100644
@@ -10,6 +10,10 @@ struct test arch_tests[] = {
 		.func = test__dwarf_unwind,
 	},
 #endif
+	{
+		.desc = "Pinned CPU user counter access",
+		.func = test__rd_pinned,
+	},
 	{
 		.func = NULL,
 	},
tools/perf/arch/arm64/tests/user-events.c new file +170 -0

170 lines not on this page · raw message

Signature · 1 line
2.25.1
#28 lore raw

PATCH v4 9/9 Documentation: arm64: Document PMU counters access from userspace

In reply to Rob Herring

From: Raphael Gault <[email protected]>

Add a documentation file to describe the access to the pmu hardware counters from userspace

---
v2:
- Update links to test examples

Changes from Raphael's v4:
- Convert to rSt
- Update chained event status
- Add section for heterogeneous systems

---
 Documentation/arm64/index.rst                 |  1 +
 .../arm64/perf_counter_user_access.rst        | 56 +++++++++++++++++++
 2 files changed, 57 insertions(+)
 create mode 100644 Documentation/arm64/perf_counter_user_access.rst
Documentation/arm64/index.rst +1 -0
index d9665d83c53a..c712a08e7627 100644
@@ -15,6 +15,7 @@ ARM64 Architecture
     legacy_instructions
     memory
     perf
+    perf_counter_user_access
     pointer-authentication
     silicon-errata
     sve
Documentation/arm64/perf_counter_user_access.rst new file +56 -0

56 lines not on this page · raw message

Signature · 1 line
2.25.1