On 7/29/2026 9:20 AM, Peter Krempa wrote:
On Wed, Jul 22, 2026 at 13:52:12 -0500, Jaehoon Kim wrote:
Use the configured iothread poll-weight value when interacting with
QEMU.
This wires the new setting into command line generation, monitor data
parsing, driver-side parameter handling, and capability-based
validation. Remove the break from the iothread validation loop because
the loop now also checks poll-weight on every iothread.
Signed-off-by: Jaehoon Kim <[email protected]>
---
include/libvirt/libvirt-domain.h | 26 +++++++++
src/qemu/qemu_command.c | 18 +++++-
src/qemu/qemu_driver.c | 38 ++++++++++++
src/qemu/qemu_monitor.h | 2 +
src/qemu/qemu_monitor_json.c | 18 ++++++
src/qemu/qemu_validate.c | 10 +++-
...othreads-ids-poll-weight.x86_64-11.0.0.err | 1 +
...threads-ids-poll-weight.x86_64-latest.args | 40 +++++++++++++
...othreads-ids-poll-weight.x86_64-latest.xml | 58 +++++++++++++++++++
.../iothreads-ids-poll-weight.xml | 58 +++++++++++++++++++
tests/qemuxmlconftest.c | 2 +
11 files changed, 267 insertions(+), 4 deletions(-)
create mode 100644
tests/qemuxmlconfdata/iothreads-ids-poll-weight.x86_64-11.0.0.err
create mode 100644
tests/qemuxmlconfdata/iothreads-ids-poll-weight.x86_64-latest.args
create mode 100644
tests/qemuxmlconfdata/iothreads-ids-poll-weight.x86_64-latest.xml
create mode 100644 tests/qemuxmlconfdata/iothreads-ids-poll-weight.xml
diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h
index 5b67f8f897..f49aa1c136 100644
--- a/include/libvirt/libvirt-domain.h
+++ b/include/libvirt/libvirt-domain.h
@@ -2748,6 +2748,22 @@ int virDomainDelIOThread(virDomainPtr
domain,
*/
# define VIR_DOMAIN_IOTHREAD_POLL_SHRINK "poll_shrink"
+/**
+ * VIR_DOMAIN_IOTHREAD_POLL_WEIGHT:
+ *
+ * This provides a shift value for the adaptive polling algorithm to control
+ * how much the most recent event interval affects the next polling duration
+ * calculation. Larger values decrease the weight of the current interval,
+ * enabling more gradual adjustments. Valid range is [0, 63]. A value of 0
+ * lets the hypervisor select a default weight (typically 3, meaning the
Okay, so 0 is a special value. The parser doesn't treat it as a special
value. With this impl it would mean that there are 2 manifestation of
the same "hypervisor default" setting. One is if the 'weight' attribute
is missing completely. Second one is if it's explicitly set to '0'.
I'm not a fan of having 2 manifestations of the default but I don't have
a better sugestion either.
+ * current interval contributes approximately 1/8 to the weighted average).
+ *
+ * Accepted type is VIR_TYPED_PARAM_UINT.
+ *
+ * Since: 12.6.0
This will have to be 12.7.0, the tree is in freeze for the 12.6.0
release already.
I will fix to 12.7.0 in v2.
+ */
+# define VIR_DOMAIN_IOTHREAD_POLL_WEIGHT "poll_weight"
+
/**
* VIR_DOMAIN_IOTHREAD_THREAD_POOL_MIN:
*
@@ -4330,6 +4346,16 @@ struct _virDomainStatsRecord {
*/
# define VIR_DOMAIN_STATS_IOTHREAD_SUFFIX_POLL_SHRINK ".poll-shrink"
+/**
+ * VIR_DOMAIN_STATS_IOTHREAD_SUFFIX_POLL_WEIGHT:
+ *
+ * Polling weight factor as an unsigned int. This shift value controls how
+ * much the most recent event interval affects adaptive polling calculations.
+ * A 0 (zero) indicates the hypervisor's default weight is used.
+ *
+ * Since: 12.6.0
ditto
So here I presume the presence of this field will be based on whether
qemu supports this feature (since the stats will be filled from the
return from qemu) so 0 in fact can be shown even without config. That
does make sense for the getter.
+ */
+# define VIR_DOMAIN_STATS_IOTHREAD_SUFFIX_POLL_WEIGHT ".poll-weight"
/**
* VIR_DOMAIN_STATS_MEMORY_BANDWIDTH_MONITOR_COUNT:
diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c
index 95e2ea9a6b..b351059fc1 100644
--- a/src/qemu/qemu_command.c
+++ b/src/qemu/qemu_command.c
@@ -7659,7 +7659,8 @@ qemuBuildMemCommandLine(virCommand *cmd,
static int
qemuBuildIOThreadCommandLine(virCommand *cmd,
- const virDomainDef *def)
+ const virDomainDef *def,
+ virQEMUCaps *qemuCaps)
{
size_t i;
@@ -7694,6 +7695,19 @@ qemuBuildIOThreadCommandLine(virCommand *cmd,
NULL) < 0)
return -1;
+ if (iothread->set_poll_weight) {
+ if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_IOTHREAD_POLL_WEIGHT)) {
+ virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+ _("poll-weight is not supported by this QEMU
binary"));
+ return -1;
+ }
This check is dead code since qemuValidateDomainDefIOThreads is called
before this happens in all cases.
Agreed. This check is redundant since qemuValidateDomainDefIOThreads
already handles this case before command line generation.
I'll remove it in v2.
+
+ if (virJSONValueObjectAdd(&props,
+ "u:poll-weight", iothread->poll_weight,
+ NULL) < 0)
+ return -1;
+ }
+
if (qemuBuildObjectCommandlineFromJSON(cmd, props) < 0)
return -1;
}
[...]
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index bdc0cff66a..1afd6e83ba 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -5261,6 +5261,11 @@ qemuDomainHotplugModIOThreadIDDef(virDomainIOThreadIDDef
*def,
def->set_poll_shrink = true;
}
+ if (mondef.set_poll_weight) {
+ def->poll_weight = mondef.poll_weight;
+ def->set_poll_weight = true;
+ }
+
if (mondef.set_thread_pool_min)
def->thread_pool_min = mondef.thread_pool_min;
@@ -5353,6 +5358,11 @@ qemuDomainHotplugDelIOThread(virDomainObj *vm,
* necessary. If a 0 (zero) value is provided, QEMU resets the polling
* interval to 0 (zero) allowing the poll-grow to manipulate the time.
*
+ * - "poll-weight" - weight shift value used by the adaptive polling algorithm
+ * to determine how much the most recent event interval influences the
+ * next interval calculation. Accepted range is [0, 63]. If a 0 (zero)
+ * value is provided, QEMU uses its default weight.
+ *
* QEMU keeps track of the polling time elapsed and may grow or shrink the
* its polling interval based upon its heuristic algorithm. It is possible
* that calculations determine that it has found a "sweet spot" and no
@@ -5388,6 +5398,20 @@ qemuDomainIOThreadParseParams(virTypedParameterPtr
params,
if (rc == 1)
iothread->set_poll_shrink = true;
+ if ((rc = virTypedParamsGetUInt(params, nparams,
+ VIR_DOMAIN_IOTHREAD_POLL_WEIGHT,
+ &iothread->poll_weight)) < 0)
+ return -1;
+ if (rc == 1) {
+ if (iothread->poll_weight > 63) {
+ virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+ _("poll-weight value %1$u is out of range [0, 63]"),
+ iothread->poll_weight);
+ return -1;
+ }
+ iothread->set_poll_weight = true;
+ }
+
if ((rc = virTypedParamsGetInt(params, nparams,
VIR_DOMAIN_IOTHREAD_THREAD_POOL_MIN,
&iothread->thread_pool_min)) < 0)
[...]
diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h
index c2afb580e4..e73be4e263 100644
--- a/src/qemu/qemu_monitor.h
+++ b/src/qemu/qemu_monitor.h
@@ -1583,11 +1583,13 @@ struct _qemuMonitorIOThreadInfo {
unsigned long long poll_max_ns;
unsigned long long poll_grow;
unsigned long long poll_shrink;
+ unsigned int poll_weight;
int thread_pool_min;
int thread_pool_max;
bool set_poll_max_ns;
bool set_poll_grow;
bool set_poll_shrink;
+ bool set_poll_weight;
bool set_thread_pool_min;
bool set_thread_pool_max;
};
diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c
index 21f4d613b7..c88f74c10b 100644
--- a/src/qemu/qemu_monitor_json.c
+++ b/src/qemu/qemu_monitor_json.c
@@ -7202,6 +7202,11 @@ qemuMonitorJSONGetIOThreads(qemuMonitor *mon,
virJSONValueObjectGetNumberUlong(child, "poll-shrink",
&info->poll_shrink) == 0)
info->poll_valid = true;
+
+ /* poll-weight is optional, only present on newer QEMU */
+ if (virJSONValueObjectGetNumberUint(child, "poll-weight",
+ &info->poll_weight) == 0)
+ info->set_poll_weight = true;
}
*niothreads = n;
I'd prefer if this getter code changes are separated. The monitor can
fetch the values and not use them. Maybe even the stats entries can be
added before this is set.
I'll split in v2. The monitor getter parsing and the stats reporting will
be move into a separate patch ahead of the setter wiring.
@@ -7243,6 +7248,19 @@ qemuMonitorJSONSetIOThread(qemuMonitor *mon,
#undef VIR_IOTHREAD_SET_PROP_UL
+#define VIR_IOTHREAD_SET_PROP_UINT(propName, propVal) \
+ if (iothreadInfo->set_##propVal) { \
+ memset(&prop, 0, sizeof(prop)); \
+ prop.type = QEMU_MONITOR_OBJECT_PROPERTY_UINT; \
+ prop.val.ui = iothreadInfo->propVal; \
+ if (qemuMonitorJSONSetObjectProperty(mon, path, propName, &prop) < 0) \
+ return -1; \
+ }
+
+ VIR_IOTHREAD_SET_PROP_UINT("poll-weight", poll_weight);
+
+#undef VIR_IOTHREAD_SET_PROP_UINT
+
if (iothreadInfo->set_thread_pool_min &&
iothreadInfo->set_thread_pool_max) {
int curr_max = -1;
diff --git a/src/qemu/qemu_validate.c b/src/qemu/qemu_validate.c
index 439d4b1916..5a770a800f 100644
--- a/src/qemu/qemu_validate.c
+++ b/src/qemu/qemu_validate.c
@@ -500,9 +500,15 @@ qemuValidateDomainDefIOThreads(const virDomainDef *def,
for (i = 0; i < def->niothreadids; i++) {
virDomainIOThreadIDDef *iothread = def->iothreadids[i];
- if (iothread->thread_pool_min != -1 || iothread->thread_pool_max != -1) {
+ if (iothread->thread_pool_min != -1 || iothread->thread_pool_max != -1)
needsThreadPoolCap = true;
- break;
+
+ /* poll-weight requires QEMU_CAPS_IOTHREAD_POLL_WEIGHT */
+ if (iothread->set_poll_weight &&
+ !virQEMUCapsGet(qemuCaps, QEMU_CAPS_IOTHREAD_POLL_WEIGHT)) {
+ virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+ _("poll-weight is not supported by this QEMU
binary"));
+ return -1;
}
}
[...]
diff --git a/tests/qemuxmlconftest.c b/tests/qemuxmlconftest.c
index df0b257cbe..682aaad74f 100644
--- a/tests/qemuxmlconftest.c
+++ b/tests/qemuxmlconftest.c
@@ -2333,6 +2333,8 @@ mymain(void)
DO_TEST_CAPS_LATEST("iothreads-ids");
DO_TEST_CAPS_LATEST("iothreads-ids-partial");
DO_TEST_CAPS_LATEST("iothreads-ids-pool-sizes");
+ DO_TEST_CAPS_LATEST("iothreads-ids-poll-weight");
+ DO_TEST_CAPS_VER_PARSE_ERROR("iothreads-ids-poll-weight", "11.0.0");
I'd not bother with the negative case that just depends on capability.
A more interesting (but not strictly required) one would be with out of
range value.
I will drop the capability based negative test in v2.
Thanks,
Jaehoon