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
24#[cfg(feature = "function_call_metrics")]
26pub(crate) static METRIC_GUEST_FUNC_DURATION: &str = "guest_call_duration_seconds";
27
28#[cfg(feature = "function_call_metrics")]
30pub(crate) static METRIC_HOST_FUNC_DURATION: &str = "host_call_duration_seconds";
31
32pub(crate) fn maybe_time_and_emit_guest_call<T, F: FnOnce() -> T>(
39 #[allow(unused_variables)] name: &str,
40 f: F,
41) -> T {
42 cfg_if::cfg_if! {
43 if #[cfg(feature = "function_call_metrics")] {
44 use std::time::Instant;
45
46 let start = Instant::now();
47 let result = f();
48 let duration = start.elapsed();
49
50 static LABEL_GUEST_FUNC_NAME: &str = "function_name";
51 metrics::histogram!(METRIC_GUEST_FUNC_DURATION, LABEL_GUEST_FUNC_NAME => name.to_string()).record(duration);
52 result
53 } else {
54 f()
55 }
56 }
57}
58
59pub(crate) fn maybe_time_and_emit_host_call<T, F: FnOnce() -> T>(
66 #[allow(unused_variables)] name: &str,
67 f: F,
68) -> T {
69 cfg_if::cfg_if! {
70 if #[cfg(feature = "function_call_metrics")] {
71 use std::time::Instant;
72
73 let start = Instant::now();
74 let result = f();
75 let duration = start.elapsed();
76
77 static LABEL_HOST_FUNC_NAME: &str = "function_name";
78 metrics::histogram!(METRIC_HOST_FUNC_DURATION, LABEL_HOST_FUNC_NAME => name.to_string()).record(duration);
79 result
80 } else {
81 f()
82 }
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use std::thread;
89 use std::time::Duration;
90
91 use hyperlight_testing::simple_guest_as_string;
92 use metrics::{Key, with_local_recorder};
93 use metrics_util::CompositeKey;
94
95 use super::*;
96 use crate::{GuestBinary, UninitializedSandbox};
97
98 #[test]
99 fn test_metrics_are_emitted() {
100 let recorder = metrics_util::debugging::DebuggingRecorder::new();
101 let snapshotter = recorder.snapshotter();
102 let snapshot = with_local_recorder(&recorder, || {
103 let uninit = UninitializedSandbox::new(
104 GuestBinary::FilePath(simple_guest_as_string().unwrap()),
105 None,
106 )
107 .unwrap();
108
109 let mut multi = uninit.evolve().unwrap();
110 let interrupt_handle = multi.interrupt_handle();
111
112 let thread = thread::spawn(move || {
114 thread::sleep(Duration::from_secs(1));
115 assert!(interrupt_handle.kill());
116 });
117
118 multi
119 .call::<i32>("PrintOutput", "Hello".to_string())
120 .unwrap();
121
122 multi.call::<i32>("Spin", ()).unwrap_err();
123 thread.join().unwrap();
124
125 snapshotter.snapshot()
126 });
127
128 #[expect(clippy::mutable_key_type)]
130 let snapshot = snapshot.into_hashmap();
131
132 cfg_if::cfg_if! {
133 if #[cfg(feature = "function_call_metrics")] {
134 use metrics::Label;
135
136 let expected_num_metrics = if cfg!(all(feature = "seccomp", target_os = "linux")) {
137 3 } else {
139 4
140 };
141
142 assert_eq!(snapshot.len(), expected_num_metrics);
144
145 let histogram_key = CompositeKey::new(
147 metrics_util::MetricKind::Histogram,
148 Key::from_parts(
149 METRIC_GUEST_FUNC_DURATION,
150 vec![Label::new("function_name", "PrintOutput")],
151 ),
152 );
153 let histogram_value = &snapshot.get(&histogram_key).unwrap().2;
154 assert!(
155 matches!(
156 histogram_value,
157 metrics_util::debugging::DebugValue::Histogram(histogram) if histogram.len() == 1
158 ),
159 "Histogram metric does not match expected value"
160 );
161
162 let counter_key = CompositeKey::new(
164 metrics_util::MetricKind::Counter,
165 Key::from_name(METRIC_GUEST_CANCELLATION),
166 );
167 assert_eq!(
168 snapshot.get(&counter_key).unwrap().2,
169 metrics_util::debugging::DebugValue::Counter(1)
170 );
171
172 let histogram_key = CompositeKey::new(
174 metrics_util::MetricKind::Histogram,
175 Key::from_parts(
176 METRIC_GUEST_FUNC_DURATION,
177 vec![Label::new("function_name", "Spin")],
178 ),
179 );
180 let histogram_value = &snapshot.get(&histogram_key).unwrap().2;
181 assert!(
182 matches!(
183 histogram_value,
184 metrics_util::debugging::DebugValue::Histogram(histogram) if histogram.len() == 1
185 ),
186 "Histogram metric does not match expected value"
187 );
188
189 if !cfg!(all(feature = "seccomp", target_os = "linux")) {
190 let histogram_key = CompositeKey::new(
192 metrics_util::MetricKind::Histogram,
193 Key::from_parts(
194 METRIC_HOST_FUNC_DURATION,
195 vec![Label::new("function_name", "HostPrint")],
196 ),
197 );
198 let histogram_value = &snapshot.get(&histogram_key).unwrap().2;
199 assert!(
200 matches!(
201 histogram_value,
202 metrics_util::debugging::DebugValue::Histogram(histogram) if histogram.len() == 1
203 ),
204 "Histogram metric does not match expected value"
205 );
206 }
207 } else {
208 assert_eq!(snapshot.len(), 1);
210
211 let counter_key = CompositeKey::new(
212 metrics_util::MetricKind::Counter,
213 Key::from_name(METRIC_GUEST_CANCELLATION),
214 );
215 assert_eq!(
216 snapshot.get(&counter_key).unwrap().2,
217 metrics_util::debugging::DebugValue::Counter(1)
218 );
219 }
220 }
221 }
222}