* [PATCH 0/1] alpha/perf: Enable duration-based sampling throttling
@ 2026-11-07 9:18 Magnus Lindholm
2026-11-07 9:18 ` [PATCH 1/1] alpha/perf: Account for time spent handling PMU interrupts Magnus Lindholm
0 siblings, 1 reply; 4+ messages in thread
From: Magnus Lindholm @ 2026-11-07 9:18 UTC (permalink / raw)
To: richard.henderson, mattst88, linux-kernel, linux-alpha; +Cc: linmag7
Hi,
While testing Matt's v7 ProfileMe series [1] on my EV67 AlphaServer ES40,
I found that short-period sampling could make the machine unresponsive.
Once serial logging was enabled, the console showed RCU grace-period
kthread starvation reports.
Count-based throttling was working: at HZ=1024 and the default limit of
100000 samples/s, the observed cadence was approximately 98 interrupts
per throttle cycle. However, Alpha never called perf_sample_event_took(),
so perf could not reduce that limit based on interrupt handling cost.
Setting perf_cpu_time_max_percent to 25 did not provide that feedback.
Keeping period 17 but lowering the limit to 1024 or 4096 samples/s made
the tests pass while still exercising repeated stop/park/restart cycles.
Adding duration feedback also made the original high-rate test pass,
without changing those counter-state operations. The minimal fix measured
average handler costs of 24-26 us against a 2.5 us budget and reduced the
limit to 7168-8192 samples/s.
Reproducer
==========
The reproducer requires Matt's ProfileMe kernel series and matching perf
tools. On the ES40 I used the following one-second workload, built
natively as "busy":
#include <stdint.h>
#include <time.h>
static double now(void)
{
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
return t.tv_sec + t.tv_nsec * 1e-9;
}
int main(void)
{
uint64_t x = 1;
double end = now() + 1;
do {
for (int i = 0; i < 1000; i++)
asm volatile("mulq %0,3,%0" : "+r" (x));
} while (now() < end);
return 0;
}
cc -O2 -o busy busy.c
As root, with serial capture running:
dmesg -n 7
sysctl -w kernel.perf_event_max_sample_rate=100000
sysctl -w kernel.perf_cpu_time_max_percent=25
The period-10007 control completed successfully:
taskset -c 0 perf record -B -R \
-e cpu/instructions,profileme,randomize/ -c 10007 \
-o control.data -- ./busy
Changing only the period reproduced the failure:
taskset -c 0 perf record -B -R \
-e cpu/instructions,profileme,randomize/ -c 17 \
-o period17.data -- ./busy
The unpatched runs required a manual reset; a userspace timeout did not
reliably recover the system. The commands above are the essential
recording commands from the controlled tests, which also used a timeout
supervisor and saved logs on another machine.
Validation and submission scope
===============================
The minimal fix, without diagnostic instrumentation, passed three
repeats of the high-rate reproducer and the two low-rate controls.
Broader testing covered 21 cases on all three CPUs: randomized ProfileMe
instruction sampling at periods 16, 17 and 31, ProfileMe and Aggregate
cycle sampling, and concurrent ProfileMe and mixed-mode workloads.
All completed, with 1352488 samples, no recorded losses and no new RCU
stalls or kernel faults. This was bounded stress testing, not a long soak.
The missing feedback predates ProfileMe. This patch is therefore based
on mainline and can be reviewed and applied independently of Matt's
series. I cross-built vmlinux, the Alpha boot image and modules on
mainline d48723711bae with the ES40 configuration and GCC 15.0.1. The
only build warning was the existing saved_config COMMON-symbol warning.
The hardware results above used Matt's v7 series with this fix applied;
the mainline-only build has not been boot-tested. RPCC supplies a short,
same-CPU interval because Alpha's tick-based sched_clock() is too coarse
for this measurement. Counter programming and event handling are unchanged.
[1] https://lore.kernel.org/linux-alpha/20261105-alpha-profileme-v7-0-c474e892abb0@gmail.com/
Thanks,
Magnus
Magnus Lindholm (1):
alpha/perf: Account for time spent handling PMU interrupts
arch/alpha/kernel/perf_event.c | 26 ++++++++++++++++++++++++--
1 file changed, 24 insertions(+), 2 deletions(-)
base-commit: d48723711bae9b8cdc56719c448ba42fef28cbee
^ permalink raw reply [flat|nested] 4+ messages in thread* [PATCH 1/1] alpha/perf: Account for time spent handling PMU interrupts
2026-11-07 9:18 [PATCH 0/1] alpha/perf: Enable duration-based sampling throttling Magnus Lindholm
@ 2026-11-07 9:18 ` Magnus Lindholm
2026-11-10 2:05 ` Matt Turner
0 siblings, 1 reply; 4+ messages in thread
From: Magnus Lindholm @ 2026-11-07 9:18 UTC (permalink / raw)
To: richard.henderson, mattst88, linux-kernel, linux-alpha; +Cc: linmag7
Alpha does not report PMU interrupt duration to perf, preventing its
adaptive throttling from reducing an excessively costly sampling rate.
Measure handler time with RPCC and report it to perf_sample_event_took().
Convert cycles using the calibrated frequency selected by time_init();
Alpha's tick-based sched_clock() is too coarse for this measurement.
Wrap the existing handler so all return paths are accounted for, without
changing counter programming or event handling.
Signed-off-by: Magnus Lindholm <linmag7@gmail.com>
---
arch/alpha/kernel/perf_event.c | 26 ++++++++++++++++++++++++--
1 file changed, 24 insertions(+), 2 deletions(-)
diff --git a/arch/alpha/kernel/perf_event.c b/arch/alpha/kernel/perf_event.c
index a3eaab094ece..dd3bc70b71d7 100644
--- a/arch/alpha/kernel/perf_event.c
+++ b/arch/alpha/kernel/perf_event.c
@@ -16,8 +16,10 @@
#include <linux/kdebug.h>
#include <linux/mutex.h>
#include <linux/init.h>
+#include <linux/math64.h>
#include <asm/hwrpb.h>
+#include <asm/timex.h>
#include <linux/atomic.h>
#include <asm/irq.h>
#include <asm/irq_regs.h>
@@ -26,6 +28,9 @@
#include <asm/hw_irq.h>
+extern unsigned long est_cycle_freq;
+static unsigned long pmu_cycle_freq __read_mostly;
+
/* The maximum number of PMCs on any Alpha CPU whatsoever. */
#define MAX_HWEVENTS 3
#define PMC_NO_INDEX -1
@@ -797,8 +802,8 @@ void perf_event_print_debug(void)
* Performance Monitoring Interrupt Service Routine called when a PMC
* overflows. The PMC that overflowed is passed in la_ptr.
*/
-static void alpha_perf_event_irq_handler(unsigned long la_ptr,
- struct pt_regs *regs)
+static void alpha_perf_event_irq_inner(unsigned long la_ptr,
+ struct pt_regs *regs)
{
struct cpu_hw_events *cpuc;
struct perf_sample_data data;
@@ -862,6 +867,21 @@ static void alpha_perf_event_irq_handler(unsigned long la_ptr,
+/* sched_clock() is tick-based on Alpha; measure a short same-CPU interval. */
+static void alpha_perf_event_irq_handler(unsigned long la_ptr,
+ struct pt_regs *regs)
+{
+ u32 start = get_cycles();
+ u32 cycles;
+
+ alpha_perf_event_irq_inner(la_ptr, regs);
+ /* Unsigned subtraction handles a wrap of the low 32-bit counter. */
+ cycles = (u32)get_cycles() - start;
+ if (likely(pmu_cycle_freq))
+ perf_sample_event_took(div64_u64((u64)cycles * NSEC_PER_SEC,
+ pmu_cycle_freq));
+}
+
/*
* Init call to initialise performance events at kernel startup.
*/
@@ -878,6 +898,8 @@ static int __init init_hw_perf_events(void)
/* Override performance counter IRQ vector */
+ /* Match the calibrated frequency selected by time_init(). */
+ pmu_cycle_freq = est_cycle_freq ? est_cycle_freq : hwrpb->cycle_freq;
perf_irq = alpha_perf_event_irq_handler;
/* And set up PMU specification */
^ permalink raw reply related [flat|nested] 4+ messages in thread* Re: [PATCH 1/1] alpha/perf: Account for time spent handling PMU interrupts
2026-11-07 9:18 ` [PATCH 1/1] alpha/perf: Account for time spent handling PMU interrupts Magnus Lindholm
@ 2026-11-10 2:05 ` Matt Turner
2026-11-10 18:37 ` Magnus Lindholm
0 siblings, 1 reply; 4+ messages in thread
From: Matt Turner @ 2026-11-10 2:05 UTC (permalink / raw)
To: Magnus Lindholm; +Cc: richard.henderson, linux-kernel, linux-alpha
On Sat, Nov 7, 2026 at 4:19 AM Magnus Lindholm <linmag7@gmail.com> wrote:
> Alpha does not report PMU interrupt duration to perf, preventing its
> adaptive throttling from reducing an excessively costly sampling rate.
Nice find, and thanks for the thorough testing write-up. The logic looks
right to me: time_init() runs before early initcalls, and since it zeroes
est_cycle_freq when the HWRPB value is within tolerance, the
"est_cycle_freq ? : hwrpb->cycle_freq" idiom picks the same frequency it
did. A few small things below.
It would be worth adding a sentence on the symptom from the cover letter
(short sampling periods leave the machine unresponsive with RCU
grace-period kthread starvation reports) so the commit stands on its own.
> +#include <linux/math64.h>
On 64-bit, div64_u64() is just a plain division, so this include only
serves that one call. See below for an alternative that drops it.
> +extern unsigned long est_cycle_freq;
This is already declared in arch/alpha/kernel/proto.h. Please
#include "proto.h" instead, like the rest of arch/alpha/kernel does.
> +static unsigned long pmu_cycle_freq __read_mostly;
Since this is only written from an early_initcall, __ro_after_init seems
a better fit.
> + u32 start = get_cycles();
> + u32 cycles;
> +
> + alpha_perf_event_irq_inner(la_ptr, regs);
> + /* Unsigned subtraction handles a wrap of the low 32-bit counter. */
> + cycles = (u32)get_cycles() - start;
cycles_t is already a 32-bit unsigned int on Alpha, so the cast isn't
needed. Declaring these as cycles_t would read a bit more naturally.
> + if (likely(pmu_cycle_freq))
> + perf_sample_event_took(div64_u64((u64)cycles * NSEC_PER_SEC,
> + pmu_cycle_freq));
Alpha has no integer divide instruction, so this compiles to a __divqu
millicode call on every PMU interrupt. It's small next to the handler
cost you measured, but it's easy to avoid: compute a mult/shift pair
once at init with clocks_calc_mult_shift() and convert with
mul_u64_u32_shr(cycles, mult, shift) here.
> + /* Match the calibrated frequency selected by time_init(). */
> + pmu_cycle_freq = est_cycle_freq ? est_cycle_freq : hwrpb->cycle_freq;
Not for this patch, but this is now the second copy of that selection
(show_cpuinfo() has the other). Having time_init() store the frequency
it chose in a global would let both just read it. Fine as a follow-up.
With the proto.h and cast nits fixed (the rest at your discretion),
Reviewed-by <mattst88@gmail.com>
Thanks,
Matt
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH 1/1] alpha/perf: Account for time spent handling PMU interrupts
2026-11-10 2:05 ` Matt Turner
@ 2026-11-10 18:37 ` Magnus Lindholm
0 siblings, 0 replies; 4+ messages in thread
From: Magnus Lindholm @ 2026-11-10 18:37 UTC (permalink / raw)
To: Matt Turner; +Cc: richard.henderson, linux-kernel, linux-alpha
Hi Matt,
On Tue, Nov 10, 2026 at 3:05 AM Matt Turner <mattst88@gmail.com> wrote:
>
> On Sat, Nov 7, 2026 at 4:19 AM Magnus Lindholm <linmag7@gmail.com> wrote:
> > Alpha does not report PMU interrupt duration to perf, preventing its
> > adaptive throttling from reducing an excessively costly sampling rate.
>
> Nice find, and thanks for the thorough testing write-up. The logic looks
> right to me: time_init() runs before early initcalls, and since it zeroes
> est_cycle_freq when the HWRPB value is within tolerance, the
> "est_cycle_freq ? : hwrpb->cycle_freq" idiom picks the same frequency it
> did. A few small things below.
>
> It would be worth adding a sentence on the symptom from the cover letter
> (short sampling periods leave the machine unresponsive with RCU
> grace-period kthread starvation reports) so the commit stands on its own.
>
Agree, I'll update the commit message in next version.
> > +#include <linux/math64.h>
>
> On 64-bit, div64_u64() is just a plain division, so this include only
> serves that one call. See below for an alternative that drops it.
>
> > +extern unsigned long est_cycle_freq;
>
> This is already declared in arch/alpha/kernel/proto.h. Please
> #include "proto.h" instead, like the rest of arch/alpha/kernel does.
>
yes, will do.
> > +static unsigned long pmu_cycle_freq __read_mostly;
>
> Since this is only written from an early_initcall, __ro_after_init seems
> a better fit.
>
> > + u32 start = get_cycles();
> > + u32 cycles;
> > +
> > + alpha_perf_event_irq_inner(la_ptr, regs);
> > + /* Unsigned subtraction handles a wrap of the low 32-bit counter. */
> > + cycles = (u32)get_cycles() - start;
>
> cycles_t is already a 32-bit unsigned int on Alpha, so the cast isn't
> needed. Declaring these as cycles_t would read a bit more naturally.
>
you' re right.
> > + if (likely(pmu_cycle_freq))
> > + perf_sample_event_took(div64_u64((u64)cycles * NSEC_PER_SEC,
> > + pmu_cycle_freq));
>
> Alpha has no integer divide instruction, so this compiles to a __divqu
> millicode call on every PMU interrupt. It's small next to the handler
> cost you measured, but it's easy to avoid: compute a mult/shift pair
> once at init with clocks_calc_mult_shift() and convert with
> mul_u64_u32_shr(cycles, mult, shift) here.
>
True, that makes sense. I'll fix this in next version.
> > + /* Match the calibrated frequency selected by time_init(). */
> > + pmu_cycle_freq = est_cycle_freq ? est_cycle_freq : hwrpb->cycle_freq;
>
> Not for this patch, but this is now the second copy of that selection
> (show_cpuinfo() has the other). Having time_init() store the frequency
> it chose in a global would let both just read it. Fine as a follow-up.
>
> With the proto.h and cast nits fixed (the rest at your discretion),
>
> Reviewed-by <mattst88@gmail.com>
>
> Thanks,
> Matt
Thanks for taking the time to look at this, I'll post a v2
Regards
Magnus
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-11-10 18:37 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-07 9:18 [PATCH 0/1] alpha/perf: Enable duration-based sampling throttling Magnus Lindholm
2026-11-07 9:18 ` [PATCH 1/1] alpha/perf: Account for time spent handling PMU interrupts Magnus Lindholm
2026-11-10 2:05 ` Matt Turner
2026-11-10 18:37 ` Magnus Lindholm
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox