Skip to contentSkip to conversation

genirq, proc: Speedup /proc/stat interrupt statistics

PATCH Superseded Thomas Gleixner 4 participants 12 messages Jan 30, 2019 – Jan 31, 2019

Subsystems (3)

v1 of 2 Superseded Latest is v2 →

lore
Share & tools
lore
#0 lore raw

cover genirq, proc: Speedup /proc/stat interrupt statistics

Waiman reported that on large systems with a large amount of interrupts the readout of /proc/stat takes a long time to sum up the interrupt statistics. In principle this is not a problem. but for unknown reasons some enterprise quality software reads /proc/stat with a high frequency.

The reason for this is that interrupt statistics are accounted per cpu. So the /proc/stat logic has to sum up the interrupt stats for each interrupt.

The following series addresses this by making the interrupt statitics code in the core generate the sum directly and by making the loop in the /proc/stat read function smarter.

Thanks,

tglx

8<----------------

 fs/proc/stat.c          |   28 +++++++++++++++++++++++++---
 include/linux/irqdesc.h |    3 ++-
 kernel/irq/chip.c       |   12 ++++++++++--
 kernel/irq/internals.h  |    8 +++++++-
 kernel/irq/irqdesc.c    |    7 ++++++-
 5 files changed, 50 insertions(+), 8 deletions(-)
#1 lore raw

PATCH 1/2 genriq: Avoid summation loops for /proc/stat

Waiman reported that on large systems with a large amount of interrupts the readout of /proc/stat takes a long time to sum up the interrupt statistics. In principle this is not a problem. but for unknown reasons some enterprise quality software reads /proc/stat with a high frequency.

The reason for this is that interrupt statistics are accounted per cpu. So the /proc/stat logic has to sum up the interrupt stats for each interrupt.

This can be largely avoided for interrupts which are not marked as 'PER_CPU' interrupts by simply adding a per interrupt summation counter which is incremented along with the per interrupt per cpu counter.

The PER_CPU interrupts need to avoid that and use only per cpu accounting because they share the interrupt number and the interrupt descriptor and concurrent updates would conflict or require unwanted synchronization.

8<-------------

 include/linux/irqdesc.h |    3 ++-
 kernel/irq/chip.c       |   12 ++++++++++--
 kernel/irq/internals.h  |    8 +++++++-
 kernel/irq/irqdesc.c    |    7 ++++++-
 4 files changed, 25 insertions(+), 5 deletions(-)
include/linux/irqdesc.h +2 -1
@@ -65,9 +65,10 @@ struct irq_desc {
 	unsigned int		core_internal_state__do_not_mess_with_it;
 	unsigned int		depth;		/* nested irq disables */
 	unsigned int		wake_depth;	/* nested wake enables */
+	unsigned int		tot_count;
 	unsigned int		irq_count;	/* For detecting broken IRQs */
-	unsigned long		last_unhandled;	/* Aging timer for unhandled count */
 	unsigned int		irqs_unhandled;
+	unsigned long		last_unhandled;	/* Aging timer for unhandled count */
 	atomic_t		threads_handled;
 	int			threads_handled_last;
 	raw_spinlock_t		lock;
kernel/irq/chip.c +10 -2
@@ -855,7 +855,11 @@ void handle_percpu_irq(struct irq_desc *
 {
 	struct irq_chip *chip = irq_desc_get_chip(desc);
 
-	kstat_incr_irqs_this_cpu(desc);
+	/*
+	 * PER CPU interrupts are not serialized. Do not touch
+	 * desc->tot_count.
+	 */
+	__kstat_incr_irqs_this_cpu(desc);
 
 	if (chip->irq_ack)
 		chip->irq_ack(&desc->irq_data);
@@ -884,7 +888,11 @@ void handle_percpu_devid_irq(struct irq_
 	unsigned int irq = irq_desc_get_irq(desc);
 	irqreturn_t res;
 
-	kstat_incr_irqs_this_cpu(desc);
+	/*
+	 * PER CPU interrupts are not serialized. Do not touch
+	 * desc->tot_count.
+	 */
+	__kstat_incr_irqs_this_cpu(desc);
 
 	if (chip->irq_ack)
 		chip->irq_ack(&desc->irq_data);
kernel/irq/internals.h +7 -1
@@ -242,12 +242,18 @@ static inline void irq_state_set_masked(
 
 #undef __irqd_to_state
 
-static inline void kstat_incr_irqs_this_cpu(struct irq_desc *desc)
+static inline void __kstat_incr_irqs_this_cpu(struct irq_desc *desc)
 {
 	__this_cpu_inc(*desc->kstat_irqs);
 	__this_cpu_inc(kstat.irqs_sum);
 }
 
+static inline void kstat_incr_irqs_this_cpu(struct irq_desc *desc)
+{
+	__kstat_incr_irqs_this_cpu(desc);
+	desc->tot_count++;
+}
+
 static inline int irq_desc_get_node(struct irq_desc *desc)
 {
 	return irq_common_data_get_node(&desc->irq_common_data);
kernel/irq/irqdesc.c +6 -1
@@ -119,6 +119,7 @@ static void desc_set_defaults(unsigned i
 	desc->depth = 1;
 	desc->irq_count = 0;
 	desc->irqs_unhandled = 0;
+	desc->tot_count = 0;
 	desc->name = NULL;
 	desc->owner = owner;
 	for_each_possible_cpu(cpu)
@@ -919,11 +920,15 @@ unsigned int kstat_irqs_cpu(unsigned int
 unsigned int kstat_irqs(unsigned int irq)
 {
 	struct irq_desc *desc = irq_to_desc(irq);
-	int cpu;
 	unsigned int sum = 0;
+	int cpu;
 
 	if (!desc || !desc->kstat_irqs)
 		return 0;
+	if (!irq_settings_is_per_cpu_devid(desc) &&
+	    !irq_settings_is_per_cpu(desc))
+	    return desc->tot_count;
+
 	for_each_possible_cpu(cpu)
 		sum += *per_cpu_ptr(desc->kstat_irqs, cpu);
 	return sum;
#2 lore raw

Re: [patch 1/2] genriq: Avoid summation loops for /proc/stat

Quoted 32 lines · Thomas Gleixner

Waiman reported that on large systems with a large amount of interrupts the readout of /proc/stat takes a long time to sum up the interrupt statistics. In principle this is not a problem. but for unknown reasons some enterprise quality software reads /proc/stat with a high frequency.

The reason for this is that interrupt statistics are accounted per cpu. So the /proc/stat logic has to sum up the interrupt stats for each interrupt.

This can be largely avoided for interrupts which are not marked as 'PER_CPU' interrupts by simply adding a per interrupt summation counter which is incremented along with the per interrupt per cpu counter.

The PER_CPU interrupts need to avoid that and use only per cpu accounting because they share the interrupt number and the interrupt descriptor and concurrent updates would conflict or require unwanted synchronization.

8<-------------

 include/linux/irqdesc.h |    3 ++-
 kernel/irq/chip.c       |   12 ++++++++++--
 kernel/irq/internals.h  |    8 +++++++-
 kernel/irq/irqdesc.c    |    7 ++++++-
 4 files changed, 25 insertions(+), 5 deletions(-)
include/linux/irqdesc.h +0 -0
@@ -65,9 +65,10 @@ struct irq_desc {
 	unsigned int		core_internal_state__do_not_mess_with_it;

include/linux/irqdesc.h struct irq_desc { · line 74

Quoted 10 lines
include/linux/irqdesc.h +2 -1
 	unsigned int		depth;		/* nested irq disables */
 	unsigned int		wake_depth;	/* nested wake enables */
+	unsigned int		tot_count;
 	unsigned int		irq_count;	/* For detecting broken IRQs */
-	unsigned long		last_unhandled;	/* Aging timer for unhandled count */
 	unsigned int		irqs_unhandled;
+	unsigned long		last_unhandled;	/* Aging timer for unhandled count */
 	atomic_t		threads_handled;
 	int			threads_handled_last;
 	raw_spinlock_t		lock;

Just one minor nit. Why you want to move the last_unhandled down one slot? There were 5 int's before. Adding one more will just fill the padding hole. Moving down the last_unhandled will probably leave 4-byte holes in both above and below it assuming that raw_spinlock_t is 4 bytes.

Cheers,
Longman

#3 lore raw

Re: [patch 1/2] genriq: Avoid summation loops for /proc/stat

Quoted 12 lines · Waiman Long
Quoted 11 lines · Thomas Gleixner
include/linux/irqdesc.h +2 -1
@@ -65,9 +65,10 @@ struct irq_desc {
 	unsigned int		core_internal_state__do_not_mess_with_it;
 	unsigned int		depth;		/* nested irq disables */
 	unsigned int		wake_depth;	/* nested wake enables */
+	unsigned int		tot_count;
 	unsigned int		irq_count;	/* For detecting broken IRQs */
-	unsigned long		last_unhandled;	/* Aging timer for unhandled count */
 	unsigned int		irqs_unhandled;
+	unsigned long		last_unhandled;	/* Aging timer for unhandled count */
Quoted 8 lines

include/linux/irqdesc.h struct irq_desc { · line 74

Quoted 3 lines
include/linux/irqdesc.h +0 -0
 	atomic_t		threads_handled;
 	int			threads_handled_last;
 	raw_spinlock_t		lock;

Just one minor nit. Why you want to move the last_unhandled down one slot? There were 5 int's before. Adding one more will just fill the padding hole. Moving down the last_unhandled will probably leave 4-byte holes in both above and below it assuming that raw_spinlock_t is 4 bytes.

Unintentional wreckage. Will undo. Thanks for spotting it.

Thanks,

tglx

#4 lore raw

PATCH 2/2 proc/stat: Make the interrupt statistics more efficient

In reply to Thomas Gleixner

Waiman reported that on large systems with a large amount of interrupts the readout of /proc/stat takes a long time to sum up the interrupt statistics. In principle this is not a problem. but for unknown reasons some enterprise quality software reads /proc/stat with a high frequency.

The reason for this is that interrupt statistics are accounted per cpu. So the /proc/stat logic has to sum up the interrupt stats for each interrupt.

The interrupt core provides now a per interrupt summary counter which can be used to avoid the summation loops completely except for interrupts marked PER_CPU which are only a small fraction of the interrupt space if at all.

Another simplification is to iterate only over the active interrupts and skip the potentially large gaps in the interrupt number space and just print zeros for the gaps without going into the interrupt core in the first place.

---
 fs/proc/stat.c |   28 +++++++++++++++++++++++++---
 1 file changed, 25 insertions(+), 3 deletions(-)
fs/proc/stat.c +25 -3
@@ -79,6 +79,30 @@ static u64 get_iowait_time(int cpu)
 
 #endif
 
+static void show_irq_gap(struct seq_file *p, int gap)
+{
+	static const char zeros[] = " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0";
+
+	while (gap > 0) {
+		int inc = min_t(int, gap, ARRAY_SIZE(zeros) / 2);
+
+		seq_write(p, zeros, 2 * inc);
+		gap -= inc;
+	}
+}
+
+static void show_all_irqs(struct seq_file *p)
+{
+	int i, next = 0;
+
+	for_each_active_irq(i) {
+		show_irq_gap(p, i - next);
+		seq_put_decimal_ull(p, " ", kstat_irqs_usr(i));
+		next = i + 1;
+	}
+	show_irq_gap(p, nr_irqs - next);
+}
+
 static int show_stat(struct seq_file *p, void *v)
 {
 	int i, j;
@@ -156,9 +180,7 @@ static int show_stat(struct seq_file *p,
 	}
 	seq_put_decimal_ull(p, "intr ", (unsigned long long)sum);
 
-	/* sum again ? it could be updated? */
-	for_each_irq_nr(j)
-		seq_put_decimal_ull(p, " ", kstat_irqs_usr(j));
+	show_all_irqs(p);
 
 	seq_printf(p,
 		"\nctxt %llu\n"
#5 lore raw

Re: [patch 2/2] proc/stat: Make the interrupt statistics more efficient

Quoted 13 lines · Thomas Gleixner
fs/proc/stat.c +13 -0
+static void show_irq_gap(struct seq_file *p, int gap)
+{
+	static const char zeros[] = " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0";
+
+	while (gap > 0) {
+		int inc = min_t(int, gap, ARRAY_SIZE(zeros) / 2);
+
+		seq_write(p, zeros, 2 * inc);
+		gap -= inc;
+	}
+}
+
+static void show_all_irqs(struct seq_file *p)

fs/proc/stat.c static u64 get_iowait_time(int cpu) · line 104

Quoted 10 lines
fs/proc/stat.c +10 -0
+{
+	int i, next = 0;
+
+	for_each_active_irq(i) {
+		show_irq_gap(p, i - next);
+		seq_put_decimal_ull(p, " ", kstat_irqs_usr(i));
+		next = i + 1;
+	}
+	show_irq_gap(p, nr_irqs - next);
+}

Every signed int can and should be unsigned int in this patch.

#6 lore raw

Re: [patch 2/2] proc/stat: Make the interrupt statistics more efficient

Quoted 18 lines · Alexey Dobriyan
Quoted 17 lines · Thomas Gleixner
fs/proc/stat.c +17 -0
+static void show_irq_gap(struct seq_file *p, int gap)
+{
+	static const char zeros[] = " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0";
+
+	while (gap > 0) {
+		int inc = min_t(int, gap, ARRAY_SIZE(zeros) / 2);
+
+		seq_write(p, zeros, 2 * inc);
+		gap -= inc;
+	}
+}
+
+static void show_all_irqs(struct seq_file *p)
+{
+	int i, next = 0;
+
+	for_each_active_irq(i) {
Quoted 9 lines

fs/proc/stat.c static u64 get_iowait_time(int cpu) · line 104

Quoted 6 lines
fs/proc/stat.c +6 -0
+		show_irq_gap(p, i - next);
+		seq_put_decimal_ull(p, " ", kstat_irqs_usr(i));
+		next = i + 1;
+	}
+	show_irq_gap(p, nr_irqs - next);
+}

Every signed int can and should be unsigned int in this patch.

Indeed.

Acked-by on the v1 series
#7 lore raw
In reply to Thomas Gleixner
Quoted 16 lines · Thomas Gleixner

Waiman reported that on large systems with a large amount of interrupts the readout of /proc/stat takes a long time to sum up the interrupt statistics. In principle this is not a problem. but for unknown reasons some enterprise quality software reads /proc/stat with a high frequency.

The reason for this is that interrupt statistics are accounted per cpu. So the /proc/stat logic has to sum up the interrupt stats for each interrupt.

The following series addresses this by making the interrupt statitics code in the core generate the sum directly and by making the loop in the /proc/stat read function smarter.

Thanks,

tglx

Quoted 10 lines

8<----------------

 fs/proc/stat.c          |   28 +++++++++++++++++++++++++---
 include/linux/irqdesc.h |    3 ++-
 kernel/irq/chip.c       |   12 ++++++++++--
 kernel/irq/internals.h  |    8 +++++++-
 kernel/irq/irqdesc.c    |    7 ++++++-
 5 files changed, 50 insertions(+), 8 deletions(-)

For the whole series,

#8 lore raw
In reply to Thomas Gleixner
Quoted 5 lines · On Wed, 30 Jan 2019 13:31:30 +0100 Thomas Gleixner <[email protected]> wrote:

Waiman reported that on large systems with a large amount of interrupts the readout of /proc/stat takes a long time to sum up the interrupt statistics. In principle this is not a problem. but for unknown reasons some enterprise quality software reads /proc/stat with a high frequency.

Quoted 7 lines

The reason for this is that interrupt statistics are accounted per cpu. So the /proc/stat logic has to sum up the interrupt stats for each interrupt.

The following series addresses this by making the interrupt statitics code in the core generate the sum directly and by making the loop in the /proc/stat read function smarter.

Has the speedup been quantified?

#9 lore raw
Quoted 10 lines · Andrew Morton
Quoted 8 lines · On Wed, 30 Jan 2019 13:31:30 +0100 Thomas Gleixner <[email protected]> wrote:

Waiman reported that on large systems with a large amount of interrupts the readout of /proc/stat takes a long time to sum up the interrupt statistics. In principle this is not a problem. but for unknown reasons some enterprise quality software reads /proc/stat with a high frequency.

The reason for this is that interrupt statistics are accounted per cpu. So the /proc/stat logic has to sum up the interrupt stats for each interrupt.

Quoted 6 lines
Quoted 4 lines

The following series addresses this by making the interrupt statitics code in the core generate the sum directly and by making the loop in the /proc/stat read function smarter.

Has the speedup been quantified?

Waiman should be able to provide numbers

#10 lore raw
Quoted 11 lines · Thomas Gleixner
Quoted 10 lines · Andrew Morton
Quoted 8 lines · On Wed, 30 Jan 2019 13:31:30 +0100 Thomas Gleixner <[email protected]> wrote:

Waiman reported that on large systems with a large amount of interrupts the readout of /proc/stat takes a long time to sum up the interrupt statistics. In principle this is not a problem. but for unknown reasons some enterprise quality software reads /proc/stat with a high frequency.

The reason for this is that interrupt statistics are accounted per cpu. So the /proc/stat logic has to sum up the interrupt stats for each interrupt.

Quoted 8 lines
Quoted 5 lines
Quoted 4 lines

The following series addresses this by making the interrupt statitics code in the core generate the sum directly and by making the loop in the /proc/stat read function smarter.

Has the speedup been quantified?

Waiman should be able to provide numbers

Will run some performance test on the patch and give you guys the result sometime tomorrow.

Cheers,
Longman

#11 lore raw
In reply to Thomas Gleixner
Quoted 11 lines · Thomas Gleixner
Quoted 10 lines · Andrew Morton
Quoted 8 lines · On Wed, 30 Jan 2019 13:31:30 +0100 Thomas Gleixner <[email protected]> wrote:

Waiman reported that on large systems with a large amount of interrupts the readout of /proc/stat takes a long time to sum up the interrupt statistics. In principle this is not a problem. but for unknown reasons some enterprise quality software reads /proc/stat with a high frequency.

The reason for this is that interrupt statistics are accounted per cpu. So the /proc/stat logic has to sum up the interrupt stats for each interrupt.

Quoted 8 lines
Quoted 5 lines
Quoted 4 lines

The following series addresses this by making the interrupt statitics code in the core generate the sum directly and by making the loop in the /proc/stat read function smarter.

Has the speedup been quantified?

Waiman should be able to provide numbers

On a 4-socket IvyBridge-EX system (60-core 120-thread) and 3016 irqs, I ran a test program that read /proc/stat 50,000 time. Before the patch, the elapsed time was 18.436s (sys 18.380s). After the patch, it was 3.769s (sys 3.742s). It was an almost 80% reduction in execution time. It was better than I expected. I like that.

Cheers,
Longman