mm/page_reporting: Add page_reporting_delay sysctl
Subsystems (2)
RFC PATCH 1/1 mm/page_reporting: Add page_reporting_delay sysctl
From: Pratyush Mallick <[email protected]>
Currently, the free page reporting daemon uses a hardcoded delay of (2 HZ) between reporting intervals. While this is a reasonable default, it lacks the flexibility to adapt to varying guest workloads.
A low delay allows aggressive memory reclamation, returning unused pages to the hypervisor as quickly as possible. However, during spiky allocation/free churn, this immediate reporting can lead to a severe performance penalty (nested page faults) as the guest re-allocates memory that the hypervisor has just unmapped. In these scenarios, there is benefit from increasing the delay to batch free pages over a longer window, absorbing the churn without hypercall and re-fault overhead.
This patch refactors the delay into a dynamically tunable sysctl, /proc/sys/vm/page_reporting_delay, measured in milliseconds. The value defaults to 2000ms to precisely match the original (2 HZ) behavior. If the sysctl is modified across reporting windows, the sysctl handler immediately issues a mod_delayed_work() to honor the new configuration without waiting for the prior timeout to lapse.
---
Sending this as an RFC to get thoughts on exposing this delay as a sysctl.
Benchmark Results:
We kill a process allocated with 10GB memory within the VM and mesure the time it takes to report all the memory to host as well the delay in initiating the reporting.
default(2s delay): https://drive.google.com/file/d/1Ouxm_raj4xPNthc_ryk0Mdbo-c_zXKiP/view?usp=sharing
Tuned to 0s delay: https://drive.google.com/file/d/1mk58LPiYgIF4Yk6kmX5JHplhrwIw0NbC/view?usp=sharing
The data shows (sysctl.vm.page_reporting_delay=0) that within ~2 seconds, the guest reports ~10 GiB and the reporting is initiated as soon as 100ms. With the default 2s delay, it takes around ~7 sec to report the same and initiation delay is around 2 sec.
mm/page_reporting.c | 47 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-)
mm/page_reporting.c +42 -5
@@ -6,6 +6,7 @@#include <linux/export.h> #include <linux/module.h> #include <linux/delay.h> +#include <linux/sysctl.h> #include <linux/scatterlist.h> #include "page_reporting.h"
@@ -47,15 +48,44 @@ MODULE_PARM_DESC(page_reporting_order, "Set page reporting order");*/ EXPORT_SYMBOL_GPL (page_reporting_order); -#define PAGE_REPORTING_DELAY (2 * HZ) -static struct page_reporting_dev_info __rcu *pr_dev_info__read_mostly ; -enum {PAGE_REPORTING_IDLE =0 ,PAGE_REPORTING_REQUESTED ,PAGE_REPORTING_ACTIVE }; +static unsigned int page_reporting_delay =2000 ; +static struct page_reporting_dev_info __rcu *pr_dev_info__read_mostly ; + +static int page_reporting_delay_sysctl (const struct ctl_table *table,int write, +void *buffer,size_t *lenp,loff_t *ppos) +{ +int ret; +struct page_reporting_dev_info *prdev; + + ret =proc_dointvec (table, write, buffer, lenp, ppos); +if (ret <0 || !write) +return ret; + +rcu_read_lock (); + prdev =rcu_dereference (pr_dev_info); +if (prdev &&atomic_read (&prdev->state) ==PAGE_REPORTING_REQUESTED ) +mod_delayed_work (system_wq, &prdev->work,msecs_to_jiffies (page_reporting_delay)); +rcu_read_unlock (); + +return 0 ; +} + +static struct ctl_table page_reporting_sysctls[] = { + { + .procname ="page_reporting_delay" , + .data = &page_reporting_delay, + .maxlen =sizeof (unsigned int ), + .mode =0644 , + .proc_handler = page_reporting_delay_sysctl, + }, +}; +/* request page reporting */ static void __page_reporting_request (struct page_reporting_dev_info *prdev)
@@ -80,7 +110,7 @@ __page_reporting_request(struct page_reporting_dev_info *prdev)* now we are limiting this to running no more than once every * couple of seconds. */ -schedule_delayed_work (&prdev->work,PAGE_REPORTING_DELAY ); +schedule_delayed_work (&prdev->work,msecs_to_jiffies (page_reporting_delay)); }/* notify prdev of free page reporting request */
@@ -343,7 +373,7 @@ static void page_reporting_process(struct work_struct *work)*/ state =atomic_cmpxchg (&prdev->state, state,PAGE_REPORTING_IDLE );if (state ==PAGE_REPORTING_REQUESTED ) -schedule_delayed_work (&prdev->work,PAGE_REPORTING_DELAY ); +schedule_delayed_work (&prdev->work,msecs_to_jiffies (page_reporting_delay)); }static DEFINE_MUTEX (page_reporting_mutex);
@@ -415,3 +445,10 @@ void page_reporting_unregister(struct page_reporting_dev_info *prdev)mutex_unlock (&page_reporting_mutex); }EXPORT_SYMBOL_GPL (page_reporting_unregister); + +static int __init page_reporting_sysctl_init (void ) +{ +register_sysctl_init ("vm" , page_reporting_sysctls); +return 0 ; +} +late_initcall (page_reporting_sysctl_init);
Signature · 1 line
2.55.0.141.g00534a21ce-goog
Quoted 26 lines · On 7/14/26 19:14, [email protected] wrote:
From: Pratyush Mallick <[email protected]>
Currently, the free page reporting daemon uses a hardcoded delay of (2 HZ) between reporting intervals. While this is a reasonable default, it lacks the flexibility to adapt to varying guest workloads.
A low delay allows aggressive memory reclamation, returning unused pages to the hypervisor as quickly as possible. However, during spiky allocation/free churn, this immediate reporting can lead to a severe performance penalty (nested page faults) as the guest re-allocates memory that the hypervisor has just unmapped. In these scenarios, there is benefit from increasing the delay to batch free pages over a longer window, absorbing the churn without hypercall and re-fault overhead.
This patch refactors the delay into a dynamically tunable sysctl,
/proc/sys/vm/page_reporting_delay, measured in milliseconds. The value defaults to 2000ms to precisely match the original (2 HZ) behavior. If the sysctl is modified across reporting windows, the sysctl handler immediately issues amod_delayed_work()to honor the new configuration without waiting for the prior timeout to lapse.Signed-off-by Pratyush Mallick <[email protected]>---
Sending this as an RFC to get thoughts on exposing this delay as a sysctl.
Quoted 7 lines
Benchmark Results:
We kill a process allocated with 10GB memory within the VM and mesure the time it takes to report all the memory to host as well the delay in initiating the reporting.default(2s delay): https://drive.google.com/file/d/1Ouxm_raj4xPNthc_ryk0Mdbo-c_zXKiP/view?usp=sharing
Tuned to 0s delay: https://drive.google.com/file/d/1mk58LPiYgIF4Yk6kmX5JHplhrwIw0NbC/view?usp=sharing
[...]
Hi!
mm/page_reporting.c MODULE_PARM_DESC(page_reporting_order, "Set page reporting order"); · line 52
Quoted 9 lines
#include "
page_reporting.h"mm/page_reporting.c +0
-3@@ -47,15 +48,44 @@ MODULE_PARM_DESC(page_reporting_order, "Set page reporting order");*/ EXPORT_SYMBOL_GPL (page_reporting_order); -#define PAGE_REPORTING_DELAY (2 * HZ) -static struct page_reporting_dev_info __rcu *pr_dev_info__read_mostly ; -
Why are you moving that?
mm/page_reporting.c MODULE_PARM_DESC(page_reporting_order, "Set page reporting order"); · line 57
Quoted 7 lines
mm/page_reporting.c +1
-0enum {PAGE_REPORTING_IDLE =0 ,PAGE_REPORTING_REQUESTED ,PAGE_REPORTING_ACTIVE }; +static unsigned int page_reporting_delay =2000 ;
Maybe "2 * MSEC_PER_SEC;"
Would we want to call that page_reporting_delay_ms to make it clearer what we are dealing with?
mm/page_reporting.c MODULE_PARM_DESC(page_reporting_order, "Set page reporting order"); · line 61
Quoted 4 lines
mm/page_reporting.c +4
-0+static struct page_reporting_dev_info __rcu *pr_dev_info__read_mostly ; + +static int page_reporting_delay_sysctl (const struct ctl_table *table,int write, +void *buffer,size_t *lenp,loff_t *ppos)
We prefer two tabs here in MM land.
mm/page_reporting.c MODULE_PARM_DESC(page_reporting_order, "Set page reporting order"); · line 68
Quoted 7 lines
mm/page_reporting.c +7
-0+{ +int ret; +struct page_reporting_dev_info *prdev; + + ret =proc_dointvec (table, write, buffer, lenp, ppos); +if (ret <0 || !write) +return ret;
Would we want to cap it at reasonable values?
mm/page_reporting.c MODULE_PARM_DESC(page_reporting_order, "Set page reporting order"); · line 74
Quoted 6 lines
mm/page_reporting.c +6
-0+ +rcu_read_lock (); + prdev =rcu_dereference (pr_dev_info); +if (prdev &&atomic_read (&prdev->state) ==PAGE_REPORTING_REQUESTED ) +mod_delayed_work (system_wq, &prdev->work,msecs_to_jiffies (page_reporting_delay)); +rcu_read_unlock ();
Is that really required? Seems unnecessary given that we expect something in the range of a couple of seconds max.
Signature · 3 lines
Cheers, David
Quoted 45 lines · David Hildenbrand (Arm)
Quoted 26 lines · On 7/14/26 19:14, [email protected] wrote:
From: Pratyush Mallick <[email protected]>
Currently, the free page reporting daemon uses a hardcoded delay of (2 HZ) between reporting intervals. While this is a reasonable default, it lacks the flexibility to adapt to varying guest workloads.
A low delay allows aggressive memory reclamation, returning unused pages to the hypervisor as quickly as possible. However, during spiky allocation/free churn, this immediate reporting can lead to a severe performance penalty (nested page faults) as the guest re-allocates memory that the hypervisor has just unmapped. In these scenarios, there is benefit from increasing the delay to batch free pages over a longer window, absorbing the churn without hypercall and re-fault overhead.
This patch refactors the delay into a dynamically tunable sysctl,
/proc/sys/vm/page_reporting_delay, measured in milliseconds. The value defaults to 2000ms to precisely match the original (2 HZ) behavior. If the sysctl is modified across reporting windows, the sysctl handler immediately issues amod_delayed_work()to honor the new configuration without waiting for the prior timeout to lapse.Signed-off-by Pratyush Mallick <[email protected]>---
Sending this as an RFC to get thoughts on exposing this delay as a sysctl.
Quoted 7 lines
Benchmark Results:
We kill a process allocated with 10GB memory within the VM and mesure the time it takes to report all the memory to host as well the delay in initiating the reporting.default(2s delay): https://drive.google.com/file/d/1Ouxm_raj4xPNthc_ryk0Mdbo-c_zXKiP/view?usp=sharing
Tuned to 0s delay: https://drive.google.com/file/d/1mk58LPiYgIF4Yk6kmX5JHplhrwIw0NbC/view?usp=sharing[...]
Hi!
Quoted 6 lines
#include "
page_reporting.h"mm/page_reporting.c +0
-0@@ -47,15 +48,44 @@ MODULE_PARM_DESC(page_reporting_order, "Set page reporting order");*/ EXPORT_SYMBOL_GPL (page_reporting_order);
Quoted 5 lines
mm/page_reporting.c MODULE_PARM_DESC(page_reporting_order, "Set page reporting order"); · line 52
Quoted 3 lines
mm/page_reporting.c +0
-3-#define PAGE_REPORTING_DELAY (2 * HZ) -static struct page_reporting_dev_info __rcu *pr_dev_info__read_mostly ; -Why are you moving that?
+1
Quoted 7 lines
Quoted 7 lines
mm/page_reporting.c +0
-0enum {PAGE_REPORTING_IDLE =0 ,PAGE_REPORTING_REQUESTED ,PAGE_REPORTING_ACTIVE };
Quoted 5 lines
mm/page_reporting.c MODULE_PARM_DESC(page_reporting_order, "Set page reporting order"); · line 57
Quoted 1 line
mm/page_reporting.c +1
-0+static unsigned int page_reporting_delay =2000 ;Maybe "2 *
MSEC_PER_SEC;"Quoted 1 line
Would we want to call that
page_reporting_delay_msto make it clearer what weare dealing with?
+1
Quoted 8 lines
mm/page_reporting.c MODULE_PARM_DESC(page_reporting_order, "Set page reporting order"); · line 61
Quoted 5 lines
mm/page_reporting.c +4
-0+static struct page_reporting_dev_info __rcu *pr_dev_info__read_mostly ; + +static int page_reporting_delay_sysctl (const struct ctl_table *table,int write, +void *buffer,size_t *lenp,loff_t *ppos)We prefer two tabs here in MM land.
Quoted 9 lines
mm/page_reporting.c MODULE_PARM_DESC(page_reporting_order, "Set page reporting order"); · line 68
Quoted 7 lines
mm/page_reporting.c +7
-0+{ +int ret; +struct page_reporting_dev_info *prdev; + + ret =proc_dointvec (table, write, buffer, lenp, ppos); +if (ret <0 || !write) +return ret;Would we want to cap it at reasonable values?
Possibly with a macro PAGE_REPORTING_DELAY_MS_MAX or similar.
Quoted 10 lines
mm/page_reporting.c MODULE_PARM_DESC(page_reporting_order, "Set page reporting order"); · line 74
Quoted 7 lines
mm/page_reporting.c +6
-0+ +rcu_read_lock (); + prdev =rcu_dereference (pr_dev_info); +if (prdev &&atomic_read (&prdev->state) ==PAGE_REPORTING_REQUESTED ) +mod_delayed_work (system_wq, &prdev->work,msecs_to_jiffies (page_reporting_delay)); +rcu_read_unlock ();Is that really required? Seems unnecessary given that we expect something in the range of a couple of seconds max.
Agreed.
Quoted 7 lines · Anshuman Khandual
Quoted 4 lines · David Hildenbrand (Arm)
mm/page_reporting.c +0
-3-#define PAGE_REPORTING_DELAY (2 * HZ) -static struct page_reporting_dev_info __rcu *pr_dev_info__read_mostly ; -Why are you moving that?
+1
I wanted the new definitions grouped together, and the sysctl handler has to sit below the enum so it can see PAGE_REPORTING_REQUESTED. But that only constrains where the handler goes, not the declaration. I'll leave pr_dev_info where it was.
Quoted 8 lines
Quoted 6 lines
+static unsigned int
page_reporting_delay= 2000;Maybe "2 *
MSEC_PER_SEC;"Would we want to call that
page_reporting_delay_msto make it clearer what we are dealing with?+1
Agreed on both. I'll rename the sysctl to page_reporting_delay_ms as well in v2.
Quoted 4 lines
Quoted 4 lines
mm/page_reporting.c +2
-0+static int page_reporting_delay_sysctl (const struct ctl_table *table,int write, +void *buffer,size_t *lenp,loff_t *ppos)We prefer two tabs here in MM land.
Thanks. Will fix this.
Quoted 7 lines
Quoted 5 lines
mm/page_reporting.c +3
-0+ ret =proc_dointvec (table, write, buffer, lenp, ppos); +if (ret <0 || !write) +return ret;Would we want to cap it at reasonable values?
Possibly with a macro
PAGE_REPORTING_DELAY_MS_MAXor similar.
Yes. Will fix this in v2.
Will use proc_douintvec_minmax() with a floor and a ceiling (using a new PAGE_REPORTING_DELAY_MS_MAX macro).
Quoted 10 lines
Quoted 8 lines
mm/page_reporting.c +5
-0+rcu_read_lock (); + prdev =rcu_dereference (pr_dev_info); +if (prdev &&atomic_read (&prdev->state) ==PAGE_REPORTING_REQUESTED ) + mod_delayed_work(...); +rcu_read_unlock ();Is that really required? Seems unnecessary given that we expect something in the range of a couple of seconds max.
Agreed.
Our use case is that the delay isn't static. A daemon in the guest watches for host memory pressure and drops the delay to expedite reporting, then raises it again once the pressure clears. The delay can be tens of seconds when we're absorbing churn, so waiting out the current window defeats the purpose of the dynamic adjustment. Apologies, I should have clarified this in the original commit message.
I'm happy to drop it or decouple this triggering behavior from the tuning behavior. We could probably create a separate sysctl to manually trigger the reporting instead.
Thanks to both of you for the review.
Pratyush Mallick.