hyperlight_host/metrics/
mod.rs

1/*
2Copyright 2024 The Hyperlight Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17use std::collections::HashMap;
18use std::sync::Once;
19
20use log::error;
21use once_cell::sync::OnceCell;
22use prometheus::{default_registry, histogram_opts, opts, HistogramOpts, Opts, Registry};
23use strum::{IntoEnumIterator, VariantNames};
24
25use crate::error::HyperlightError::{Error, MetricNotFound};
26use crate::{log_then_return, new_error, Result};
27mod int_gauge_vec;
28/// An Integer Gauge Metric for Hyperlight
29///
30pub use int_gauge_vec::IntGaugeVec;
31mod int_gauge;
32/// An Integer Gauge Metric for Hyperlight
33pub use int_gauge::IntGauge;
34mod int_counter_vec;
35/// An Integer Counter Vec for Hyperlight
36pub use int_counter_vec::IntCounterVec;
37mod int_counter;
38/// An Integer Counter for Hyperlight
39pub use int_counter::IntCounter;
40mod histogram_vec;
41/// A Histogram Vec for Hyperlight
42pub use histogram_vec::HistogramVec;
43mod histogram;
44/// AHistogram for Hyperlight
45pub use histogram::Histogram;
46/// A trait that should be implemented by all enums that represent hyperlight metrics
47pub trait HyperlightMetricEnum<T>:
48    IntoEnumIterator + VariantNames + From<T> + Into<&'static str>
49where
50    &'static str: From<Self>,
51    &'static str: for<'a> From<&'a Self>,
52{
53    /// A function that should return a static reference to a Once that is used to guard the initialization of the metrics hashmap.
54    fn get_init_metrics() -> &'static Once;
55    /// A function that should return a static reference to a OnceCell that is used to store the metrics hashmap.
56    fn get_metrics() -> &'static OnceCell<HashMap<&'static str, HyperlightMetric>>;
57    /// A function that should return a static reference to a slice of HyperlightMetricDefinitions that are used to initialize the metrics hashmap.
58    fn get_metric_definitions() -> &'static [HyperlightMetricDefinition];
59
60    /// Gets a HyperlightMetric from the hashmap using the enum variant name as the key
61    #[inline]
62    fn get_hyperlight_metric(&self) -> Result<&HyperlightMetric> {
63        Self::get_init_metrics().call_once(|| {
64            let result = init_metrics(Self::get_metric_definitions(), Self::get_metrics());
65            if let Err(e) = result {
66                error!("Error initializing metrics : {0:?}", e);
67            }
68        });
69        let key: &'static str = <&Self as Into<&'static str>>::into(self);
70        HyperlightMetric::get_metric_using_key(key, Self::get_hash_map()?)
71    }
72    /// Gets the hashmap using the containing the metrics
73    #[inline]
74    fn get_hash_map() -> Result<&'static HashMap<&'static str, HyperlightMetric>> {
75        Self::get_metrics()
76            .get()
77            .ok_or_else(|| Error("metrics hashmap not initialized".to_string()))
78    }
79}
80/// A trait that should be implemented by all enums that represent hyperlight metrics to
81/// convert the enum into a HyperlightMetric
82pub trait HyperlightMetricOps {
83    /// Converts the enum into a HyperlightMetric
84    fn get_metric(&self) -> Result<&HyperlightMetric>;
85}
86
87/// A trait that should be implemented by all hyperlight metric definitions to convert the metric into a HyperlightMetric
88pub trait GetHyperlightMetric<T> {
89    /// Converts the metric into a HyperlightMetric
90    fn metric(&self) -> Result<&T>;
91}
92
93impl<T: HyperlightMetricEnum<T>> HyperlightMetricOps for T
94where
95    &'static str: From<T>,
96    for<'a> &'static str: From<&'a T>,
97{
98    fn get_metric(&self) -> Result<&HyperlightMetric> {
99        self.get_hyperlight_metric()
100    }
101}
102
103/// Initializes the metrics hashmap using the metric definitions
104#[inline]
105fn init_metrics(
106    metric_definitions: &[HyperlightMetricDefinition],
107    metrics: &OnceCell<HashMap<&'static str, HyperlightMetric>>,
108) -> Result<()> {
109    let mut hash_map: HashMap<&'static str, HyperlightMetric> = HashMap::new();
110    register_metrics(metric_definitions, &mut hash_map)?;
111    // the only failure case is if the metrics hashmap is already set which should not be possible
112    // but if it were to happen we dont care.
113    if let Err(e) = metrics.set(hash_map) {
114        error!("metrics hashmap already set : {0:?}", e);
115    }
116    Ok(())
117}
118//TODO: Remove this when we have uses of all metric types
119#[allow(dead_code)]
120#[derive(Debug)]
121/// The types of Hyperlight metrics that can be created
122pub enum HyperlightMetricType {
123    /// A counter that can only be incremented
124    IntCounter,
125    /// A counter that can only be incremented and has labels
126    IntCounterVec,
127    /// A gauge that can be incremented, decremented, set, added to, and subtracted from
128    IntGauge,
129    /// A gauge that can be incremented, decremented, set, added to, and subtracted from and has labels
130    IntGaugeVec,
131    /// A histogram that can observe values for activities   
132    Histogram,
133    /// A histogram that can observe values for activities and has labels
134    HistogramVec,
135}
136
137/// The definition of a Hyperlight metric
138pub struct HyperlightMetricDefinition {
139    /// The name of the metric
140    pub name: &'static str,
141    /// The help text for the metric
142    pub help: &'static str,
143    /// The type of the metric
144    pub metric_type: HyperlightMetricType,
145    /// The labels for the metric
146    pub labels: &'static [&'static str],
147    /// The buckets for the metric
148    pub buckets: &'static [f64],
149}
150
151fn register_metrics(
152    metric_definitions: &[HyperlightMetricDefinition],
153    hash_map: &mut HashMap<&'static str, HyperlightMetric>,
154) -> Result<()> {
155    for metric_definition in metric_definitions {
156        let metric: HyperlightMetric = match &metric_definition.metric_type {
157            HyperlightMetricType::IntGauge => {
158                IntGauge::new(metric_definition.name, metric_definition.help)?.into()
159            }
160
161            HyperlightMetricType::IntCounterVec => IntCounterVec::new(
162                metric_definition.name,
163                metric_definition.help,
164                metric_definition.labels,
165            )?
166            .into(),
167
168            HyperlightMetricType::IntCounter => {
169                IntCounter::new(metric_definition.name, metric_definition.help)?.into()
170            }
171            HyperlightMetricType::HistogramVec => HistogramVec::new(
172                metric_definition.name,
173                metric_definition.help,
174                metric_definition.labels,
175                metric_definition.buckets.to_vec(),
176            )?
177            .into(),
178            HyperlightMetricType::Histogram => Histogram::new(
179                metric_definition.name,
180                metric_definition.help,
181                metric_definition.buckets.to_vec(),
182            )?
183            .into(),
184            HyperlightMetricType::IntGaugeVec => IntGaugeVec::new(
185                metric_definition.name,
186                metric_definition.help,
187                metric_definition.labels,
188            )?
189            .into(),
190        };
191
192        hash_map.insert(metric_definition.name, metric);
193    }
194    Ok(())
195}
196
197#[derive(Debug)]
198/// An instance of a Hyperlight metric
199pub enum HyperlightMetric {
200    /// A counter that can only be incremented
201    IntCounter(IntCounter),
202    /// A counter that can only be incremented and has labels
203    IntCounterVec(IntCounterVec),
204    /// A gauge that can be incremented, decremented, set, added to, and subtracted from
205    IntGauge(IntGauge),
206    /// A gauge that can be incremented, decremented, set, added to, and subtracted from and has labels
207    IntGaugeVec(IntGaugeVec),
208    /// A histogram that can observe values for activities
209    Histogram(Histogram),
210    /// A histogram that can observe values for activities and has labels
211    HistogramVec(HistogramVec),
212}
213
214impl HyperlightMetric {
215    #[inline]
216    fn get_metric_using_key<'a>(
217        key: &'static str,
218        hash_map: &'a HashMap<&'static str, HyperlightMetric>,
219    ) -> Result<&'a HyperlightMetric> {
220        hash_map.get(key).ok_or_else(|| MetricNotFound(key))
221    }
222}
223
224// The registry used for all metrics, this can be set by the user of the library, if its not set then the default will be used.
225
226static REGISTRY: OnceCell<&Registry> = OnceCell::new();
227
228/// Get the registry to be used for all metrics, this can be set by the user of the library, if its not set then the default registry will be used.
229#[inline]
230pub fn get_metrics_registry() -> &'static Registry {
231    REGISTRY.get_or_init(default_registry)
232}
233/// Set the registry to be used for all metrics, this can be set by the user of the library, if its not set then the default registry will be used.
234/// This function should be called before any other function in this module is called.
235///
236/// The user of can then use the registry to gather metrics from the library.
237pub fn set_metrics_registry(registry: &'static Registry) -> Result<()> {
238    match REGISTRY.get() {
239        Some(_) => {
240            log_then_return!("Registry was already set");
241        }
242        None => {
243            REGISTRY
244                .set(registry)
245                // This should be impossible
246                .map_err(|e| new_error!("Registry already set : {0:?}", e))
247        }
248    }
249}
250
251fn get_metric_opts(name: &str, help: &str) -> Opts {
252    let opts = opts!(name, help);
253    opts.namespace("hyperlight")
254}
255
256fn get_histogram_opts(name: &str, help: &str, buckets: Vec<f64>) -> HistogramOpts {
257    let mut opts = histogram_opts!(name, help);
258    opts = opts.namespace("hyperlight");
259    opts.buckets(buckets)
260}
261
262#[allow(clippy::disallowed_macros)]
263/// Provides functionality to help with testing Hyperlight Metrics
264pub mod tests {
265    use std::collections::HashSet;
266
267    use super::*;
268
269    /// A trait that provides test helper functions for Hyperlight Metrics
270    pub trait HyperlightMetricEnumTest<T>:
271        HyperlightMetricEnum<T> + From<T> + Into<&'static str>
272    where
273        &'static str: From<Self>,
274        &'static str: for<'a> From<&'a Self>,
275    {
276        /// Defines a function that should return the names of all the metric enum variants
277        fn get_enum_variant_names() -> &'static [&'static str];
278
279        /// Provides a function to test that all hyperlight metric definitions in a module have a corresponding enum variant
280        /// Should be called in tests in modules that define hyperlight metrics.
281        #[track_caller]
282        fn enum_has_variant_for_all_metrics() {
283            let metric_definitions = Self::get_metric_definitions().iter();
284            for metric_definition in metric_definitions {
285                let metric_definition_name = metric_definition.name;
286                assert!(
287                    Self::get_enum_variant_names().contains(&metric_definition_name),
288                    "Metric Definition Name {} not found",
289                    metric_definition_name,
290                );
291            }
292        }
293
294        /// Provides a function to test that all hyperlight metric definitions have a unique help text
295        /// and that there are the same number of enum variants as metric definitions
296        /// Should be called in tests in modules that define hyperlight metrics.
297        #[track_caller]
298        fn check_metric_definitions() {
299            let sandbox_metric_definitions = Self::get_metric_definitions();
300            let metric_definitions = sandbox_metric_definitions.iter();
301            let mut help_text = HashSet::new();
302            for metric_definition in metric_definitions {
303                assert!(
304                    help_text.insert(metric_definition.help),
305                    "duplicate metric help definition for {}",
306                    metric_definition.name
307                );
308            }
309            assert_eq!(
310                Self::get_enum_variant_names().len(),
311                sandbox_metric_definitions.len()
312            );
313        }
314
315        /// Gets a named int gauge metric
316        fn get_intguage_metric(name: &str) -> Result<&IntGauge> {
317            Self::get_metrics()
318                .get()
319                .ok_or_else(|| new_error!("metrics hashmap not initialized"))?
320                .get(name)
321                .ok_or_else(|| new_error!("metric not found : {0:?}", name))?
322                .try_into()
323        }
324
325        /// Gets a named int counter vec metric
326        fn get_intcountervec_metric(name: &str) -> Result<&IntCounterVec> {
327            Self::get_metrics()
328                .get()
329                .ok_or_else(|| new_error!("metrics hashmap not initialized"))?
330                .get(name)
331                .ok_or_else(|| new_error!("metric not found : {0:?}", name))?
332                .try_into()
333        }
334
335        /// Gets a named int counter metric
336        fn get_intcounter_metric(name: &str) -> Result<&IntCounter> {
337            Self::get_metrics()
338                .get()
339                .ok_or_else(|| new_error!("metrics hashmap not initialized"))?
340                .get(name)
341                .ok_or_else(|| new_error!("metric not found : {0:?}", name))?
342                .try_into()
343        }
344
345        /// Gets a named histogram vec metric
346        fn get_histogramvec_metric(name: &str) -> Result<&HistogramVec> {
347            Self::get_metrics()
348                .get()
349                .ok_or_else(|| new_error!("metrics hashmap not initialized"))?
350                .get(name)
351                .ok_or_else(|| new_error!("metric not found : {0:?}", name))?
352                .try_into()
353        }
354    }
355}