Skip to content

Commit 88fbde7

Browse files
authored
Publish richer virtual disk statistics to oximeter (#746)
* Publish richer virtual disk statistics to oximeter - Use the `virtual_disk:*` timeseries defined in Omicron to publish statistics about the block operations performed on virtual disks. - Add concept of a completion callback to the existing block tracking object, and expose a way to set the callback on a `block::Device`. - Create object for managing all the statistics for one virtual disk backed by Crucible. This is shared between the existing oximeter producer, and the block devices, via the above completion callback mechanism. As I/Os complete, the stats are updated, and oximeter collects them periodically. - Straighten out a bunch of the initialiazation logic for the pre-existing metrics, producer server, and registry. This grew pretty organically, and was getting unwieldy and confusing. - Use `virtual_machine:reset` definition from TOML - Move kstat sampler creation to machine initialization process, rather than buried in the producer server startup. This lets us use the sampler in-line when initializing the vCPUs, where it should be much easier to re-use when we want to add more kstat-based metrics, such as Viona link stats. - Simplify and cleanup the `ServerStats` types, and remove the kstat sampler from it, which never should have been there at all. - Bump kstat-rs dep, which includes the new version that is cross-platform. This gets rid of a bunch of cfg-soup in the stats modules. * Shorten collection interval, since disk stats are only polled * Small fixes - Use 512B as the lowest I/O size histogram bin - Update Crucible dep so we can test in CI - Fixups for some cfg directives around kstats * Review feedback - Clarify when we create kstat sampler - Style nits and comment cleanup - Don't return early from blockdev initialization if we fail to track stats, just continue instead * Rename oximeter collection interval for clarity * Review feedback - Remove default impl of `set_completion_callback()` - Cleanup internals of `Tracking` creation * Point back to Omicron / Crucible main branches
1 parent 39d6150 commit 88fbde7

File tree

13 files changed

+1493
-451
lines changed

13 files changed

+1493
-451
lines changed

Cargo.lock

Lines changed: 855 additions & 310 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ oximeter = { git = "https://github.com/oxidecomputer/omicron", branch = "main" }
8181
sled-agent-client = { git = "https://github.com/oxidecomputer/omicron", branch = "main" }
8282

8383
# Crucible
84-
crucible = { git = "https://github.com/oxidecomputer/crucible", rev = "e58ca3693cb9ce0438947beba10e97ee38a0966b" }
85-
crucible-client-types = { git = "https://github.com/oxidecomputer/crucible", rev = "e58ca3693cb9ce0438947beba10e97ee38a0966b" }
84+
crucible = { git = "https://github.com/oxidecomputer/crucible", rev = "d037eeb9a56a8a62ff17266f340c011224d15146" }
85+
crucible-client-types = { git = "https://github.com/oxidecomputer/crucible", rev = "d037eeb9a56a8a62ff17266f340c011224d15146" }
8686

8787
# External dependencies
8888
anyhow = "1.0"
@@ -120,7 +120,7 @@ http = "0.2.9"
120120
hyper = "0.14"
121121
indicatif = "0.17.3"
122122
inventory = "0.3.0"
123-
kstat-rs = "0.2.3"
123+
kstat-rs = "0.2.4"
124124
lazy_static = "1.4"
125125
libc = "0.2"
126126
mockall = "0.12"

bin/propolis-server/src/lib/initializer.rs

Lines changed: 83 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,17 @@ use std::sync::Arc;
1111
use std::time::{SystemTime, UNIX_EPOCH};
1212

1313
use crate::serial::Serial;
14-
use crate::stats::virtual_machine::VirtualMachine;
14+
use crate::stats::{
15+
create_kstat_sampler, track_vcpu_kstats, virtual_disk::VirtualDiskProducer,
16+
virtual_machine::VirtualMachine,
17+
};
1518
use crate::vm::{BlockBackendMap, CrucibleBackendMap, DeviceMap};
1619
use anyhow::{Context, Result};
1720
use crucible_client_types::VolumeConstructionRequest;
1821
pub use nexus_client::Client as NexusClient;
1922
use oximeter::types::ProducerRegistry;
2023

24+
use oximeter_instruments::kstat::KstatSampler;
2125
use propolis::block;
2226
use propolis::chardev::{self, BlockingSource, Source};
2327
use propolis::common::{Lifecycle, GB, MB, PAGE_SIZE};
@@ -491,7 +495,8 @@ impl<'a> MachineInitializer<'a> {
491495
Nvme,
492496
}
493497

494-
for (name, device_spec) in &self.spec.devices.storage_devices {
498+
'devloop: for (name, device_spec) in &self.spec.devices.storage_devices
499+
{
495500
info!(
496501
self.log,
497502
"Creating storage device {} with properties {:?}",
@@ -542,14 +547,15 @@ impl<'a> MachineInitializer<'a> {
542547
.await?;
543548

544549
self.block_backends.insert(backend_name.clone(), backend.clone());
545-
match device_interface {
550+
let block_dev: Arc<dyn block::Device> = match device_interface {
546551
DeviceInterface::Virtio => {
547552
let vioblk = virtio::PciVirtioBlock::new(0x100);
548553

549554
self.devices
550555
.insert(format!("pci-virtio-{}", bdf), vioblk.clone());
551556
block::attach(vioblk.clone(), backend).unwrap();
552-
chipset.pci_attach(bdf, vioblk);
557+
chipset.pci_attach(bdf, vioblk.clone());
558+
vioblk
553559
}
554560
DeviceInterface::Nvme => {
555561
// Limit data transfers to 1MiB (2^8 * 4k) in size
@@ -564,18 +570,59 @@ impl<'a> MachineInitializer<'a> {
564570
self.devices
565571
.insert(format!("pci-nvme-{bdf}"), nvme.clone());
566572
block::attach(nvme.clone(), backend).unwrap();
567-
chipset.pci_attach(bdf, nvme);
573+
chipset.pci_attach(bdf, nvme.clone());
574+
nvme
568575
}
569576
};
570577

571-
if let Some((id, backend)) = crucible {
572-
let prev = self.crucible_backends.insert(id, backend);
578+
if let Some((disk_id, backend)) = crucible {
579+
let block_size = backend.block_size().await;
580+
let prev = self.crucible_backends.insert(disk_id, backend);
573581
if prev.is_some() {
574582
return Err(Error::new(
575583
ErrorKind::InvalidInput,
576-
format!("multiple disks with id {}", id),
584+
format!("multiple disks with id {}", disk_id),
577585
));
578586
}
587+
588+
let Some(block_size) = block_size else {
589+
slog::error!(
590+
self.log,
591+
"Could not get Crucible backend block size, \
592+
virtual disk metrics can't be reported for it";
593+
"disk_id" => %disk_id,
594+
);
595+
continue 'devloop;
596+
};
597+
598+
// Register the block device as a metric producer, if we've been
599+
// setup to do so. Note we currently only do this for the Crucible
600+
// backend, in which case we have the disk ID.
601+
if let Some(registry) = &self.producer_registry {
602+
let stats = VirtualDiskProducer::new(
603+
block_size,
604+
self.properties.id,
605+
disk_id,
606+
&self.properties.metadata,
607+
);
608+
if let Err(e) = registry.register_producer(stats.clone()) {
609+
slog::error!(
610+
self.log,
611+
"Could not register virtual disk producer, \
612+
metrics will not be produced";
613+
"disk_id" => %disk_id,
614+
"error" => ?e,
615+
);
616+
continue 'devloop;
617+
};
618+
619+
// Set the on-completion callback for the block device, to
620+
// update stats.
621+
let callback = move |op, result, duration| {
622+
stats.on_completion(op, result, duration);
623+
};
624+
block_dev.on_completion(Box::new(callback));
625+
};
579626
}
580627
}
581628
Ok(())
@@ -1041,13 +1088,40 @@ impl<'a> MachineInitializer<'a> {
10411088
Ok(ramfb)
10421089
}
10431090

1044-
pub fn initialize_cpus(&mut self) -> Result<(), Error> {
1091+
pub async fn initialize_cpus(
1092+
&mut self,
1093+
kstat_sampler: &Option<KstatSampler>,
1094+
) -> Result<(), Error> {
10451095
for vcpu in self.machine.vcpus.iter() {
10461096
vcpu.set_default_capabs().unwrap();
10471097

10481098
// The vCPUs behave like devices, so add them to the list as well
10491099
self.devices.insert(format!("vcpu-{}", vcpu.id), vcpu.clone());
10501100
}
1101+
if let Some(sampler) = kstat_sampler.as_ref() {
1102+
track_vcpu_kstats(&self.log, sampler, self.properties).await;
1103+
}
10511104
Ok(())
10521105
}
1106+
1107+
/// Create an object used to sample kstats.
1108+
pub(crate) fn create_kstat_sampler(&self) -> Option<KstatSampler> {
1109+
let Some(registry) = &self.producer_registry else {
1110+
return None;
1111+
};
1112+
let sampler =
1113+
create_kstat_sampler(&self.log, u32::from(self.properties.vcpus))?;
1114+
match registry.register_producer(sampler.clone()) {
1115+
Ok(_) => Some(sampler),
1116+
Err(e) => {
1117+
slog::error!(
1118+
self.log,
1119+
"Failed to register kstat sampler in producer \
1120+
registry, no kstat-based metrics will be produced";
1121+
"error" => ?e,
1122+
);
1123+
None
1124+
}
1125+
}
1126+
}
10531127
}

0 commit comments

Comments
 (0)