layered: Initialize layers before attaching scheduler

We currently have a bug in layered wherein we could fail to propagate
layer updates from user space to kernel space if a layer is never
adjusted after it's first initialized. For example, in the following
configuration:

[
	{
		"name": "workload.slice",
		"comment": "main workload slice",
		"matches": [
			[
				{
					"CgroupPrefix": "workload.slice/"
				}
			]
		],
		"kind": {
			"Grouped": {
				"cpus_range": [30, 30],
				"util_range": [
					0.0,
					1.0
				],
				"preempt": false
			}
		}
	},
	{
		"name": "normal",
		"comment": "the rest",
		"matches": [
			[]
		],
		"kind": {
			"Grouped": {
				"cpus_range": [2, 2],
				"util_range": [
					0.0,
					1.0
				],
				"preempt": false
			}
		}
	}
]

Both layers are static, and need only be resized a single time, so the
configuration would never be propagated to the kernel due to us never
calling update_bpf_layer_cpumask(). Let's instead have the
initialization propagate changes to the skeleton before we attach the
scheduler.

This has the advantage both of fixing the bug mentioned above where a
static configuration is never propagated to the kernel, and that we
don't have a short period when the scheduler is first attached where we
don't make optimal scheduling decisions due to the layer resizing not
being propagated.

Signed-off-by: David Vernet <void@manifault.com>
This commit is contained in:
David Vernet 2024-02-21 14:57:10 -06:00
parent 59bd206559
commit 1f834e7f94
No known key found for this signature in database
GPG Key ID: 59E4B86965C4F364

View File

@ -841,37 +841,13 @@ impl Layer {
let nr_cpus = cpu_pool.nr_cpus;
let mut layer = Self {
Ok(Self {
name: name.into(),
kind,
nr_cpus: 0,
cpus: bitvec![0; nr_cpus],
};
match &layer.kind {
LayerKind::Confined {
cpus_range,
util_range,
}
| LayerKind::Grouped {
cpus_range,
util_range,
..
} => {
layer.resize_confined_or_grouped(
cpu_pool,
*cpus_range,
*util_range,
(0.0, 0.0),
(0.0, 0.0),
false,
)?;
}
_ => {}
}
Ok(layer)
})
}
fn grow_confined_or_grouped(
@ -1239,17 +1215,7 @@ impl<'a> Scheduler<'a> {
}
Self::init_layers(&mut skel, &layer_specs)?;
// Attach.
let mut skel = skel.load().context("Failed to load BPF program")?;
skel.attach().context("Failed to attach BPF program")?;
let struct_ops = Some(
skel.maps_mut()
.layered()
.attach_struct_ops()
.context("Failed to attach layered struct ops")?,
);
info!("Layered Scheduler Attached");
let mut layers = vec![];
for spec in layer_specs.iter() {
layers.push(Layer::new(&mut cpu_pool, &spec.name, spec.kind.clone())?);
@ -1258,8 +1224,8 @@ impl<'a> Scheduler<'a> {
// Other stuff.
let proc_reader = procfs::ProcReader::new();
Ok(Self {
struct_ops, // should be held to keep it attached
let mut sched = Self {
struct_ops: None,
layer_specs,
sched_intv: Duration::from_secs_f64(opts.interval),
@ -1281,7 +1247,22 @@ impl<'a> Scheduler<'a> {
om_stats: OpenMetricsStats::new(),
om_format: opts.open_metrics_format,
})
};
// Initialize layers before we attach the scheduler
sched.refresh_cpumasks()?;
// Attach.
sched.skel.attach().context("Failed to attach BPF program")?;
sched.struct_ops = Some(
sched.skel.maps_mut()
.layered()
.attach_struct_ops()
.context("Failed to attach layered struct ops")?,
);
info!("Layered Scheduler Attached");
Ok(sched)
}
fn update_bpf_layer_cpumask(layer: &Layer, bpf_layer: &mut bpf_bss_types::layer) {
@ -1295,10 +1276,7 @@ impl<'a> Scheduler<'a> {
bpf_layer.refresh_cpus = 1;
}
fn step(&mut self) -> Result<()> {
let started_at = Instant::now();
self.sched_stats
.refresh(&mut self.skel, &self.proc_reader, started_at)?;
fn refresh_cpumasks(&mut self) -> Result<()> {
let mut updated = false;
for idx in 0..self.layers.len() {
@ -1366,6 +1344,16 @@ impl<'a> Scheduler<'a> {
}
}
Ok(())
}
fn step(&mut self) -> Result<()> {
let started_at = Instant::now();
self.sched_stats
.refresh(&mut self.skel, &self.proc_reader, started_at)?;
self.refresh_cpumasks()?;
self.processing_dur += Instant::now().duration_since(started_at);
Ok(())
}