use super::model::RuntimeMetricReportingInitError;
#[cfg(feature = "trace")]
use std::sync::{Arc, OnceLock, atomic::{AtomicBool, Ordering}};
#[cfg(feature = "trace")]
use super::{counts::RuntimeMetricCounts, model::{validate_label, LabelValidationError, RuntimeMetricMetadata,
RuntimeMetricInstanceStatus, RuntimeMetricCollectionError}};
#[cfg(feature = "trace")]
pub(super) struct Reporter {
process: Arc<str>,
len: opentelemetry::metrics::Gauge<u64>,
wait_len: opentelemetry::metrics::Gauge<u64>,
state: opentelemetry::metrics::Gauge<u64>,
}
#[cfg(feature = "trace")]
static REPORTER: OnceLock<Reporter> = OnceLock::new();
#[cfg(feature = "trace")]
static INITIALIZING: AtomicBool = AtomicBool::new(false);
#[cfg(feature = "trace")]
struct InitializationGuard;
#[cfg(feature = "trace")]
impl Drop for InitializationGuard {
fn drop(&mut self) { INITIALIZING.store(false, Ordering::Release); }
}
#[cfg(feature = "trace")]
pub(super) fn initialize(process: &str) -> Result<(), RuntimeMetricReportingInitError> {
validate_label(process).map_err(|error| match error {
LabelValidationError::TooLong => RuntimeMetricReportingInitError::ProcessInstanceTooLong,
LabelValidationError::Invalid => RuntimeMetricReportingInitError::InvalidProcessInstance,
})?;
let check = |reporter: &Reporter| if reporter.process.as_ref() == process { Ok(()) }
else { Err(RuntimeMetricReportingInitError::AlreadyInitialized) };
if let Some(reporter) = REPORTER.get() { return check(reporter); }
INITIALIZING.compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed)
.map_err(|_| RuntimeMetricReportingInitError::InitializationInProgress)?;
let _guard = InitializationGuard;
if let Some(reporter) = REPORTER.get() { return check(reporter); }
let scope = opentelemetry::InstrumentationScope::builder("pi_async_rt")
.with_version(env!("CARGO_PKG_VERSION")).build();
let meter = opentelemetry::global::meter_with_scope(scope);
let reporter = Reporter {
process: Arc::from(process),
len: meter.u64_gauge("pi_async_rt.runtime.len").with_unit("{task}")
.with_description("已登记运行时原任务池待轮询数量").build(),
wait_len: meter.u64_gauge("pi_async_rt.runtime.wait_len").with_unit("{task}")
.with_description("已登记运行时原等待读数").build(),
state: meter.u64_gauge("pi_async_rt.runtime.sample_state").with_unit("1")
.with_description("运行时指标采样及记录状态码").build(),
};
match REPORTER.set(reporter) {
Ok(()) => Ok(()),
Err(_) => unreachable!("唯一指标初始化许可不允许重复发布"),
}
}
#[cfg(not(feature = "trace"))]
pub(super) fn initialize(_process: &str) -> Result<(), RuntimeMetricReportingInitError> {
Err(RuntimeMetricReportingInitError::ReportingDisabled)
}
#[cfg(feature = "trace")]
pub(super) fn initialized() -> Result<&'static Reporter, RuntimeMetricCollectionError> {
REPORTER.get().ok_or(RuntimeMetricCollectionError::ReportingNotInitialized)
}
#[cfg(feature = "trace")]
impl Reporter {
pub(super) fn record(&self, metadata: &RuntimeMetricMetadata,
counts: Option<RuntimeMetricCounts>, mut status: RuntimeMetricInstanceStatus)
-> RuntimeMetricInstanceStatus
{
if let Some(counts) = counts {
if counts.len() as u128 > i64::MAX as u128
|| counts.wait_len().map_or(false, |value| value as u128 > i64::MAX as u128)
{
status = RuntimeMetricInstanceStatus::MetricValueOutOfRange;
}
}
let labels = metadata.labels(&self.process);
if status == RuntimeMetricInstanceStatus::RecordInvoked {
if let Some(counts) = counts {
self.len.record(counts.len() as u64, labels);
if let Some(wait_len) = counts.wait_len() { self.wait_len.record(wait_len as u64, labels); }
}
}
self.state.record(status.code(), labels);
status
}
}
#[cfg(all(test, feature = "trace"))]
mod tests {
use super::*;
use super::super::{identity::RuntimeMetricIdentitySlot, model::RuntimeMetricKind};
use opentelemetry::{KeyValue, metrics::{Gauge, SyncInstrument}};
use std::sync::Mutex;
type Events = Arc<Mutex<Vec<(&'static str, u64, Vec<KeyValue>)>>>;
struct CallProbe(&'static str, Events);
impl SyncInstrument<u64> for CallProbe {
fn measure(&self, value: u64, attributes: &[KeyValue]) {
self.1.lock().unwrap().push((self.0, value, attributes.to_vec()));
}
}
fn fixture() -> (Reporter, Events, RuntimeMetricMetadata) {
let events: Events = Arc::new(Mutex::new(Vec::new()));
let gauge = |name| Gauge::new(Arc::new(CallProbe(name, events.clone())));
let reporter = Reporter { process: Arc::from("process-unique"), len: gauge("len"),
wait_len: gauge("wait_len"), state: gauge("sample_state") };
let id = RuntimeMetricIdentitySlot::new().get_or_allocate().unwrap().id();
(reporter, events, RuntimeMetricMetadata::new(id, "网关", RuntimeMetricKind::Single))
}
#[test]
fn test_record_calls_labels_and_failure_without_fake_counts() {
let (reporter, events, metadata) = fixture();
assert_eq!(reporter.record(&metadata, Some(RuntimeMetricCounts::new(7, Some(0))),
RuntimeMetricInstanceStatus::RecordInvoked), RuntimeMetricInstanceStatus::RecordInvoked);
let labels = vec![KeyValue::new("rt.id", metadata.id().as_u64() as i64),
KeyValue::new("rt.name", "网关"), KeyValue::new("rt.process", "process-unique")];
assert_eq!(*events.lock().unwrap(), vec![("len", 7, labels.clone()),
("wait_len", 0, labels.clone()), ("sample_state", 1, labels.clone())]);
let cached = metadata.labels(&reporter.process).as_ptr();
events.lock().unwrap().clear();
reporter.record(&metadata, Some(RuntimeMetricCounts::new(0, None)), RuntimeMetricInstanceStatus::RecordInvoked);
assert_eq!(*events.lock().unwrap(), vec![("len", 0, labels.clone()), ("sample_state", 1, labels.clone())]);
assert_eq!(cached, metadata.labels(&reporter.process).as_ptr());
for status in [RuntimeMetricInstanceStatus::Unregistered, RuntimeMetricInstanceStatus::RequestInFlight,
RuntimeMetricInstanceStatus::DeadlineExceeded, RuntimeMetricInstanceStatus::ReplyClosed,
RuntimeMetricInstanceStatus::RoundDeadlineSkipped]
{
events.lock().unwrap().clear();
assert_eq!(reporter.record(&metadata, None, status), status);
assert_eq!(*events.lock().unwrap(), vec![("sample_state", status.code(), labels.clone())]);
}
}
#[test]
fn test_whole_sample_integer_range_preserves_raw_counts() {
let (reporter, events, metadata) = fixture();
let max_valid = (i64::MAX as u128).min(usize::MAX as u128) as usize;
reporter.record(&metadata, Some(RuntimeMetricCounts::new(max_valid, Some(max_valid))),
RuntimeMetricInstanceStatus::RecordInvoked);
assert_eq!(events.lock().unwrap().iter().map(|(name, value, _)| (*name, *value)).collect::<Vec<_>>(),
vec![("len", max_valid as u64), ("wait_len", max_valid as u64), ("sample_state", 1)]);
if usize::BITS >= 64 {
let over = (i64::MAX as u128 + 1) as usize;
for (len, wait_len) in [(over, None), (0, Some(over)), (usize::MAX, Some(usize::MAX))] {
events.lock().unwrap().clear();
let counts = RuntimeMetricCounts::new(len, wait_len);
assert_eq!(reporter.record(&metadata, Some(counts), RuntimeMetricInstanceStatus::RecordInvoked),
RuntimeMetricInstanceStatus::MetricValueOutOfRange);
assert_eq!((counts.len(), counts.wait_len()), (len, wait_len));
let records = events.lock().unwrap();
assert_eq!(records.len(), 1);
assert_eq!((records[0].0, records[0].1), ("sample_state", 6));
}
}
}
#[test]
fn test_record_result_and_report_summary_preserve_range_failure() {
use super::super::model::{RuntimeMetricCollectionReport, RuntimeMetricInstanceReport};
let (reporter, events, metadata) = fixture();
let max = (i64::MAX as u128).min(usize::MAX as u128) as usize;
let mut items = Vec::new();
for counts in [RuntimeMetricCounts::new(max, Some(0)), RuntimeMetricCounts::new(0, None)] {
let status = reporter.record(&metadata, Some(counts), RuntimeMetricInstanceStatus::RecordInvoked);
assert_eq!(status, RuntimeMetricInstanceStatus::RecordInvoked);
items.push(RuntimeMetricInstanceReport::new(&metadata, status, Some((counts.len(), counts.wait_len()))));
}
let mut expected = vec![("len", max as u64), ("wait_len", 0), ("sample_state", 1), ("len", 0), ("sample_state", 1)];
let mut out_of_range = 0;
if usize::BITS >= 64 {
let counts = RuntimeMetricCounts::new(0, Some(usize::MAX));
let status = reporter.record(&metadata, Some(counts), RuntimeMetricInstanceStatus::RecordInvoked);
assert_eq!(status, RuntimeMetricInstanceStatus::MetricValueOutOfRange);
items.push(RuntimeMetricInstanceReport::new(&metadata, status, Some((0, Some(usize::MAX)))));
expected.push(("sample_state", 6));
out_of_range = 1;
}
for status in [RuntimeMetricInstanceStatus::Unregistered, RuntimeMetricInstanceStatus::RequestInFlight,
RuntimeMetricInstanceStatus::DeadlineExceeded, RuntimeMetricInstanceStatus::ReplyClosed,
RuntimeMetricInstanceStatus::RoundDeadlineSkipped]
{
assert_eq!(reporter.record(&metadata, None, status), status);
items.push(RuntimeMetricInstanceReport::new(&metadata, status, None));
expected.push(("sample_state", status.code()));
}
assert_eq!(events.lock().unwrap().iter().map(|(name, value, _)| (*name, *value)).collect::<Vec<_>>(), expected);
let report = RuntimeMetricCollectionReport::new(items);
assert_eq!((report.active_count(), report.retired_count(), report.sampled_count()),
(6 + out_of_range, 1, 2 + out_of_range));
assert_eq!(report.count(RuntimeMetricInstanceStatus::RecordInvoked), 2);
assert_eq!(report.count(RuntimeMetricInstanceStatus::MetricValueOutOfRange), out_of_range);
if out_of_range != 0 { assert_eq!(report.instances()[2].counts(), Some((0, Some(usize::MAX)))); }
for status in [RuntimeMetricInstanceStatus::Unregistered, RuntimeMetricInstanceStatus::RequestInFlight,
RuntimeMetricInstanceStatus::DeadlineExceeded, RuntimeMetricInstanceStatus::ReplyClosed,
RuntimeMetricInstanceStatus::RoundDeadlineSkipped]
{ assert_eq!(report.count(status), 1); }
}
}