Skip to content

Commit 3e296a6

Browse files
authored
Publish instance vCPU stats to oximeter (#659)
* Publish instance vCPU stats to oximeter - Add more metadata to instance and HTTP API, for tracking silo / project IDs for an instance. - Add the `VirtualMachine` kstat target for tracking individual vCPU usage by microstate. - Use `VirtualMachine` in place of the bare instance UUID for tracking other statistics too. - Use `KstatSampler` to pull vCPU kernel statistics to track vCPU usage - A few minor tweaks to existing metrics - Update OpenAPI documents * Will it blend on linux? * Review feedback - Merge various use statements - Docstring improvements - Ref issue tracking PVPANIC statistic organization - Add oximeter state to `ServiceProviders`, storing registration task handle. Abort the registration task in `stop()` method, like the others. * Review feedback - Move lock around oximeter state up one layer - Add mechanism for the `ServiceProviders::stop()` method to notify the metric registration task to bail if needed. * Correct const for number of tracked vCPU microstates * More review feedback - Added test to catch changes in kstate->oximeter vCPU microstate mapping - Make oximeter state names consts - Define the number of expected states explicitly as the len of an array of the state names. * Use typed UUID wrapper
1 parent 4ccbb04 commit 3e296a6

File tree

19 files changed

+1220
-177
lines changed

19 files changed

+1220
-177
lines changed

Cargo.lock

+45-13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ internal-dns = { git = "https://github.com/oxidecomputer/omicron", branch = "mai
7373
nexus-client = { git = "https://github.com/oxidecomputer/omicron", branch = "main" }
7474
omicron-common = { git = "https://github.com/oxidecomputer/omicron", branch = "main" }
7575
omicron-zone-package = "0.9.0"
76+
oximeter-instruments = { git = "https://github.com/oxidecomputer/omicron", branch = "main", default-features = false, features = ["kstat"] }
7677
oximeter-producer = { git = "https://github.com/oxidecomputer/omicron", branch = "main" }
7778
oximeter = { git = "https://github.com/oxidecomputer/omicron", branch = "main" }
7879

@@ -115,9 +116,11 @@ http = "0.2.9"
115116
hyper = "0.14"
116117
indicatif = "0.17.3"
117118
inventory = "0.3.0"
119+
kstat-rs = "0.2.3"
118120
lazy_static = "1.4"
119121
libc = "0.2"
120122
mockall = "0.11"
123+
newtype-uuid = { version = "1.0.1", features = [ "v4" ] }
121124
num_enum = "0.5.11"
122125
owo-colors = "4"
123126
pin-project-lite = "0.2.13"

bin/mock-server/src/lib/api_types.rs

+10
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@ progenitor::generate_api!(
99
spec = "../../openapi/propolis-server.json",
1010
derives = [schemars::JsonSchema],
1111
patch = {
12+
InstanceMetadata = {
13+
derives = [
14+
Clone,
15+
schemars::JsonSchema,
16+
Serialize,
17+
Deserialize,
18+
Eq,
19+
PartialEq,
20+
]
21+
},
1222
InstanceProperties = {
1323
derives = [
1424
Clone,

bin/propolis-cli/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ anyhow.workspace = true
99
clap = { workspace = true, features = ["derive"] }
1010
futures.workspace = true
1111
libc.workspace = true
12+
newtype-uuid.workspace = true
1213
propolis-client.workspace = true
1314
slog.workspace = true
1415
slog-async.workspace = true

bin/propolis-cli/src/main.rs

+40
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ use std::{
1414
use anyhow::{anyhow, Context};
1515
use clap::{Parser, Subcommand};
1616
use futures::{future, SinkExt};
17+
use newtype_uuid::{GenericUuid, TypedUuid, TypedUuidKind, TypedUuidTag};
18+
use propolis_client::types::InstanceMetadata;
1719
use slog::{o, Drain, Level, Logger};
1820
use tokio::io::{AsyncReadExt, AsyncWriteExt};
1921
use tokio_tungstenite::tungstenite::{
@@ -79,6 +81,14 @@ enum Command {
7981
// cloud_init ISO file
8082
#[clap(long, action)]
8183
cloud_init: Option<PathBuf>,
84+
85+
/// A UUID to use for the instance's silo, attached to instance metrics.
86+
#[clap(long)]
87+
silo_id: Option<TypedUuid<SiloKind>>,
88+
89+
/// A UUID to use for the instance's project, attached to instance metrics.
90+
#[clap(long)]
91+
project_id: Option<TypedUuid<ProjectKind>>,
8292
},
8393

8494
/// Get the properties of a propolis instance
@@ -178,6 +188,26 @@ fn create_logger(opt: &Opt) -> Logger {
178188
Logger::root(drain, o!())
179189
}
180190

191+
// Implement typed UUID wrappers for the project / silo IDs, to avoid conflating
192+
// them.
193+
enum ProjectKind {}
194+
195+
impl TypedUuidKind for ProjectKind {
196+
fn tag() -> TypedUuidTag {
197+
const TAG: TypedUuidTag = TypedUuidTag::new("project");
198+
TAG
199+
}
200+
}
201+
202+
enum SiloKind {}
203+
204+
impl TypedUuidKind for SiloKind {
205+
fn tag() -> TypedUuidTag {
206+
const TAG: TypedUuidTag = TypedUuidTag::new("silo");
207+
TAG
208+
}
209+
}
210+
181211
#[allow(clippy::too_many_arguments)]
182212
async fn new_instance(
183213
client: &Client,
@@ -187,11 +217,17 @@ async fn new_instance(
187217
memory: u64,
188218
disks: Vec<DiskRequest>,
189219
cloud_init_bytes: Option<String>,
220+
silo_id: TypedUuid<SiloKind>,
221+
project_id: TypedUuid<ProjectKind>,
190222
) -> anyhow::Result<()> {
191223
let properties = InstanceProperties {
192224
id,
193225
name,
194226
description: "propolis-cli generated instance".to_string(),
227+
metadata: InstanceMetadata {
228+
silo_id: silo_id.into_untyped_uuid(),
229+
project_id: project_id.into_untyped_uuid(),
230+
},
195231
// TODO: Use real UUID
196232
image_id: Uuid::default(),
197233
// TODO: Use real UUID
@@ -620,6 +656,8 @@ async fn main() -> anyhow::Result<()> {
620656
memory,
621657
crucible_disks,
622658
cloud_init,
659+
silo_id,
660+
project_id,
623661
} => {
624662
let disks = if let Some(crucible_disks) = crucible_disks {
625663
parse_json_file(&crucible_disks)?
@@ -642,6 +680,8 @@ async fn main() -> anyhow::Result<()> {
642680
memory,
643681
disks,
644682
cloud_init_bytes,
683+
silo_id.unwrap_or_else(TypedUuid::new_v4),
684+
project_id.unwrap_or_else(TypedUuid::new_v4),
645685
)
646686
.await?
647687
}

bin/propolis-server/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,11 @@ futures.workspace = true
3333
http.workspace = true
3434
hyper.workspace = true
3535
internal-dns.workspace = true
36+
kstat-rs.workspace = true
3637
lazy_static.workspace = true
3738
nexus-client.workspace = true
3839
omicron-common.workspace = true
40+
oximeter-instruments.workspace = true
3941
oximeter-producer.workspace = true
4042
oximeter.workspace = true
4143
ron.workspace = true

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

+6-3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
1212

1313
use crate::serial::Serial;
1414
use crate::server::{BlockBackendMap, CrucibleBackendMap, DeviceMap};
15+
use crate::stats::virtual_machine::VirtualMachine;
1516
use anyhow::{Context, Result};
1617
use crucible_client_types::VolumeConstructionRequest;
1718
pub use nexus_client::Client as NexusClient;
@@ -334,7 +335,7 @@ impl<'a> MachineInitializer<'a> {
334335

335336
pub fn initialize_qemu_pvpanic(
336337
&mut self,
337-
uuid: uuid::Uuid,
338+
virtual_machine: VirtualMachine,
338339
) -> Result<(), anyhow::Error> {
339340
if let Some(ref spec) = self.spec.devices.qemu_pvpanic {
340341
if spec.enable_isa {
@@ -346,8 +347,10 @@ impl<'a> MachineInitializer<'a> {
346347
.insert(pvpanic.type_name().into(), pvpanic.clone());
347348

348349
if let Some(ref registry) = self.producer_registry {
349-
let producer =
350-
crate::stats::PvpanicProducer::new(uuid, pvpanic);
350+
let producer = crate::stats::PvpanicProducer::new(
351+
virtual_machine,
352+
pvpanic,
353+
);
351354
registry.register_producer(producer).context(
352355
"failed to register PVPANIC Oximeter producer",
353356
)?;

0 commit comments

Comments
 (0)