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 <bogorodskiy@gmail.com> --- 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 <pkrempa@redhat.com>