pub(crate) static METRIC_GUEST_ERROR: &str = "guest_errors_total";
pub(crate) static METRIC_GUEST_ERROR_LABEL_CODE: &str = "code";
pub(crate) static METRIC_GUEST_CANCELLATION: &str = "guest_cancellations_total";
pub(crate) static METRIC_ERRONEOUS_VCPU_KICKS: &str = "erroneous_vcpu_kicks_total";
#[cfg(feature = "function_call_metrics")]
pub(crate) static METRIC_GUEST_FUNC_DURATION: &str = "guest_call_duration_seconds";
#[cfg(feature = "function_call_metrics")]
pub(crate) static METRIC_HOST_FUNC_DURATION: &str = "host_call_duration_seconds";
pub(crate) fn maybe_time_and_emit_guest_call<T, F: FnOnce() -> T>(
#[allow(unused_variables)] name: &str,
f: F,
) -> T {
cfg_if::cfg_if! {
if #[cfg(feature = "function_call_metrics")] {
use std::time::Instant;
let start = Instant::now();
let result = f();
let duration = start.elapsed();
static LABEL_GUEST_FUNC_NAME: &str = "function_name";
metrics::histogram!(METRIC_GUEST_FUNC_DURATION, LABEL_GUEST_FUNC_NAME => name.to_string()).record(duration);
result
} else {
f()
}
}
}
pub(crate) fn maybe_time_and_emit_host_call<T, F: FnOnce() -> T>(
#[allow(unused_variables)] name: &str,
f: F,
) -> T {
cfg_if::cfg_if! {
if #[cfg(feature = "function_call_metrics")] {
use std::time::Instant;
let start = Instant::now();
let result = f();
let duration = start.elapsed();
static LABEL_HOST_FUNC_NAME: &str = "function_name";
metrics::histogram!(METRIC_HOST_FUNC_DURATION, LABEL_HOST_FUNC_NAME => name.to_string()).record(duration);
result
} else {
f()
}
}
}
#[cfg(test)]
mod tests {
use std::thread;
use std::time::Duration;
use hyperlight_testing::simple_guest_as_string;
use metrics::{Key, with_local_recorder};
use metrics_util::CompositeKey;
use super::*;
use crate::{GuestBinary, UninitializedSandbox};
#[test]
fn test_metrics_are_emitted() {
let recorder = metrics_util::debugging::DebuggingRecorder::new();
let snapshotter = recorder.snapshotter();
let snapshot = with_local_recorder(&recorder, || {
let uninit = UninitializedSandbox::new(
GuestBinary::FilePath(simple_guest_as_string().unwrap()),
None,
)
.unwrap();
let mut multi = uninit.evolve().unwrap();
let interrupt_handle = multi.interrupt_handle();
let thread = thread::spawn(move || {
thread::sleep(Duration::from_secs(1));
assert!(interrupt_handle.kill());
});
multi
.call::<i32>("PrintOutput", "Hello".to_string())
.unwrap();
multi.call::<i32>("Spin", ()).unwrap_err();
thread.join().unwrap();
snapshotter.snapshot()
});
#[expect(clippy::mutable_key_type)]
let snapshot = snapshot.into_hashmap();
cfg_if::cfg_if! {
if #[cfg(feature = "function_call_metrics")] {
use metrics::Label;
let expected_num_metrics = 4;
assert_eq!(snapshot.len(), expected_num_metrics);
let histogram_key = CompositeKey::new(
metrics_util::MetricKind::Histogram,
Key::from_parts(
METRIC_GUEST_FUNC_DURATION,
vec![Label::new("function_name", "PrintOutput")],
),
);
let histogram_value = &snapshot.get(&histogram_key).unwrap().2;
assert!(
matches!(
histogram_value,
metrics_util::debugging::DebugValue::Histogram(histogram) if histogram.len() == 1
),
"Histogram metric does not match expected value"
);
let counter_key = CompositeKey::new(
metrics_util::MetricKind::Counter,
Key::from_name(METRIC_GUEST_CANCELLATION),
);
assert_eq!(
snapshot.get(&counter_key).unwrap().2,
metrics_util::debugging::DebugValue::Counter(1)
);
let histogram_key = CompositeKey::new(
metrics_util::MetricKind::Histogram,
Key::from_parts(
METRIC_GUEST_FUNC_DURATION,
vec![Label::new("function_name", "Spin")],
),
);
let histogram_value = &snapshot.get(&histogram_key).unwrap().2;
assert!(
matches!(
histogram_value,
metrics_util::debugging::DebugValue::Histogram(histogram) if histogram.len() == 1
),
"Histogram metric does not match expected value"
);
} else {
assert_eq!(snapshot.len(), 1);
let counter_key = CompositeKey::new(
metrics_util::MetricKind::Counter,
Key::from_name(METRIC_GUEST_CANCELLATION),
);
assert_eq!(
snapshot.get(&counter_key).unwrap().2,
metrics_util::debugging::DebugValue::Counter(1)
);
}
}
}
}