Skip to content

Commit 04d59d4

Browse files
committed
Describe metric with unit and description
Signed-off-by: Ludvig Liljenberg <[email protected]>
1 parent 2936272 commit 04d59d4

File tree

4 files changed

+217
-31
lines changed

4 files changed

+217
-31
lines changed

src/hyperlight_host/src/func/guest_err.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode;
1818

1919
use crate::error::HyperlightError::{GuestError, OutBHandlingError, StackOverflow};
2020
use crate::mem::shared_mem::HostSharedMemory;
21-
use crate::metrics::{CounterMetric, Metric};
21+
use crate::metrics::{CounterMetric, EmittableMetric};
2222
use crate::sandbox::mem_mgr::MemMgrWrapper;
2323
use crate::{log_then_return, Result};
2424
/// Check for a guest error and return an `Err` if one was found,

src/hyperlight_host/src/hypervisor/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use tracing::{instrument, Span};
1919

2020
use crate::error::HyperlightError::ExecutionCanceledByHost;
2121
use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags};
22-
use crate::metrics::{CounterMetric, Metric};
22+
use crate::metrics::{CounterMetric, EmittableMetric};
2323
use crate::{log_then_return, new_error, HyperlightError, Result};
2424

2525
/// Util for handling x87 fpu state

src/hyperlight_host/src/metrics/metrics_macro.rs

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,33 @@
1+
/// Crate internal trait for defining metrics.
12
pub(crate) trait NamedMetric {
2-
/// Returns the name of the metric as a static string
3+
/// The name of the metric.
34
fn name(&self) -> &'static str;
5+
/// The description of the metric.
6+
fn description(&self) -> &'static str;
7+
/// The unit of the metric.
8+
fn unit(&self) -> metrics::Unit;
49
}
510

611
/// A macro to define metrics with variants and optional fields.
712
#[macro_export]
813
macro_rules! define_metrics {
914
(
10-
$( $metric_type:ident $( < $( $gen:tt ),+ > )? {
11-
$(
12-
$( #[cfg($feature:meta)] )?
13-
$variant:ident $( { $($field_name:ident : $field_ty:ty),* $(,)? } )? => $name:expr
14-
),* $(,)?
15-
} )*
15+
$(
16+
$metric_type:ident $( < $( $gen:tt ),+ > )? {
17+
$(
18+
$( #[cfg($feature:meta)] )?
19+
$variant:ident $( { $($field_name:ident : $field_ty:ty),* $(,)? } )? => {
20+
name: $name:expr,
21+
description: $description:expr,
22+
unit: $unit:expr $(,)?
23+
}
24+
),* $(,)?
25+
}
26+
)*
1627
) => {
1728
$(
1829
#[derive(Debug, Clone)]
30+
#[allow(dead_code)]
1931
pub(crate) enum $metric_type $( < $( $gen ),+ > )? {
2032
$(
2133
$( #[cfg($feature)] )?
@@ -32,6 +44,24 @@ macro_rules! define_metrics {
3244
)*
3345
}
3446
}
47+
48+
fn description(&self) -> &'static str {
49+
match self {
50+
$(
51+
$( #[cfg($feature)] )?
52+
Self::$variant { .. } => $description,
53+
)*
54+
}
55+
}
56+
57+
fn unit(&self) -> metrics::Unit {
58+
match self {
59+
$(
60+
$( #[cfg($feature)] )?
61+
Self::$variant { .. } => $unit,
62+
)*
63+
}
64+
}
3565
}
3666
)*
3767
};

src/hyperlight_host/src/metrics/mod.rs

Lines changed: 178 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1+
use std::sync::Once;
12
use std::time::Duration;
2-
#[cfg(feature = "function_call_metrics")]
3-
use std::time::Instant;
43

54
use metrics_macro::NamedMetric;
65

@@ -10,33 +9,58 @@ mod metrics_macro;
109
// These defines all types of metrics in this crate
1110
define_metrics! {
1211
CounterMetric {
13-
GuestErrors { code: u64} => "guest_errors_total",
14-
GuestCancellations => "guest_cancellations_total",
12+
GuestErrors { code: u64 } => {
13+
name: "guest_errors_total",
14+
description: "Number of errors encountered by guests",
15+
unit: metrics::Unit::Count,
16+
},
17+
GuestCancellations => {
18+
name: "guest_cancellations_total",
19+
description: "Number of times guest execution was cancelled due to timeout",
20+
unit: metrics::Unit::Count,
21+
},
1522
}
1623
HistogramMetric {
17-
GuestCallDuration { name: String, duration: Duration } => "guest_call_duration_seconds",
18-
HostCallDuration { name: String, duration: Duration} => "host_call_duration_seconds",
24+
GuestCallDuration { name: String, duration: Duration } => {
25+
name: "guest_call_duration_seconds",
26+
description: "Duration of guest function calls",
27+
unit: metrics::Unit::Seconds,
28+
},
29+
HostCallDuration { name: String, duration: Duration} => {
30+
name: "host_call_duration_seconds",
31+
description: "Duration of host function calls",
32+
unit: metrics::Unit::Seconds,
33+
},
1934
}
2035
}
2136

2237
impl CounterMetric {
2338
/// Create a new guest error metric, ready to be emitted
2439
#[must_use]
25-
pub(crate) fn guest_error(code: u64) -> CounterMetric {
40+
pub(crate) fn guest_error(code: u64) -> Self {
2641
CounterMetric::GuestErrors { code }
2742
}
2843
/// Create a new guest cancellation metric, ready to be emitted
2944
#[must_use]
30-
pub(crate) fn guest_cancellation() -> CounterMetric {
45+
pub(crate) fn guest_cancellation() -> Self {
3146
CounterMetric::GuestCancellations
3247
}
3348
}
3449

3550
impl HistogramMetric {
36-
/// Time a guest call and emit its duration as a metric
37-
pub(crate) fn time_and_emit_guest_call<T, F: FnOnce() -> T>(name: String, f: F) -> T {
51+
/// Measures the time to execute the given closure, and then emits the duration
52+
/// as a guest call metric.
53+
///
54+
/// Note: If the `function_call_metrics` feature is not enabled, this function
55+
/// will simply execute the closure without measuring time or emitting metrics.
56+
pub(crate) fn time_and_emit_guest_call<T, F: FnOnce() -> T>(
57+
#[allow(unused_variables)] name: String,
58+
f: F,
59+
) -> T {
3860
cfg_if::cfg_if! {
3961
if #[cfg(feature = "function_call_metrics")] {
62+
use std::time::Instant;
63+
4064
let start = Instant::now();
4165
let result = f();
4266
let duration = start.elapsed();
@@ -48,10 +72,19 @@ impl HistogramMetric {
4872
}
4973
}
5074

51-
/// Time a host call and emit its duration as a metric
52-
pub(crate) fn time_and_emit_host_call<T, F: FnOnce() -> T>(name: String, f: F) -> T {
75+
/// Measures the time to execute the given closure, and then emits the duration
76+
/// as a host call metric.
77+
///
78+
/// Note: If the `function_call_metrics` feature is not enabled, this function
79+
/// will simply execute the closure without measuring time or emitting metrics.
80+
pub(crate) fn time_and_emit_host_call<T, F: FnOnce() -> T>(
81+
#[allow(unused_variables)] name: String,
82+
f: F,
83+
) -> T {
5384
cfg_if::cfg_if! {
5485
if #[cfg(feature = "function_call_metrics")] {
86+
use std::time::Instant;
87+
5588
let start = Instant::now();
5689
let result = f();
5790
let duration = start.elapsed();
@@ -78,36 +111,68 @@ impl HistogramMetric {
78111
}
79112

80113
/// A metric which can be emitted to the underlying metrics system
81-
pub(crate) trait Metric {
82-
/// Emits the metric to the underlying metrics system
114+
pub(crate) trait EmittableMetric {
115+
/// Emits the metric to the underlying metrics system.
116+
/// The first time this is called for a given metric variant, it will
117+
/// also describe the metric to the underlying metrics system.
83118
fn emit(self);
84119
}
85120

86-
impl Metric for CounterMetric {
121+
impl EmittableMetric for CounterMetric {
87122
/// Increases the counter represented by `self` by 1
88123
fn emit(self) {
89124
let name = self.name();
125+
let unit = self.unit();
126+
let description = self.description();
127+
90128
match self {
91129
CounterMetric::GuestErrors { code } => {
92-
// label keys
93-
static LABEL_ERROR_CODE: &str = "code";
130+
// Describe each metric variant only once
131+
static DESCRIBE: Once = Once::new();
132+
DESCRIBE.call_once(|| {
133+
metrics::describe_counter!(name, unit, description);
134+
});
94135

136+
static LABEL_ERROR_CODE: &str = "code";
95137
metrics::counter!(name, LABEL_ERROR_CODE => code.to_string()).increment(1);
96138
}
97139
CounterMetric::GuestCancellations => {
140+
// Describe each metric variant only once
141+
static DESCRIBE: Once = Once::new();
142+
DESCRIBE.call_once(|| {
143+
metrics::describe_counter!(name, unit, description);
144+
});
145+
98146
metrics::counter!(name).increment(1);
99147
}
100148
}
101149
}
102150
}
103151

104-
impl Metric for HistogramMetric {
152+
impl EmittableMetric for HistogramMetric {
105153
fn emit(self) {
106154
let metric_name = self.name();
155+
let unit = self.unit();
156+
let description = self.description();
157+
158+
static LABEL_FUNCTION_NAME: &str = "function_name";
159+
107160
match self {
108-
HistogramMetric::GuestCallDuration { name, duration }
109-
| HistogramMetric::HostCallDuration { name, duration } => {
110-
static LABEL_FUNCTION_NAME: &str = "function_name";
161+
HistogramMetric::GuestCallDuration { name, duration } => {
162+
// Describe each metric variant only once
163+
static DESCRIBE: Once = Once::new();
164+
DESCRIBE.call_once(|| {
165+
metrics::describe_histogram!(metric_name, unit, description);
166+
});
167+
168+
metrics::histogram!(metric_name, LABEL_FUNCTION_NAME => name).record(duration);
169+
}
170+
HistogramMetric::HostCallDuration { name, duration } => {
171+
// Describe each metric variant only once
172+
static DESCRIBE: Once = Once::new();
173+
DESCRIBE.call_once(|| {
174+
metrics::describe_histogram!(metric_name, unit, description);
175+
});
111176

112177
metrics::histogram!(metric_name, LABEL_FUNCTION_NAME => name).record(duration);
113178
}
@@ -117,7 +182,9 @@ impl Metric for HistogramMetric {
117182

118183
#[cfg(test)]
119184
mod tests {
120-
use metrics::{Key, Label};
185+
use std::sync::atomic::AtomicU64;
186+
187+
use metrics::{Counter, Gauge, Histogram, Key, Label};
121188
use metrics_util::CompositeKey;
122189

123190
use super::*;
@@ -207,4 +274,93 @@ mod tests {
207274
"Histogram metric does not match expected value"
208275
);
209276
}
277+
278+
/// Makes sure that the description function is called only once for each metric variant.
279+
/// This helps performance
280+
#[test]
281+
fn test_description_called_once() {
282+
struct DescriptionCounterRecorder {
283+
num_descriptions: AtomicU64,
284+
}
285+
286+
impl metrics::Recorder for DescriptionCounterRecorder {
287+
fn describe_counter(
288+
&self,
289+
_key: metrics::KeyName,
290+
_unit: Option<metrics::Unit>,
291+
_description: metrics::SharedString,
292+
) {
293+
self.num_descriptions
294+
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
295+
}
296+
297+
fn describe_gauge(
298+
&self,
299+
_key: metrics::KeyName,
300+
_unit: Option<metrics::Unit>,
301+
_description: metrics::SharedString,
302+
) {
303+
self.num_descriptions
304+
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
305+
}
306+
307+
fn describe_histogram(
308+
&self,
309+
_key: metrics::KeyName,
310+
_unit: Option<metrics::Unit>,
311+
_description: metrics::SharedString,
312+
) {
313+
self.num_descriptions
314+
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
315+
}
316+
317+
fn register_counter(
318+
&self,
319+
_key: &Key,
320+
_metadata: &metrics::Metadata<'_>,
321+
) -> metrics::Counter {
322+
Counter::noop()
323+
}
324+
325+
fn register_gauge(
326+
&self,
327+
_key: &Key,
328+
_metadata: &metrics::Metadata<'_>,
329+
) -> metrics::Gauge {
330+
Gauge::noop()
331+
}
332+
333+
fn register_histogram(
334+
&self,
335+
_key: &Key,
336+
_metadata: &metrics::Metadata<'_>,
337+
) -> metrics::Histogram {
338+
Histogram::noop()
339+
}
340+
}
341+
342+
let recorder = DescriptionCounterRecorder {
343+
num_descriptions: AtomicU64::new(0),
344+
};
345+
346+
metrics::with_local_recorder(&recorder, || {
347+
CounterMetric::guest_error(404).emit();
348+
CounterMetric::guest_error(500).emit();
349+
CounterMetric::GuestCancellations.emit();
350+
CounterMetric::GuestCancellations.emit();
351+
HistogramMetric::guest_call("GuestFunc1".to_string(), Duration::from_secs(1)).emit();
352+
HistogramMetric::guest_call("GuestFunc2".to_string(), Duration::from_secs(2)).emit();
353+
HistogramMetric::host_call("HostFunc1".to_string(), Duration::from_secs(3)).emit();
354+
HistogramMetric::host_call("HostFunc2".to_string(), Duration::from_secs(4)).emit();
355+
});
356+
357+
// Despite 8 emitted metrics above, we expect only 4 descriptions to be recorded
358+
assert_eq!(
359+
recorder
360+
.num_descriptions
361+
.load(std::sync::atomic::Ordering::Relaxed),
362+
4,
363+
"Expected each metric variant to be described only once"
364+
);
365+
}
210366
}

0 commit comments

Comments
 (0)