cvmts: Allow specifying cgroup cpu period

probably limited utility wise but it's there now
This commit is contained in:
modeco80 2024-11-03 13:10:08 -05:00
parent fe82973b9f
commit 405e88bd1b
3 changed files with 30 additions and 13 deletions

View file

@ -57,20 +57,29 @@ qemuArgs = "qemu-system-x86_64"
vncPort = 5900 vncPort = 5900
snapshots = true snapshots = true
# Resource limits. Only works on Linux, with `Delegate=yes` set in your .service file. No-op on other platforms. # Resource limits.
# Only works on Linux, with `Delegate=yes` set in your .service file. No-op on other platforms.
# #
# cpuUsageMax optionally specifies the max CPU usage as percentage in the common top notation, so 200% means 2 CPUs, 400% is 4 CPUs, # cpuUsageMax is an optional value specifies the max CPU usage as percentage in the common top notation.
# so on. # 200% means 2 CPUs, 400% is 4 CPUs.
#
# A general reccomendation is to set this to 100*[vCPU count].
# For example, if your QEMU command line contains -smp cores=2, then cpuUsageMax should be 200.
# For an overcomitted host, you can use lower values,
# but it *can* get noticable if you throttle CPU too low.
# #
# runOnCpus is an optional array that specifies what CPUs the VM is allowed to run on. # runOnCpus is an optional array that specifies what CPUs the VM is allowed to run on.
# Systemd user slices can not delegate the cpuset controller, meaning this option will *not* work if you do not use a system service. # This option will *not* work if you do not use a system service. (effectively, it will be a loud no-op that logs an error on startup).
# (effectively, it will be a loud no-op that logs an error on startup)
# #
# limitProcess is optional (default false) and determines if only qemu vCPU threads are put into the cgroup, # periodMs is an optional value in milliseconds that specifies the cgroup's CPU accounting period.
# The default is 100 ms (which matches the cgroups2 defaults), and should work in pretty much all cases, but
# it's a knob provided for any addl. tuning purposes.
#
# limitProcess is an optional boolean (default false) that determines if only qemu vCPU threads are put into the cgroup,
# or the entire QEMU process (incl. all its threads). The default behaviour of only limiting vCPU threads # or the entire QEMU process (incl. all its threads). The default behaviour of only limiting vCPU threads
# is more than likely what you want, so the example configuration omits specifying this key. # is more than likely what you want, so the example configuration omits specifying this key.
# #
# Commenting this inline table from the configuration disables resource limiting entirely. # Commenting or removing this table from the configuration disables resource limiting entirely.
resourceLimits = { cpuUsageMax = 100, runOnCpus = [ 2, 4 ] } resourceLimits = { cpuUsageMax = 100, runOnCpus = [ 2, 4 ] }

View file

@ -19,8 +19,8 @@ export class CGroupController {
WriteValue(key: string, value: string) { WriteValue(key: string, value: string) {
try { try {
writeFileSync(path.join(this.cg.Path(), `${this.controller}.${key}`), value); writeFileSync(path.join(this.cg.Path(), `${this.controller}.${key}`), value);
} catch(e) { } catch (e) {
logger.error({error: e, controller_key: `${this.controller}.${key}`, value: value }, 'Failed to set CGroup controller value') logger.error({ error: e, controller_name: this.controller, controller_key: `${this.controller}.${key}`, value: value }, 'Failed to set CGroup controller value');
} }
} }
} }
@ -35,11 +35,11 @@ export class CGroup {
InitControllers(wants_cpuset: boolean) { InitControllers(wants_cpuset: boolean) {
// Configure this "root" cgroup to provide cpu and cpuset controllers to the leaf // Configure this "root" cgroup to provide cpu and cpuset controllers to the leaf
// QEMU cgroups. A bit iffy but whatever. // QEMU cgroups. A bit iffy but whatever.
if(wants_cpuset) { if (wants_cpuset) {
try { try {
writeFileSync(path.join(this.path, 'cgroup.subtree_control'), '+cpu +cpuset'); writeFileSync(path.join(this.path, 'cgroup.subtree_control'), '+cpu +cpuset');
} catch(err) { } catch (err) {
logger.error({error: err}, 'Could not provide cpuset controller to subtree'); logger.error({ error: err }, 'Could not provide cpuset controller to subtree. runOnCpus will not function.');
// just give up if this fails // just give up if this fails
writeFileSync(path.join(this.path, 'cgroup.subtree_control'), '+cpu'); writeFileSync(path.join(this.path, 'cgroup.subtree_control'), '+cpu');
} }

View file

@ -7,6 +7,7 @@ import { CGroup } from '../util/cgroup.js';
export interface CgroupLimits { export interface CgroupLimits {
cpuUsageMax?: number; cpuUsageMax?: number;
runOnCpus?: number[]; runOnCpus?: number[];
periodMs?: number;
limitProcess?: boolean; limitProcess?: boolean;
} }
@ -19,12 +20,19 @@ interface CGroupValue {
function MakeValuesFromLimits(limits: CgroupLimits): CGroupValue[] { function MakeValuesFromLimits(limits: CgroupLimits): CGroupValue[] {
let option_array = []; let option_array = [];
// The default period is 100 ms, which matches cgroups2 defaults.
let periodUs = 100 * 1000;
// Convert a user-configured period to us, since that's what cgroups2 expects.
if(limits.periodMs)
periodUs = limits.periodMs * 1000;
if (limits.cpuUsageMax) { if (limits.cpuUsageMax) {
// cpu.max // cpu.max
option_array.push({ option_array.push({
controller: 'cpu', controller: 'cpu',
key: 'max', key: 'max',
value: `${(limits.cpuUsageMax / 100) * 100000} 100000` value: `${(limits.cpuUsageMax / 100) * periodUs} ${periodUs}`
}); });
} }