use crate::{
InternalError, InternalErrorClass, InternalErrorOrigin, domain::policy::pool::PoolPolicyError,
};
use std::{cell::RefCell, collections::HashMap};
thread_local! {
static POOL_METRICS: RefCell<HashMap<PoolMetricKey, u64>> =
RefCell::new(HashMap::new());
}
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[remain::sorted]
pub enum PoolMetricOperation {
CreateEmpty,
ImportImmediate,
ImportQueued,
Recycle,
Reset,
Scheduler,
SelectReady,
}
impl PoolMetricOperation {
#[must_use]
pub const fn metric_label(self) -> &'static str {
match self {
Self::CreateEmpty => "create_empty",
Self::ImportImmediate => "import_immediate",
Self::ImportQueued => "import_queued",
Self::Recycle => "recycle",
Self::Reset => "reset",
Self::Scheduler => "scheduler",
Self::SelectReady => "select_ready",
}
}
}
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[remain::sorted]
pub enum PoolMetricOutcome {
Completed,
Failed,
Requeued,
Scheduled,
Skipped,
Started,
}
impl PoolMetricOutcome {
#[must_use]
pub const fn metric_label(self) -> &'static str {
match self {
Self::Completed => "completed",
Self::Failed => "failed",
Self::Requeued => "requeued",
Self::Scheduled => "scheduled",
Self::Skipped => "skipped",
Self::Started => "started",
}
}
}
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[remain::sorted]
pub enum PoolMetricReason {
AlreadyPresent,
Empty,
FailedEntry,
InProgress,
InvalidState,
ManagementCall,
NonImportableLocal,
NotFound,
Ok,
PolicyDenied,
RegisteredInSubnet,
Unknown,
}
impl PoolMetricReason {
#[must_use]
pub const fn metric_label(self) -> &'static str {
match self {
Self::AlreadyPresent => "already_present",
Self::Empty => "empty",
Self::FailedEntry => "failed_entry",
Self::InProgress => "in_progress",
Self::InvalidState => "invalid_state",
Self::ManagementCall => "management_call",
Self::NonImportableLocal => "non_importable_local",
Self::NotFound => "not_found",
Self::Ok => "ok",
Self::PolicyDenied => "policy_denied",
Self::RegisteredInSubnet => "registered_in_subnet",
Self::Unknown => "unknown",
}
}
#[must_use]
pub(crate) const fn from_error(err: &InternalError) -> Self {
match (err.class(), err.origin()) {
(InternalErrorClass::Infra, InternalErrorOrigin::Infra) => Self::ManagementCall,
(InternalErrorClass::Access | InternalErrorClass::Domain, _) => Self::PolicyDenied,
(
InternalErrorClass::Invariant
| InternalErrorClass::Ops
| InternalErrorClass::Workflow,
_,
) => Self::InvalidState,
_ => Self::Unknown,
}
}
#[must_use]
pub(crate) const fn from_policy(err: &PoolPolicyError) -> Self {
match err {
PoolPolicyError::RegisteredInSubnet(_) => Self::RegisteredInSubnet,
PoolPolicyError::NonImportableOnLocal { .. } => Self::NonImportableLocal,
PoolPolicyError::NotRegisteredInSubnet(_) => Self::NotFound,
PoolPolicyError::NotAuthorized => Self::PolicyDenied,
}
}
}
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub struct PoolMetricKey {
pub operation: PoolMetricOperation,
pub outcome: PoolMetricOutcome,
pub reason: PoolMetricReason,
}
pub struct PoolMetrics;
impl PoolMetrics {
pub fn record(
operation: PoolMetricOperation,
outcome: PoolMetricOutcome,
reason: PoolMetricReason,
) {
POOL_METRICS.with_borrow_mut(|counts| {
let key = PoolMetricKey {
operation,
outcome,
reason,
};
let entry = counts.entry(key).or_insert(0);
*entry = entry.saturating_add(1);
});
}
#[must_use]
pub fn snapshot() -> Vec<(PoolMetricKey, u64)> {
POOL_METRICS
.with_borrow(std::clone::Clone::clone)
.into_iter()
.collect()
}
#[cfg(test)]
pub fn reset() {
POOL_METRICS.with_borrow_mut(HashMap::clear);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn snapshot_map() -> HashMap<PoolMetricKey, u64> {
PoolMetrics::snapshot().into_iter().collect()
}
#[test]
fn pool_metrics_accumulate_by_operation_outcome_and_reason() {
PoolMetrics::reset();
PoolMetrics::record(
PoolMetricOperation::Reset,
PoolMetricOutcome::Started,
PoolMetricReason::Ok,
);
PoolMetrics::record(
PoolMetricOperation::ImportQueued,
PoolMetricOutcome::Skipped,
PoolMetricReason::AlreadyPresent,
);
PoolMetrics::record(
PoolMetricOperation::ImportQueued,
PoolMetricOutcome::Skipped,
PoolMetricReason::AlreadyPresent,
);
let map = snapshot_map();
assert_eq!(
map.get(&PoolMetricKey {
operation: PoolMetricOperation::Reset,
outcome: PoolMetricOutcome::Started,
reason: PoolMetricReason::Ok,
}),
Some(&1)
);
assert_eq!(
map.get(&PoolMetricKey {
operation: PoolMetricOperation::ImportQueued,
outcome: PoolMetricOutcome::Skipped,
reason: PoolMetricReason::AlreadyPresent,
}),
Some(&2)
);
}
}