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.
> + */
> +# 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.
> +
> + 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.
> @@ -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.