use std::sync::{Arc, OnceLock};
use parking_lot::Mutex;
use super::{identity::RuntimeMetricId, model::{RuntimeMetricMetadata,
RuntimeMetricKind, RuntimeMetricRegistrationError, SERIES_CAPACITY}, source::RuntimeMetricsSource};
#[derive(Clone)]
pub(super) struct Entry {
pub(super) metadata: Arc<RuntimeMetricMetadata>,
pub(super) source: Option<Arc<dyn RuntimeMetricsSource>>,
}
pub(super) struct Registry(Mutex<Arc<[Entry]>>);
impl Registry {
pub(super) fn new() -> Self { Self(Mutex::new(Arc::from(Vec::<Entry>::new()))) }
pub(super) fn snapshot(&self) -> Arc<[Entry]> { self.0.lock().clone() }
pub(super) fn register(&self, id: RuntimeMetricId, name: &str, kind: RuntimeMetricKind,
source: Arc<dyn RuntimeMetricsSource>) -> Result<RuntimeMetricId, RuntimeMetricRegistrationError>
{
let candidate = Entry {
metadata: Arc::new(RuntimeMetricMetadata::new(id, name, kind)),
source: Some(source),
};
let old = {
let mut snapshot = self.0.lock();
let position = snapshot.binary_search_by_key(&id, |entry| entry.metadata.id());
if let Ok(index) = position {
if snapshot[index].metadata.name() != name {
return Err(RuntimeMetricRegistrationError::NameConflict);
}
if snapshot[index].source.is_some() { return Ok(id); }
} else if snapshot.len() == SERIES_CAPACITY {
return Err(RuntimeMetricRegistrationError::CapacityExceeded);
}
let mut next = snapshot.to_vec();
match position {
Ok(index) => {
next[index].source = candidate.source.clone();
}
Err(index) => {
next.insert(index, candidate.clone());
}
}
std::mem::replace(&mut *snapshot, Arc::from(next))
};
drop(old);
Ok(id)
}
pub(super) fn unregister(&self, id: RuntimeMetricId) -> bool {
let old = {
let mut snapshot = self.0.lock();
let index = match snapshot.binary_search_by_key(&id, |entry| entry.metadata.id()) {
Ok(index) if snapshot[index].source.is_some() => index,
_ => return false,
};
let mut next = snapshot.to_vec();
next[index].source = None;
std::mem::replace(&mut *snapshot, Arc::from(next))
};
drop(old);
true
}
}
static REGISTRY: OnceLock<Registry> = OnceLock::new();
pub(super) fn global_registry() -> &'static Registry { REGISTRY.get_or_init(Registry::new) }
#[cfg(test)]
mod tests {
use super::*;
use crate::rt::{AsyncRuntime, single_thread::SingleTaskRunner, serial_local_thread::LocalTaskRunner};
use super::super::{source::{LocalRuntimeSource, RuntimeSource}, model::{RuntimeMetricInstanceReport, RuntimeMetricInstanceStatus}};
fn local_source() -> (RuntimeMetricId, Arc<dyn RuntimeMetricsSource>) {
let runtime = LocalTaskRunner::<()>::new().into_local();
let identity = runtime.runtime_metric_slot().get_or_allocate().unwrap();
(identity.id(), Arc::new(LocalRuntimeSource::new(runtime, identity)))
}
#[test]
fn test_snapshot_retirement_and_shared_source_lifetime() {
let registry = Registry::new();
let runner = SingleTaskRunner::<()>::default();
let runtime = runner.startup().unwrap();
let pool = Arc::downgrade(&runtime.shared_pool());
let identity = runtime.runtime_metric_slot().get_or_allocate().unwrap();
let id = identity.id();
let source: Arc<dyn RuntimeMetricsSource> = Arc::new(RuntimeSource::<_, ()>::new(runtime));
let weak_source = Arc::downgrade(&source);
registry.register(id, "shared", RuntimeMetricKind::Single, source.clone()).unwrap();
assert_eq!(registry.register(id, "shared", RuntimeMetricKind::Worker, source.clone()), Ok(id));
assert_eq!(registry.register(id, "different", RuntimeMetricKind::Single, source.clone()),
Err(RuntimeMetricRegistrationError::NameConflict));
let old = registry.snapshot();
drop(runner);
drop(source);
assert!(pool.upgrade().is_some());
assert!(registry.unregister(id));
assert!(!registry.unregister(id));
assert!(registry.snapshot()[0].source.is_none());
assert!(old[0].source.is_some());
assert!(weak_source.upgrade().is_some());
registry.register(id, "shared", RuntimeMetricKind::Worker, old[0].source.as_ref().unwrap().clone()).unwrap();
let current = registry.snapshot();
assert!(Arc::ptr_eq(&old[0].metadata, ¤t[0].metadata));
let item = RuntimeMetricInstanceReport::new(¤t[0].metadata, RuntimeMetricInstanceStatus::Unregistered, None);
assert_eq!(item.kind(), RuntimeMetricKind::Single);
assert!(registry.unregister(id));
drop(current);
drop(old);
assert!(weak_source.upgrade().is_none());
assert!(pool.upgrade().is_none());
}
#[test]
fn test_capacity_sorted_identity_and_retired_budget() {
let registry = Registry::new();
let mut inputs: Vec<_> = (0..SERIES_CAPACITY).map(|_| local_source()).collect();
inputs.reverse();
for (id, source) in &inputs {
registry.register(*id, "same-name", RuntimeMetricKind::Local, source.clone()).unwrap();
}
let snapshot = registry.snapshot();
assert_eq!(snapshot.len(), SERIES_CAPACITY);
assert!(snapshot.windows(2).all(|pair| pair[0].metadata.id() < pair[1].metadata.id()));
for (id, _) in &inputs { assert!(registry.unregister(*id)); }
assert!(registry.snapshot().iter().all(|entry| entry.source.is_none()));
let (extra_id, extra_source) = local_source();
assert!(!registry.unregister(extra_id));
assert_eq!(registry.register(extra_id, "same-name", RuntimeMetricKind::Local, extra_source),
Err(RuntimeMetricRegistrationError::CapacityExceeded));
let (id, source) = &inputs[0];
assert_eq!(registry.register(*id, "same-name", RuntimeMetricKind::Local, source.clone()), Ok(*id));
assert_eq!(registry.snapshot().len(), SERIES_CAPACITY);
assert_eq!(registry.snapshot().iter().filter(|entry| entry.source.is_some()).count(), 1);
}
}