On Wed, Apr 15, 2026 at 19:28:15 +0200, Roman Bogorodskiy wrote:
> FreeBSD supports resource limiting with the rctl(4) framework.
> It supports various resource types, including I/O resources.
> It allows to limit resources for users, processes, login classes,
> and jails.
>
> To apply blkiotune limits set limits for the bhyve process.
>
> I/O related resources supported by rctl(4) are:
>
> readbps filesystem reads, in bytes per second
> writebps filesystem writes, in bytes per second
> readiops filesystem reads, in operations per second
> writeiops filesystem writes, in operations per second
>
> Thus, the actual commands look like:
>
> rctl -a process:$bhyvepid:writebps:throttle=10000000
> rctl -a process:$bhyvepid:readbps:throttle=10000000
> rctl -a process:$bhyvepid:writeiops:throttle=20000
> rctl -a process:$bhyvepid:readiops:throttle=20000
>
> This is different from the current blkiotune modeling in libvirt as
> it requires specific device to apply limits to. To adapt this model
> to per-domain I/O limits, update domain schema to specify "*" as a
> device name.
>
> The rctl(8) may be not available or not enabled, so add a capability
> check for that.
>
> Per process rules get removed when the process disappears, so no special
> clean up is necessary.
>
> Signed-off-by: Roman Bogorodskiy <[email protected]>
> ---
> Changes since v1:
>
> - Documented the "*" device name value in formatdomain.rst
>
>
>
> - Extended bhyve driver validation to allow only "*" devices
>
>
>
> and do no allow specifying device weight, added tests
>
>
>
> for these cases
>
>
>
> - Updated the RCTL macro to execute the rctl(8) command right
>
>
>
> away instead of building an array of rules and looping
>
>
>
> through it once again
>
>
>
> - Minor formatting fixes
>
> docs/formatdomain.rst | 13 ++++--
> src/bhyve/bhyve_capabilities.c | 23 ++++++++++
> src/bhyve/bhyve_capabilities.h | 1 +
> src/bhyve/bhyve_domain.c | 20 +++++++++
> src/bhyve/bhyve_process.c | 45 +++++++++++++++++++
> src/conf/schemas/domaincommon.rng | 5 ++-
> ...bhyvexml2argv-blkiotune-invalid-device.xml | 32 +++++++++++++
> ...yvexml2argv-blkiotune-multiple-devices.xml | 39 ++++++++++++++++
> .../x86_64/bhyvexml2argv-blkiotune-weight.xml | 33 ++++++++++++++
> .../x86_64/bhyvexml2argv-blkiotune.args | 10 +++++
> .../x86_64/bhyvexml2argv-blkiotune.ldargs | 4 ++
> .../x86_64/bhyvexml2argv-blkiotune.xml | 32 +++++++++++++
> tests/bhyvexml2argvtest.c | 4 ++
> .../x86_64/bhyvexml2xmlout-blkiotune.xml | 42 +++++++++++++++++
> tests/bhyvexml2xmltest.c | 4 ++
> 15 files changed, 302 insertions(+), 5 deletions(-)
> create mode 100644
> tests/bhyvexml2argvdata/x86_64/bhyvexml2argv-blkiotune-invalid-device.xml
> create mode 100644
> tests/bhyvexml2argvdata/x86_64/bhyvexml2argv-blkiotune-multiple-devices.xml
> create mode 100644
> tests/bhyvexml2argvdata/x86_64/bhyvexml2argv-blkiotune-weight.xml
> create mode 100644
> tests/bhyvexml2argvdata/x86_64/bhyvexml2argv-blkiotune.args
> create mode 100644
> tests/bhyvexml2argvdata/x86_64/bhyvexml2argv-blkiotune.ldargs
> create mode 100644 tests/bhyvexml2argvdata/x86_64/bhyvexml2argv-blkiotune.xml
> create mode 100644
> tests/bhyvexml2xmloutdata/x86_64/bhyvexml2xmlout-blkiotune.xml
>
> diff --git a/docs/formatdomain.rst b/docs/formatdomain.rst
> index 1a4bd4c6e9..6c4e067072 100644
> --- a/docs/formatdomain.rst
> +++ b/docs/formatdomain.rst
> @@ -1367,10 +1367,15 @@ Block I/O Tuning
> associated with each guest disk device (contrast this to the <iotune>
> element of a disk definition (See `Hard drives, floppy disks, CDROMs`_)
> which can applies to an individual disk). Each ``device`` element has
> - two mandatory sub-elements, ``path`` describing the absolute path of the
> - device, and ``weight`` giving the relative weight of that device, in the
> - range [100, 1000]. After kernel 2.6.39, the value could be in the range
> [10,
> - 1000]. :since:`Since 0.9.8`
> + a mandatory ``path`` sub-element describing the absolute path of the
> + device.
> +
> + A special value ``*`` can be used to throttle all domain
> + devices. :since:`Since 12.3.0, bhyve`
> +
> + An optional ``weight`` sub-element specifies the relative
> + weight of the device, in the range [100, 1000]. After kernel 2.6.39,
> + the value could be in the range [10, 1000]. :since:`Since 0.9.8`
> Additionally, the following optional sub-elements can be used:
>
> ``read_bytes_sec``
[...]
> diff --git a/src/bhyve/bhyve_domain.c b/src/bhyve/bhyve_domain.c
> index 4594d7673f..7bce6a4bc0 100644
> --- a/src/bhyve/bhyve_domain.c
> +++ b/src/bhyve/bhyve_domain.c
> @@ -464,6 +464,26 @@ bhyveDomainDefValidate(const virDomainDef *def,
> }
> }
>
> + if (def->blkio.ndevices > 1) {
> + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
> + _("Per device I/O tuning is not supported"));
> + return -1;
> + } else if (def->blkio.ndevices == 1) {
> + virBlkioDevice *device = &def->blkio.devices[0];
> +
> + if (STRNEQ(device->path, "*")) {
> + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
> + _("Per device I/O tuning is not supported"));
> + return -1;
> + }
> +
> + if (device->weight != 0) {
> + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
> + _("Only \"*\" is supported as a device path for
> I/O tuning"));
The message doesn't correspond with what the check rejects.
I'd go with something along:
_("The 'weight' I/O tuning setting doesn't make sense with '*')
> + return -1;
> + }
> + }
> +
> if (!def->os.loader)
> return 0;
>
> diff --git a/src/bhyve/bhyve_process.c b/src/bhyve/bhyve_process.c
> index 1d436da609..3eb807b9fe 100644
> --- a/src/bhyve/bhyve_process.c
> +++ b/src/bhyve/bhyve_process.c
> @@ -34,6 +34,7 @@
>
> #include "bhyve_device.h"
> #include "bhyve_driver.h"
> +#include "bhyve_capabilities.h"
> #include "bhyve_command.h"
> #include "bhyve_firmware.h"
> #include "bhyve_monitor.h"
> @@ -132,6 +133,47 @@ bhyveProcessStopHook(struct _bhyveConn *driver,
> VIR_HOOK_SUBOP_END, NULL, xml, NULL);
> }
>
> +static int
> +bhyveSetResourceLimits(struct _bhyveConn *driver, virDomainObj *vm)
> +{
> + virBlkioDevice *device;
> +
> + if (vm->def->blkio.ndevices != 1)
> + return 0;
> +
> + if ((bhyveDriverGetBhyveCaps(driver) & BHYVE_CAP_RCTL) == 0) {
> + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
> + _("Cannot set resource limits: RACCT/RCTL is either
> not supported or not enabled"));
> + return -1;
> + }
> +
> + device = &vm->def->blkio.devices[0];
> +
> +#define BHYVE_APPLY_RCTL_RULE(field, type, format) \
> + do { \
> + if ((field)) { \
> + g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; \
> + g_autofree char *rule = NULL; \
> + g_autoptr(virCommand) cmd = virCommandNew("rctl"); \
> + virBufferAsprintf(&buf, "process:%d:" type ":throttle=" format, \
> + vm->pid, (field)); \
> + rule = virBufferContentAndReset(&buf); \
> + virCommandAddArgList(cmd, "-a", rule, NULL); \
You can simplify formatting of the command so that the extra virBuffer
and char* are not needed:
g_autoptr(virCommand) cmd = virCommandNewList("rctl", "-a", NULL);
virCommandAddArgFormat(cmd, "process:%d:" type ":throttle=" format,
vm->pid, (field));
> + if (virCommandRun(cmd, NULL) < 0) \
> + return -1; \
> + } \
> + } while (0)
> +
> + BHYVE_APPLY_RCTL_RULE(device->riops, "readiops", "%u");
> + BHYVE_APPLY_RCTL_RULE(device->wiops, "writeiops", "%u");
> + BHYVE_APPLY_RCTL_RULE(device->rbps, "readbps", "%llu");
> + BHYVE_APPLY_RCTL_RULE(device->wbps, "writebps", "%llu");
> +
> +#undef BHYVE_APPLY_RCTL_RULE
> +
> + return 0;
> +}
> +
> static int
> virBhyveProcessStartImpl(struct _bhyveConn *driver,
> virDomainObj *vm,
Reviewed-by: Peter Krempa <[email protected]>