use std::collections::HashMap;
use std::sync::Once;
use log::error;
use once_cell::sync::OnceCell;
use prometheus::{default_registry, histogram_opts, opts, HistogramOpts, Opts, Registry};
use strum::{IntoEnumIterator, VariantNames};
use crate::error::HyperlightError::{Error, MetricNotFound};
use crate::{log_then_return, new_error, Result};
mod int_gauge_vec;
pub use int_gauge_vec::IntGaugeVec;
mod int_gauge;
pub use int_gauge::IntGauge;
mod int_counter_vec;
pub use int_counter_vec::IntCounterVec;
mod int_counter;
pub use int_counter::IntCounter;
mod histogram_vec;
pub use histogram_vec::HistogramVec;
mod histogram;
pub use histogram::Histogram;
pub trait HyperlightMetricEnum<T>:
IntoEnumIterator + VariantNames + From<T> + Into<&'static str>
where
&'static str: From<Self>,
&'static str: for<'a> From<&'a Self>,
{
fn get_init_metrics() -> &'static Once;
fn get_metrics() -> &'static OnceCell<HashMap<&'static str, HyperlightMetric>>;
fn get_metric_definitions() -> &'static [HyperlightMetricDefinition];
#[inline]
fn get_hyperlight_metric(&self) -> Result<&HyperlightMetric> {
Self::get_init_metrics().call_once(|| {
let result = init_metrics(Self::get_metric_definitions(), Self::get_metrics());
if let Err(e) = result {
error!("Error initializing metrics : {0:?}", e);
}
});
let key: &'static str = <&Self as Into<&'static str>>::into(self);
HyperlightMetric::get_metric_using_key(key, Self::get_hash_map()?)
}
#[inline]
fn get_hash_map() -> Result<&'static HashMap<&'static str, HyperlightMetric>> {
Self::get_metrics()
.get()
.ok_or_else(|| Error("metrics hashmap not initialized".to_string()))
}
}
pub trait HyperlightMetricOps {
fn get_metric(&self) -> Result<&HyperlightMetric>;
}
pub trait GetHyperlightMetric<T> {
fn metric(&self) -> Result<&T>;
}
impl<T: HyperlightMetricEnum<T>> HyperlightMetricOps for T
where
&'static str: From<T>,
for<'a> &'static str: From<&'a T>,
{
fn get_metric(&self) -> Result<&HyperlightMetric> {
self.get_hyperlight_metric()
}
}
#[inline]
fn init_metrics(
metric_definitions: &[HyperlightMetricDefinition],
metrics: &OnceCell<HashMap<&'static str, HyperlightMetric>>,
) -> Result<()> {
let mut hash_map: HashMap<&'static str, HyperlightMetric> = HashMap::new();
register_metrics(metric_definitions, &mut hash_map)?;
if let Err(e) = metrics.set(hash_map) {
error!("metrics hashmap already set : {0:?}", e);
}
Ok(())
}
#[allow(dead_code)]
#[derive(Debug)]
pub enum HyperlightMetricType {
IntCounter,
IntCounterVec,
IntGauge,
IntGaugeVec,
Histogram,
HistogramVec,
}
pub struct HyperlightMetricDefinition {
pub name: &'static str,
pub help: &'static str,
pub metric_type: HyperlightMetricType,
pub labels: &'static [&'static str],
pub buckets: &'static [f64],
}
fn register_metrics(
metric_definitions: &[HyperlightMetricDefinition],
hash_map: &mut HashMap<&'static str, HyperlightMetric>,
) -> Result<()> {
for metric_definition in metric_definitions {
let metric: HyperlightMetric = match &metric_definition.metric_type {
HyperlightMetricType::IntGauge => {
IntGauge::new(metric_definition.name, metric_definition.help)?.into()
}
HyperlightMetricType::IntCounterVec => IntCounterVec::new(
metric_definition.name,
metric_definition.help,
metric_definition.labels,
)?
.into(),
HyperlightMetricType::IntCounter => {
IntCounter::new(metric_definition.name, metric_definition.help)?.into()
}
HyperlightMetricType::HistogramVec => HistogramVec::new(
metric_definition.name,
metric_definition.help,
metric_definition.labels,
metric_definition.buckets.to_vec(),
)?
.into(),
HyperlightMetricType::Histogram => Histogram::new(
metric_definition.name,
metric_definition.help,
metric_definition.buckets.to_vec(),
)?
.into(),
HyperlightMetricType::IntGaugeVec => IntGaugeVec::new(
metric_definition.name,
metric_definition.help,
metric_definition.labels,
)?
.into(),
};
hash_map.insert(metric_definition.name, metric);
}
Ok(())
}
#[derive(Debug)]
pub enum HyperlightMetric {
IntCounter(IntCounter),
IntCounterVec(IntCounterVec),
IntGauge(IntGauge),
IntGaugeVec(IntGaugeVec),
Histogram(Histogram),
HistogramVec(HistogramVec),
}
impl HyperlightMetric {
#[inline]
fn get_metric_using_key<'a>(
key: &'static str,
hash_map: &'a HashMap<&'static str, HyperlightMetric>,
) -> Result<&'a HyperlightMetric> {
hash_map.get(key).ok_or_else(|| MetricNotFound(key))
}
}
static REGISTRY: OnceCell<&Registry> = OnceCell::new();
#[inline]
pub fn get_metrics_registry() -> &'static Registry {
REGISTRY.get_or_init(default_registry)
}
pub fn set_metrics_registry(registry: &'static Registry) -> Result<()> {
match REGISTRY.get() {
Some(_) => {
log_then_return!("Registry was already set");
}
None => {
REGISTRY
.set(registry)
.map_err(|e| new_error!("Registry alread set : {0:?}", e))
}
}
}
fn get_metric_opts(name: &str, help: &str) -> Opts {
let opts = opts!(name, help);
opts.namespace("hyperlight")
}
fn get_histogram_opts(name: &str, help: &str, buckets: Vec<f64>) -> HistogramOpts {
let mut opts = histogram_opts!(name, help);
opts = opts.namespace("hyperlight");
opts.buckets(buckets)
}
pub mod tests {
use std::collections::HashSet;
use super::*;
pub trait HyperlightMetricEnumTest<T>:
HyperlightMetricEnum<T> + From<T> + Into<&'static str>
where
&'static str: From<Self>,
&'static str: for<'a> From<&'a Self>,
{
fn get_enum_variant_names() -> &'static [&'static str];
#[track_caller]
fn enum_has_variant_for_all_metrics() {
let metric_definitions = Self::get_metric_definitions().iter();
for metric_definition in metric_definitions {
let metric_defintion_name = metric_definition.name;
assert!(
Self::get_enum_variant_names().contains(&metric_defintion_name),
"Metric Definition Name {} not found",
metric_defintion_name,
);
}
}
#[track_caller]
fn check_metric_definitions() {
let sandbox_metric_definitions = Self::get_metric_definitions();
let metric_definitions = sandbox_metric_definitions.iter();
let mut help_text = HashSet::new();
for metric_definition in metric_definitions {
assert!(
help_text.insert(metric_definition.help),
"duplicate metric help definition for {}",
metric_definition.name
);
}
assert_eq!(
Self::get_enum_variant_names().len(),
sandbox_metric_definitions.len()
);
}
fn get_intguage_metric(name: &str) -> Result<&IntGauge> {
Self::get_metrics()
.get()
.ok_or_else(|| new_error!("metrics hashmap not initialized"))?
.get(name)
.ok_or_else(|| new_error!("metric not found : {0:?}", name))?
.try_into()
}
fn get_intcountervec_metric(name: &str) -> Result<&IntCounterVec> {
Self::get_metrics()
.get()
.ok_or_else(|| new_error!("metrics hashmap not initialized"))?
.get(name)
.ok_or_else(|| new_error!("metric not found : {0:?}", name))?
.try_into()
}
fn get_intcounter_metric(name: &str) -> Result<&IntCounter> {
Self::get_metrics()
.get()
.ok_or_else(|| new_error!("metrics hashmap not initialized"))?
.get(name)
.ok_or_else(|| new_error!("metric not found : {0:?}", name))?
.try_into()
}
fn get_histogramvec_metric(name: &str) -> Result<&HistogramVec> {
Self::get_metrics()
.get()
.ok_or_else(|| new_error!("metrics hashmap not initialized"))?
.get(name)
.ok_or_else(|| new_error!("metric not found : {0:?}", name))?
.try_into()
}
}
}