use std::sync::Arc;
#[cfg(feature = "trace")]
use std::{sync::atomic::{AtomicBool, Ordering}, time::Instant};
use crate::rt::{AsyncTaskPool, AsyncTaskPoolExt, single_thread::SingleTaskRuntime,
multi_thread::MultiTaskRuntime, worker_thread::WorkerRuntime,
serial_local_thread::LocalTaskRuntime};
use super::{registry, reporter, source::{RuntimeSource, LocalRuntimeSource}, model::{validate_label, LabelValidationError}};
#[cfg(feature = "trace")]
use super::sample_error::RuntimeMetricSampleError;
pub use super::identity::RuntimeMetricId;
pub use super::model::{RuntimeMetricKind, RuntimeMetricRegistrationError,
RuntimeMetricReportingInitError, RuntimeMetricCollectionOptions, RuntimeMetricCollectionError,
RuntimeMetricInstanceStatus, RuntimeMetricInstanceReport, RuntimeMetricCollectionReport};
mod private { pub trait Sealed {} }
pub trait RuntimeMetricRegistrable: private::Sealed {
fn register_metrics(&self, name: &str)
-> Result<RuntimeMetricId, RuntimeMetricRegistrationError>;
}
fn check_name(name: &str) -> Result<(), RuntimeMetricRegistrationError> {
validate_label(name).map_err(|error| match error {
LabelValidationError::TooLong => RuntimeMetricRegistrationError::NameTooLong,
LabelValidationError::Invalid => RuntimeMetricRegistrationError::InvalidName,
})
}
macro_rules! direct_registration {
($runtime:ident, $kind:ident) => {
impl<O: Default + 'static, P: AsyncTaskPoolExt<O> + AsyncTaskPool<O, Pool = P>>
private::Sealed for $runtime<O, P> {}
impl<O: Default + 'static, P: AsyncTaskPoolExt<O> + AsyncTaskPool<O, Pool = P>>
RuntimeMetricRegistrable for $runtime<O, P>
{
fn register_metrics(&self, name: &str) -> Result<RuntimeMetricId, RuntimeMetricRegistrationError> {
check_name(name)?;
let identity = self.runtime_metric_slot().get_or_allocate()
.ok_or(RuntimeMetricRegistrationError::IdentityExhausted)?;
registry::global_registry().register(identity.id(), name, RuntimeMetricKind::$kind,
Arc::new(RuntimeSource::<_, O>::new(self.clone())))
}
}
};
}
direct_registration!(SingleTaskRuntime, Single);
direct_registration!(MultiTaskRuntime, Multi);
impl<O: Default + 'static, P: AsyncTaskPoolExt<O> + AsyncTaskPool<O, Pool = P>>
private::Sealed for WorkerRuntime<O, P> {}
impl<O: Default + 'static, P: AsyncTaskPoolExt<O> + AsyncTaskPool<O, Pool = P>>
RuntimeMetricRegistrable for WorkerRuntime<O, P>
{
fn register_metrics(&self, name: &str) -> Result<RuntimeMetricId, RuntimeMetricRegistrationError> {
check_name(name)?;
let identity = self.get_worker_runtime().runtime_metric_slot().get_or_allocate()
.ok_or(RuntimeMetricRegistrationError::IdentityExhausted)?;
registry::global_registry().register(identity.id(), name, RuntimeMetricKind::Worker,
Arc::new(RuntimeSource::<_, O>::new(self.clone())))
}
}
impl<O: Default + 'static> private::Sealed for LocalTaskRuntime<O> {}
impl<O: Default + 'static> RuntimeMetricRegistrable for LocalTaskRuntime<O> {
fn register_metrics(&self, name: &str) -> Result<RuntimeMetricId, RuntimeMetricRegistrationError> {
check_name(name)?;
let identity = self.runtime_metric_slot().get_or_allocate()
.ok_or(RuntimeMetricRegistrationError::IdentityExhausted)?;
registry::global_registry().register(identity.id(), name, RuntimeMetricKind::Local,
Arc::new(LocalRuntimeSource::new(self.clone(), identity)))
}
}
macro_rules! reject_serial_registration {
($module:ident, $runtime:ident) => {
impl<O: Default + 'static, P: crate::rt::serial::AsyncTaskPoolExt<O>
+ crate::rt::serial::AsyncTaskPool<O, Pool = P>>
private::Sealed for crate::rt::$module::$runtime<O, P> {}
impl<O: Default + 'static, P: crate::rt::serial::AsyncTaskPoolExt<O>
+ crate::rt::serial::AsyncTaskPool<O, Pool = P>>
RuntimeMetricRegistrable for crate::rt::$module::$runtime<O, P>
{
fn register_metrics(&self, _name: &str) -> Result<RuntimeMetricId, RuntimeMetricRegistrationError> {
Err(RuntimeMetricRegistrationError::ThreadBoundRuntimeRequiresOwnerBridge)
}
}
};
}
reject_serial_registration!(serial_single_thread, SingleTaskRuntime);
reject_serial_registration!(serial_worker_thread, WorkerRuntime);
pub fn register_runtime_metrics<R: RuntimeMetricRegistrable + ?Sized>(runtime: &R, name: &str)
-> Result<RuntimeMetricId, RuntimeMetricRegistrationError>
{
runtime.register_metrics(name)
}
pub fn unregister_runtime_metrics(id: RuntimeMetricId) -> bool {
registry::global_registry().unregister(id)
}
pub fn init_runtime_metrics_reporting(process_instance: &str) -> Result<(), RuntimeMetricReportingInitError> {
reporter::initialize(process_instance)
}
#[cfg(feature = "trace")]
static COLLECTING: AtomicBool = AtomicBool::new(false);
#[cfg(feature = "trace")]
struct CollectionGuard;
#[cfg(feature = "trace")]
impl Drop for CollectionGuard {
fn drop(&mut self) { COLLECTING.store(false, Ordering::Release); }
}
pub fn collect_and_report_runtime_metrics(options: RuntimeMetricCollectionOptions)
-> Result<RuntimeMetricCollectionReport, RuntimeMetricCollectionError>
{
#[cfg(not(feature = "trace"))]
{
let _ = options;
Err(RuntimeMetricCollectionError::ReportingDisabled)
}
#[cfg(feature = "trace")]
{
let reporter = reporter::initialized()?;
let now = Instant::now();
let round_deadline = now.checked_add(options.round_timeout())
.ok_or(RuntimeMetricCollectionError::DeadlineOverflow)?;
now.checked_add(options.instance_timeout()).ok_or(RuntimeMetricCollectionError::DeadlineOverflow)?;
COLLECTING.compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed)
.map_err(|_| RuntimeMetricCollectionError::CollectionInProgress)?;
let _guard = CollectionGuard;
let snapshot = registry::global_registry().snapshot();
let mut instances = Vec::with_capacity(snapshot.len());
for entry in snapshot.iter() {
let (counts, status) = if let Some(source) = &entry.source {
let now = Instant::now();
if now >= round_deadline {
(None, RuntimeMetricInstanceStatus::RoundDeadlineSkipped)
} else {
let budget = options.instance_timeout().min(round_deadline.duration_since(now));
let deadline = now.checked_add(budget).ok_or(RuntimeMetricCollectionError::DeadlineOverflow)?;
match source.sample(deadline) {
Ok(counts) => (Some(counts), RuntimeMetricInstanceStatus::RecordInvoked),
Err(RuntimeMetricSampleError::RequestInFlight) => (None, RuntimeMetricInstanceStatus::RequestInFlight),
Err(RuntimeMetricSampleError::DeadlineExceeded) => (None, RuntimeMetricInstanceStatus::DeadlineExceeded),
Err(RuntimeMetricSampleError::ReplyClosed) => (None, RuntimeMetricInstanceStatus::ReplyClosed),
}
}
} else { (None, RuntimeMetricInstanceStatus::Unregistered) };
let status = reporter.record(&entry.metadata, counts, status);
instances.push(RuntimeMetricInstanceReport::new(&entry.metadata, status,
counts.map(|counts| (counts.len(), counts.wait_len()))));
}
Ok(RuntimeMetricCollectionReport::new(instances))
}
}