[PATCH] genirq/proc: Fix missing CPU header in /proc/interrupts

Haris Okanovic posted 1 patch 1 week, 6 days ago
kernel/irq/proc.c | 24 +++++++++++++++++-------
1 file changed, 17 insertions(+), 7 deletions(-)
[PATCH] genirq/proc: Fix missing CPU header in /proc/interrupts
Posted by Haris Okanovic 1 week, 6 days ago
irq_seq_show() must be idempotent since it may be called multiple times
to render the same record, e.g. when seq_file's buffer is exceeded. It
emits the CPU header behind the one-shot print_header flag, which the
first pass clears, so the header is lost with the discarded buffer.

This can be seen on large systems, such as 8g.48xl (192 CPUs) and
8i.96xl (384 CPUs) AWS instance types:

  $ head -1 /proc/interrupts
     9:          0          0 ...  0  GICv3  25 Level  vgic

Replace print_header with the position of the first record and include
the header whenever that record is emitted.

Fixes: 171cc0d9eed1 ("genirq/proc: Speed up /proc/interrupts iteration")
Signed-off-by: Haris Okanovic <harisokn@amazon.com>
---
 kernel/irq/proc.c | 24 +++++++++++++++++-------
 1 file changed, 17 insertions(+), 7 deletions(-)

diff --git a/kernel/irq/proc.c b/kernel/irq/proc.c
index 1b835725f7b1c..0c0df16de5816 100644
--- a/kernel/irq/proc.c
+++ b/kernel/irq/proc.c
@@ -462,7 +462,7 @@ int __weak arch_show_interrupts(struct seq_file *p, int prec)
 static DEFINE_RAW_SPINLOCK(irq_proc_constraints_lock);
 
 static struct irq_proc_constraints {
-	bool		print_header;
+	loff_t		first_pos;
 	unsigned int	num_prec;
 	unsigned int	chip_width;
 } irq_proc_constraints __read_mostly = {
@@ -543,14 +543,13 @@ static int irq_seq_show(struct seq_file *p, void *v)
 	struct irqaction *action;
 
 	/* Print header for the first interrupt? */
-	if (constr->print_header) {
+	if (p->index == constr->first_pos) {
 		unsigned int cpu;
 
 		seq_printf(p, "%*s", constr->num_prec + 8, "");
 		for_each_online_cpu(cpu)
 			seq_printf(p, "CPU%-8d", cpu);
 		seq_putc(p, '\n');
-		constr->print_header = false;
 	}
 
 	if (desc == ARCH_PROC_IRQDESC)
@@ -635,14 +634,25 @@ static void *irq_seq_next_desc(loff_t *pos)
 
 static void *irq_seq_start(struct seq_file *f, loff_t *pos)
 {
-	if (!*pos) {
-		struct irq_proc_constraints *constr = f->private;
+	struct irq_proc_constraints *constr = f->private;
+	bool first = !*pos;
+	void *ret;
 
+	if (first) {
 		constr->num_prec = READ_ONCE(irq_proc_constraints.num_prec);
 		constr->chip_width = READ_ONCE(irq_proc_constraints.chip_width);
-		constr->print_header = true;
 	}
-	return irq_seq_next_desc(pos);
+
+	ret = irq_seq_next_desc(pos);
+
+	/*
+	 * Record position of the first output record so that irq_seq_show()
+	 * knows where to write the header.
+	 */
+	if (first)
+		constr->first_pos = *pos;
+
+	return ret;
 }
 
 static void *irq_seq_next(struct seq_file *f, void *v, loff_t *pos)

---
base-commit: 08df884136f1c1197bab2a27814404fd329d9aac
change-id: 20260911-dev-harisokn-proc-interrupts-fix-b4-7498f7e18db5

Best regards,
--  
Haris Okanovic
AWS Graviton
Re: [PATCH] genirq/proc: Fix missing CPU header in /proc/interrupts
Posted by Radu Rendec 1 week ago
On Fri, 2026-09-11 at 13:31 -0500, Haris Okanovic wrote:
> irq_seq_show() must be idempotent since it may be called multiple times
> to render the same record, e.g. when seq_file's buffer is exceeded. It
> emits the CPU header behind the one-shot print_header flag, which the
> first pass clears, so the header is lost with the discarded buffer.
> 
> This can be seen on large systems, such as 8g.48xl (192 CPUs) and
> 8i.96xl (384 CPUs) AWS instance types:
> 
>   $ head -1 /proc/interrupts
>      9:          0          0 ...  0  GICv3  25 Level  vgic
> 
> Replace print_header with the position of the first record and include
> the header whenever that record is emitted.
> 
> Fixes: 171cc0d9eed1 ("genirq/proc: Speed up /proc/interrupts iteration")
> Signed-off-by: Haris Okanovic <harisokn@amazon.com>
> ---
>  kernel/irq/proc.c | 24 +++++++++++++++++-------
>  1 file changed, 17 insertions(+), 7 deletions(-)
> 
> diff --git a/kernel/irq/proc.c b/kernel/irq/proc.c
> index 1b835725f7b1c..0c0df16de5816 100644
> --- a/kernel/irq/proc.c
> +++ b/kernel/irq/proc.c
> @@ -462,7 +462,7 @@ int __weak arch_show_interrupts(struct seq_file *p, int prec)
>  static DEFINE_RAW_SPINLOCK(irq_proc_constraints_lock);
>  
>  static struct irq_proc_constraints {
> -	bool		print_header;
> +	loff_t		first_pos;
>  	unsigned int	num_prec;
>  	unsigned int	chip_width;
>  } irq_proc_constraints __read_mostly = {
> @@ -543,14 +543,13 @@ static int irq_seq_show(struct seq_file *p, void *v)
>  	struct irqaction *action;
>  
>  	/* Print header for the first interrupt? */
> -	if (constr->print_header) {
> +	if (p->index == constr->first_pos) {
>  		unsigned int cpu;
>  
>  		seq_printf(p, "%*s", constr->num_prec + 8, "");
>  		for_each_online_cpu(cpu)
>  			seq_printf(p, "CPU%-8d", cpu);
>  		seq_putc(p, '\n');
> -		constr->print_header = false;
>  	}
>  
>  	if (desc == ARCH_PROC_IRQDESC)
> @@ -635,14 +634,25 @@ static void *irq_seq_next_desc(loff_t *pos)
>  
>  static void *irq_seq_start(struct seq_file *f, loff_t *pos)
>  {
> -	if (!*pos) {
> -		struct irq_proc_constraints *constr = f->private;
> +	struct irq_proc_constraints *constr = f->private;
> +	bool first = !*pos;
> +	void *ret;
>  
> +	if (first) {
>  		constr->num_prec = READ_ONCE(irq_proc_constraints.num_prec);
>  		constr->chip_width = READ_ONCE(irq_proc_constraints.chip_width);
> -		constr->print_header = true;
>  	}
> -	return irq_seq_next_desc(pos);
> +
> +	ret = irq_seq_next_desc(pos);
> +
> +	/*
> +	 * Record position of the first output record so that irq_seq_show()
> +	 * knows where to write the header.
> +	 */
> +	if (first)
> +		constr->first_pos = *pos;
> +
> +	return ret;
>  }
>  
>  static void *irq_seq_next(struct seq_file *f, void *v, loff_t *pos)
> 
> ---
> base-commit: 08df884136f1c1197bab2a27814404fd329d9aac
> change-id: 20260911-dev-harisokn-proc-interrupts-fix-b4-7498f7e18db5

Since I don't have access to a machine with so many CPUs, I tested
locally in a regular VM and simulated the large number of CPUs by
adding artificial buffer writes to both the header part and the IRQ
counter part of irq_seq_show(). I could reproduce the problem and
validate the fix. Also, semantically the fix looks good to me.
Thanks for catching this and for the patch!

Reviewed-by: Radu Rendec <radu@rendec.net>
Re: [PATCH] genirq/proc: Fix missing CPU header in /proc/interrupts
Posted by Okanovic, Haris 6 days, 21 hours ago
On Thu, 2026-09-17 at 22:55 -0400, Radu Rendec wrote:
> Since I don't have access to a machine with so many CPUs, I tested
> locally in a regular VM and simulated the large number of CPUs by
> adding artificial buffer writes to both the header part and the IRQ
> counter part of irq_seq_show(). I could reproduce the problem and
> validate the fix. Also, semantically the fix looks good to me.
> Thanks for catching this and for the patch!

Thanks for looking at this, and for finding a way to reproduce it without
the hardware. If a large machine would help in the future, let me know
off-list and I'll see what I can arrange.

-- 
Haris Okanovic
AWS Graviton
Re: [PATCH] genirq/proc: Fix missing CPU header in /proc/interrupts
Posted by Okanovic, Haris 1 week ago
Gentle ping on this one. Any concerns with the approach?

We'd like to get this in before more distros pick up 171cc0d9eed1. Losing the
CPU header is a user-visible format change that breaks tools parsing
/proc/interrupts, e.g. aperf:

  https://github.com/aws/aperf

Happy to respin if you'd prefer a different fix.

Regards,
-- 
Haris Okanovic
AWS Graviton