hyperlight_host/metrics/
mod.rs1pub(crate) static METRIC_GUEST_ERROR: &str = "guest_errors_total";
19pub(crate) static METRIC_GUEST_ERROR_LABEL_CODE: &str = "code";
20
21pub(crate) static METRIC_GUEST_CANCELLATION: &str = "guest_cancellations_total";
23
24pub(crate) static METRIC_ERRONEOUS_VCPU_KICKS: &str = "erroneous_vcpu_kicks_total";
29
30#[cfg(feature = "function_call_metrics")]
32pub(crate) static METRIC_GUEST_FUNC_DURATION: &str = "guest_call_duration_seconds";
33
34#[cfg(feature = "function_call_metrics")]
36pub(crate) static METRIC_HOST_FUNC_DURATION: &str = "host_call_duration_seconds";
37
38pub(crate) fn maybe_time_and_emit_guest_call<T, F: FnOnce() -> T>(
45 #[allow(unused_variables)] name: &str,
46 f: F,
47) -> T {
48 cfg_if::cfg_if! {
49 if #[cfg(feature = "function_call_metrics")] {
50 use std::time::Instant;
51
52 let start = Instant::now();
53 let result = f();
54 let duration = start.elapsed();
55
56 static LABEL_GUEST_FUNC_NAME: &str = "function_name";
57 metrics::histogram!(METRIC_GUEST_FUNC_DURATION, LABEL_GUEST_FUNC_NAME => name.to_string()).record(duration);
58 result
59 } else {
60 f()
61 }
62 }
63}
64
65pub(crate) fn maybe_time_and_emit_host_call<T, F: FnOnce() -> T>(
72 #[allow(unused_variables)] name: &str,
73 f: F,
74) -> T {
75 cfg_if::cfg_if! {
76 if #[cfg(feature = "function_call_metrics")] {
77 use std::time::Instant;
78
79 let start = Instant::now();
80 let result = f();
81 let duration = start.elapsed();
82
83 static LABEL_HOST_FUNC_NAME: &str = "function_name";
84 metrics::histogram!(METRIC_HOST_FUNC_DURATION, LABEL_HOST_FUNC_NAME => name.to_string()).record(duration);
85 result
86 } else {
87 f()
88 }
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use std::thread;
95 use std::time::Duration;
96
97 use hyperlight_testing::simple_guest_as_string;
98 use metrics::{Key, with_local_recorder};
99 use metrics_util::CompositeKey;
100
101 use super::*;
102 use crate::{GuestBinary, UninitializedSandbox};
103
104 #[test]
105 fn test_metrics_are_emitted() {
106 let recorder = metrics_util::debugging::DebuggingRecorder::new();
107 let snapshotter = recorder.snapshotter();
108 let snapshot = with_local_recorder(&recorder, || {
109 let uninit = UninitializedSandbox::new(
110 GuestBinary::FilePath(simple_guest_as_string().unwrap()),
111 None,
112 )
113 .unwrap();
114
115 let mut multi = uninit.evolve().unwrap();
116 let interrupt_handle = multi.interrupt_handle();
117
118 let thread = thread::spawn(move || {
120 thread::sleep(Duration::from_secs(1));
121 assert!(interrupt_handle.kill());
122 });
123
124 multi
125 .call::<i32>("PrintOutput", "Hello".to_string())
126 .unwrap();
127
128 multi.call::<i32>("Spin", ()).unwrap_err();
129 thread.join().unwrap();
130
131 snapshotter.snapshot()
132 });
133
134 #[expect(clippy::mutable_key_type)]
136 let snapshot = snapshot.into_hashmap();
137
138 cfg_if::cfg_if! {
139 if #[cfg(feature = "function_call_metrics")] {
140 use metrics::Label;
141
142 let expected_num_metrics = 4;
143
144 assert_eq!(snapshot.len(), expected_num_metrics);
146
147 let histogram_key = CompositeKey::new(
149 metrics_util::MetricKind::Histogram,
150 Key::from_parts(
151 METRIC_GUEST_FUNC_DURATION,
152 vec![Label::new("function_name", "PrintOutput")],
153 ),
154 );
155 let histogram_value = &snapshot.get(&histogram_key).unwrap().2;
156 assert!(
157 matches!(
158 histogram_value,
159 metrics_util::debugging::DebugValue::Histogram(histogram) if histogram.len() == 1
160 ),
161 "Histogram metric does not match expected value"
162 );
163
164 let counter_key = CompositeKey::new(
166 metrics_util::MetricKind::Counter,
167 Key::from_name(METRIC_GUEST_CANCELLATION),
168 );
169 assert_eq!(
170 snapshot.get(&counter_key).unwrap().2,
171 metrics_util::debugging::DebugValue::Counter(1)
172 );
173
174 let histogram_key = CompositeKey::new(
176 metrics_util::MetricKind::Histogram,
177 Key::from_parts(
178 METRIC_GUEST_FUNC_DURATION,
179 vec![Label::new("function_name", "Spin")],
180 ),
181 );
182 let histogram_value = &snapshot.get(&histogram_key).unwrap().2;
183 assert!(
184 matches!(
185 histogram_value,
186 metrics_util::debugging::DebugValue::Histogram(histogram) if histogram.len() == 1
187 ),
188 "Histogram metric does not match expected value"
189 );
190 } else {
191 assert_eq!(snapshot.len(), 1);
193
194 let counter_key = CompositeKey::new(
195 metrics_util::MetricKind::Counter,
196 Key::from_name(METRIC_GUEST_CANCELLATION),
197 );
198 assert_eq!(
199 snapshot.get(&counter_key).unwrap().2,
200 metrics_util::debugging::DebugValue::Counter(1)
201 );
202 }
203 }
204 }
205}