use anyhow::Error;
use erased_set::ErasedSyncSet;
use once_cell::sync::OnceCell;
#[cfg(feature = "metrics")]
use prometheus_client::{encoding::text::encode, registry::Registry};
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
use tracing::trace;
#[cfg(not(feature = "metrics"))]
type Registry = ();
static CORE: OnceCell<Core> = OnceCell::new();
#[derive(Debug, Default)]
pub struct Core {
#[cfg(feature = "metrics")]
registry: Registry,
metrics_map: ErasedSyncSet,
#[cfg(feature = "metrics")]
usage_reporter: UsageReporter,
}
#[derive(Debug, Clone)]
pub struct Counter {
#[cfg(feature = "metrics")]
pub counter: prometheus_client::metrics::counter::Counter,
pub description: &'static str,
}
impl Counter {
pub fn new(description: &'static str) -> Self {
Counter {
#[cfg(feature = "metrics")]
counter: Default::default(),
description,
}
}
pub fn inc(&self) -> u64 {
#[cfg(feature = "metrics")]
{
self.counter.inc()
}
#[cfg(not(feature = "metrics"))]
0
}
#[cfg(feature = "metrics")]
pub fn inc_by(&self, v: u64) -> u64 {
self.counter.inc_by(v)
}
#[cfg(feature = "metrics")]
pub fn set(&self, v: u64) -> u64 {
self.counter
.inner()
.store(v, std::sync::atomic::Ordering::Relaxed);
v
}
#[cfg(not(feature = "metrics"))]
pub fn set(&self, _v: u64) -> u64 {
0
}
#[cfg(not(feature = "metrics"))]
pub fn inc_by(&self, _v: u64) -> u64 {
0
}
pub fn get(&self) -> u64 {
#[cfg(feature = "metrics")]
{
self.counter.get()
}
#[cfg(not(feature = "metrics"))]
0
}
}
pub trait Metric:
Default + struct_iterable::Iterable + Sized + std::fmt::Debug + 'static + Send + Sync
{
#[cfg(feature = "metrics")]
fn new(registry: &mut prometheus_client::registry::Registry) -> Self {
let sub_registry = registry.sub_registry_with_prefix(Self::name());
let this = Self::default();
for (metric, counter) in this.iter() {
if let Some(counter) = counter.downcast_ref::<Counter>() {
sub_registry.register(metric, counter.description, counter.counter.clone());
}
}
this
}
#[cfg(not(feature = "metrics"))]
fn new(_: &mut ()) -> Self {
Self::default()
}
fn name() -> &'static str;
#[cfg(feature = "metrics")]
fn with_metric<T, F: FnOnce(&Self) -> T>(f: F) {
Self::try_get().map(f);
}
#[cfg(not(feature = "metrics"))]
fn with_metric<T, F: FnOnce(&Self) -> T>(_f: F) {
}
fn try_get() -> Option<&'static Self> {
Core::get().and_then(|c| c.get_collector::<Self>())
}
}
impl Core {
pub fn init<F: FnOnce(&mut Registry, &mut ErasedSyncSet)>(f: F) {
Self::try_init(f).expect("must only be called once");
}
pub fn try_init<F: FnOnce(&mut Registry, &mut ErasedSyncSet)>(f: F) -> std::io::Result<()> {
let mut registry = Registry::default();
let mut metrics_map = ErasedSyncSet::new();
f(&mut registry, &mut metrics_map);
#[cfg(feature = "metrics")]
let usage_reporter = UsageReporter::new();
CORE.set(Core {
metrics_map,
#[cfg(feature = "metrics")]
registry,
#[cfg(feature = "metrics")]
usage_reporter,
})
.map_err(|_| std::io::Error::new(std::io::ErrorKind::Other, "already set"))
}
pub fn get() -> Option<&'static Self> {
CORE.get()
}
#[cfg(feature = "metrics")]
pub fn registry(&self) -> &Registry {
&self.registry
}
pub fn get_collector<T: Metric>(&self) -> Option<&T> {
self.metrics_map.get::<T>()
}
#[cfg(feature = "metrics")]
pub fn encode(&self) -> Result<String, std::fmt::Error> {
let mut buf = String::new();
encode(&mut buf, &self.registry)?;
Ok(buf)
}
#[cfg(feature = "metrics")]
pub(crate) fn usage_reporter(&self) -> &UsageReporter {
&self.usage_reporter
}
}
pub trait MetricType {
fn name(&self) -> &'static str;
}
pub trait HistogramType {
fn name(&self) -> &'static str;
}
#[derive(Debug, Default)]
pub struct UsageReporter {
pub(crate) report_endpoint: Option<String>,
pub(crate) report_token: Option<String>,
}
impl UsageReporter {
pub fn new() -> Self {
let report_endpoint = std::env::var("IROH_METRICS_USAGE_STATS_ENDPOINT").ok();
let report_token = std::env::var("IROH_METRICS_USAGE_STATS_TOKEN").ok();
UsageReporter {
report_endpoint,
report_token,
}
}
pub async fn report_usage_stats(&self, report: &UsageStatsReport) -> Result<(), Error> {
if let Some(report_endpoint) = &self.report_endpoint {
trace!("reporting usage stats to {}", report_endpoint);
let mut client = reqwest::Client::new().post(report_endpoint);
if let Some(report_token) = &self.report_token {
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(
reqwest::header::COOKIE,
format!("token={}", report_token).parse().unwrap(),
);
client = client.headers(headers);
}
let _ = client.json(report).send().await?;
}
Ok(())
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct UsageStatsReport {
#[serde(with = "time::serde::rfc3339")]
pub timestamp: OffsetDateTime,
pub resource: String,
pub resource_ref: String,
pub value: i64,
pub attribution_id: Option<String>,
pub attribution_key: Option<String>,
}
pub type UsageResource = String;
impl UsageStatsReport {
pub fn new(
resource: UsageResource,
resource_ref: String,
value: i64,
attribution_id: Option<String>,
attribution_key: Option<String>,
) -> Self {
let timestamp = OffsetDateTime::now_utc();
UsageStatsReport {
timestamp,
resource,
resource_ref,
value,
attribution_id,
attribution_key,
}
}
}