use std::{fmt, sync::Arc, time::Duration};
#[cfg(feature = "trace")]
use std::sync::OnceLock;
use super::identity::RuntimeMetricId;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RuntimeMetricKind {
Single,
Multi,
Worker,
Local,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RuntimeMetricInstanceStatus {
Unregistered = 0,
RecordInvoked = 1,
RequestInFlight = 2,
DeadlineExceeded = 3,
ReplyClosed = 4,
RoundDeadlineSkipped = 5,
MetricValueOutOfRange = 6,
}
impl RuntimeMetricInstanceStatus {
pub const fn code(self) -> u64 { self as u64 }
}
macro_rules! metric_error {
($(#[$doc:meta])* $name:ident { $($(#[$variant_doc:meta])* $variant:ident => $message:literal),+ $(,)? }) => {
$(#[$doc])*
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum $name { $($(#[$variant_doc])* $variant),+ }
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self { $(Self::$variant => $message),+ })
}
}
impl std::error::Error for $name {}
};
}
metric_error! {
RuntimeMetricRegistrationError {
InvalidName => "运行时指标名称无效",
NameTooLong => "运行时指标名称过长",
NameConflict => "同一运行时核心的指标名称冲突",
CapacityExceeded => "运行时指标累计系列容量已满",
IdentityExhausted => "运行时指标身份已耗尽",
ThreadBoundRuntimeRequiresOwnerBridge => "该线程受限运行时暂不支持全局指标登记"
}
}
metric_error! {
RuntimeMetricReportingInitError {
ReportingDisabled => "未启用trace指标上报",
InvalidProcessInstance => "进程实例标识无效",
ProcessInstanceTooLong => "进程实例标识过长",
AlreadyInitialized => "指标上报已使用其它进程标识初始化",
InitializationInProgress => "指标上报初始化正在执行"
}
}
metric_error! {
RuntimeMetricCollectionError {
ReportingDisabled => "未启用trace指标上报",
ReportingNotInitialized => "指标记录器尚未初始化",
CollectionInProgress => "已有运行时指标采集正在执行",
InvalidTimeout => "指标采集时间预算必须非零",
DeadlineOverflow => "指标采集截止超出单调时钟范围"
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RuntimeMetricCollectionOptions {
instance_timeout: Duration,
round_timeout: Duration,
}
impl RuntimeMetricCollectionOptions {
pub fn new(instance_timeout: Duration, round_timeout: Duration)
-> Result<Self, RuntimeMetricCollectionError>
{
if instance_timeout.is_zero() || round_timeout.is_zero() {
return Err(RuntimeMetricCollectionError::InvalidTimeout);
}
Ok(Self { instance_timeout, round_timeout })
}
pub const fn instance_timeout(&self) -> Duration { self.instance_timeout }
pub const fn round_timeout(&self) -> Duration { self.round_timeout }
}
impl Default for RuntimeMetricCollectionOptions {
fn default() -> Self {
Self { instance_timeout: Duration::from_millis(100), round_timeout: Duration::from_secs(3) }
}
}
#[derive(Debug)]
pub struct RuntimeMetricInstanceReport {
id: RuntimeMetricId,
name: Arc<str>,
kind: RuntimeMetricKind,
status: RuntimeMetricInstanceStatus,
counts: Option<(usize, Option<usize>)>,
}
impl RuntimeMetricInstanceReport {
pub const fn id(&self) -> RuntimeMetricId { self.id }
pub fn name(&self) -> &str { &self.name }
pub const fn kind(&self) -> RuntimeMetricKind { self.kind }
pub const fn status(&self) -> RuntimeMetricInstanceStatus { self.status }
pub const fn counts(&self) -> Option<(usize, Option<usize>)> { self.counts }
pub(super) fn new(metadata: &RuntimeMetricMetadata, status: RuntimeMetricInstanceStatus,
counts: Option<(usize, Option<usize>)>) -> Self
{
Self { id: metadata.id, name: metadata.name.clone(), kind: metadata.kind, status, counts }
}
}
#[derive(Debug)]
pub struct RuntimeMetricCollectionReport {
instances: Vec<RuntimeMetricInstanceReport>,
statuses: [usize; 7],
}
impl RuntimeMetricCollectionReport {
pub fn instances(&self) -> &[RuntimeMetricInstanceReport] { &self.instances }
pub fn count(&self, status: RuntimeMetricInstanceStatus) -> usize { self.statuses[status.code() as usize] }
pub fn active_count(&self) -> usize { self.instances.len() - self.retired_count() }
pub fn retired_count(&self) -> usize { self.count(RuntimeMetricInstanceStatus::Unregistered) }
pub fn sampled_count(&self) -> usize {
self.count(RuntimeMetricInstanceStatus::RecordInvoked) + self.count(RuntimeMetricInstanceStatus::MetricValueOutOfRange)
}
pub(super) fn new(instances: Vec<RuntimeMetricInstanceReport>) -> Self {
let mut statuses = [0; 7];
for instance in &instances { statuses[instance.status.code() as usize] += 1; }
Self { instances, statuses }
}
}
pub(super) const SERIES_CAPACITY: usize = 1024;
pub(super) struct RuntimeMetricMetadata {
id: RuntimeMetricId,
name: Arc<str>,
kind: RuntimeMetricKind,
#[cfg(feature = "trace")]
labels: OnceLock<[opentelemetry::KeyValue; 3]>,
}
impl RuntimeMetricMetadata {
pub(super) fn new(id: RuntimeMetricId, name: &str, kind: RuntimeMetricKind) -> Self {
Self { id, name: Arc::from(name), kind, #[cfg(feature = "trace")] labels: OnceLock::new() }
}
pub(super) fn id(&self) -> RuntimeMetricId { self.id }
pub(super) fn name(&self) -> &str { &self.name }
#[cfg(feature = "trace")]
pub(super) fn labels(&self, process: &Arc<str>) -> &[opentelemetry::KeyValue; 3] {
self.labels.get_or_init(|| [
opentelemetry::KeyValue::new("rt.id", self.id.as_u64() as i64),
opentelemetry::KeyValue::new("rt.name", self.name.clone()),
opentelemetry::KeyValue::new("rt.process", process.clone()),
])
}
}
pub(super) enum LabelValidationError { TooLong, Invalid }
pub(super) fn validate_label(value: &str) -> Result<(), LabelValidationError> {
if value.len() > 256 { return Err(LabelValidationError::TooLong); }
if value.trim().is_empty() || value.chars().any(char::is_control) { return Err(LabelValidationError::Invalid); }
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use super::super::identity::RuntimeMetricIdentitySlot;
#[test]
fn test_label_and_options_boundaries() {
for invalid in ["", " ", "\t", "\n", "\u{2003}", "a\0b", "a\u{7f}", "a\r"] {
assert!(matches!(validate_label(invalid), Err(LabelValidationError::Invalid)));
}
for valid in ["a", " gateway ", "网关", "a-b_1"] { assert!(validate_label(valid).is_ok()); }
assert!(validate_label(&"x".repeat(256)).is_ok());
assert!(validate_label(&"界".repeat(85)).is_ok());
for invalid in ["x".repeat(257), "界".repeat(86)] {
assert!(matches!(validate_label(&invalid), Err(LabelValidationError::TooLong)));
}
for a in [Duration::ZERO, Duration::from_nanos(1), Duration::MAX] {
for b in [Duration::ZERO, Duration::from_nanos(1), Duration::MAX] {
let options = RuntimeMetricCollectionOptions::new(a, b);
if a.is_zero() || b.is_zero() {
assert_eq!(options, Err(RuntimeMetricCollectionError::InvalidTimeout));
} else {
let options = options.unwrap();
assert_eq!((options.instance_timeout(), options.round_timeout()), (a, b));
}
}
}
let default = RuntimeMetricCollectionOptions::default();
assert_eq!((default.instance_timeout(), default.round_timeout()),
(Duration::from_millis(100), Duration::from_secs(3)));
}
#[test]
fn test_report_statuses_and_counts_do_not_retain_source() {
let statuses = [RuntimeMetricInstanceStatus::Unregistered, RuntimeMetricInstanceStatus::RecordInvoked,
RuntimeMetricInstanceStatus::RequestInFlight, RuntimeMetricInstanceStatus::DeadlineExceeded,
RuntimeMetricInstanceStatus::ReplyClosed, RuntimeMetricInstanceStatus::RoundDeadlineSkipped,
RuntimeMetricInstanceStatus::MetricValueOutOfRange];
let id = RuntimeMetricIdentitySlot::new().get_or_allocate().unwrap().id();
let report = {
let metadata = RuntimeMetricMetadata::new(id, "网关", RuntimeMetricKind::Local);
let items = statuses.iter().enumerate().map(|(index, &status)| {
assert_eq!(status.code(), index as u64);
let counts = if matches!(status, RuntimeMetricInstanceStatus::RecordInvoked
| RuntimeMetricInstanceStatus::MetricValueOutOfRange) { Some((usize::MAX, None)) } else { None };
RuntimeMetricInstanceReport::new(&metadata, status, counts)
}).collect();
RuntimeMetricCollectionReport::new(items)
};
assert_eq!((report.active_count(), report.retired_count(), report.sampled_count()), (6, 1, 2));
for (&status, item) in statuses.iter().zip(report.instances()) {
assert_eq!(report.count(status), 1);
assert_eq!((item.id(), item.name(), item.kind(), item.status()), (id, "网关", RuntimeMetricKind::Local, status));
}
assert_eq!(report.instances()[6].counts(), Some((usize::MAX, None)));
let empty = RuntimeMetricCollectionReport::new(Vec::new());
assert_eq!((empty.active_count(), empty.retired_count(), empty.sampled_count()), (0, 0, 0));
}
}