use crate::{InternalError, InternalErrorClass, InternalErrorOrigin};
use std::{cell::RefCell, collections::HashMap};
thread_local! {
static PLACEMENT_BINDING_METRICS: RefCell<HashMap<PlacementBindingMetricKey, u64>> =
RefCell::new(HashMap::new());
}
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[remain::sorted]
pub enum PlacementBindingMetricOperation {
Bind,
Claim,
Classify,
CleanupStale,
CreateInstance,
Finalize,
Recover,
RecycleAbandoned,
RepairStale,
Resolve,
}
impl PlacementBindingMetricOperation {
#[must_use]
pub const fn metric_label(self) -> &'static str {
match self {
Self::Bind => "bind",
Self::Claim => "claim",
Self::Classify => "classify",
Self::CleanupStale => "cleanup_stale",
Self::CreateInstance => "create_instance",
Self::Finalize => "finalize",
Self::Recover => "recover",
Self::RecycleAbandoned => "recycle_abandoned",
Self::RepairStale => "repair_stale",
Self::Resolve => "resolve",
}
}
}
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[remain::sorted]
pub enum PlacementBindingMetricOutcome {
Completed,
Failed,
Skipped,
Started,
}
impl PlacementBindingMetricOutcome {
#[must_use]
pub const fn metric_label(self) -> &'static str {
match self {
Self::Completed => "completed",
Self::Failed => "failed",
Self::Skipped => "skipped",
Self::Started => "started",
}
}
}
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[remain::sorted]
pub enum PlacementBindingMetricReason {
AlreadyBound,
Claimed,
ClaimLost,
InvalidChild,
InvalidState,
ManagementCall,
Missing,
Ok,
PendingCurrent,
PendingFresh,
PolicyDenied,
RegistryMissing,
ReleasedStale,
ResumedPending,
RoleMismatch,
StaleCleanup,
StaleRepairable,
Unknown,
}
impl PlacementBindingMetricReason {
#[must_use]
pub const fn metric_label(self) -> &'static str {
match self {
Self::AlreadyBound => "already_bound",
Self::ClaimLost => "claim_lost",
Self::Claimed => "claimed",
Self::InvalidChild => "invalid_child",
Self::InvalidState => "invalid_state",
Self::ManagementCall => "management_call",
Self::Missing => "missing",
Self::Ok => "ok",
Self::PendingCurrent => "pending_current",
Self::PendingFresh => "pending_fresh",
Self::PolicyDenied => "policy_denied",
Self::RegistryMissing => "registry_missing",
Self::ReleasedStale => "released_stale",
Self::ResumedPending => "resumed_pending",
Self::RoleMismatch => "role_mismatch",
Self::StaleCleanup => "stale_cleanup",
Self::StaleRepairable => "stale_repairable",
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::Ops, InternalErrorOrigin::Ops)
| (InternalErrorClass::Invariant | InternalErrorClass::Workflow, _) => {
Self::InvalidState
}
_ => Self::Unknown,
}
}
}
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub struct PlacementBindingMetricKey {
pub operation: PlacementBindingMetricOperation,
pub outcome: PlacementBindingMetricOutcome,
pub reason: PlacementBindingMetricReason,
}
pub struct PlacementBindingMetrics;
impl PlacementBindingMetrics {
pub fn record(
operation: PlacementBindingMetricOperation,
outcome: PlacementBindingMetricOutcome,
reason: PlacementBindingMetricReason,
) {
PLACEMENT_BINDING_METRICS.with_borrow_mut(|counts| {
let key = PlacementBindingMetricKey {
operation,
outcome,
reason,
};
let entry = counts.entry(key).or_insert(0);
*entry = entry.saturating_add(1);
});
}
#[must_use]
pub fn snapshot() -> Vec<(PlacementBindingMetricKey, u64)> {
PLACEMENT_BINDING_METRICS
.with_borrow(std::clone::Clone::clone)
.into_iter()
.collect()
}
#[cfg(test)]
pub fn reset() {
PLACEMENT_BINDING_METRICS.with_borrow_mut(HashMap::clear);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn snapshot_map() -> HashMap<PlacementBindingMetricKey, u64> {
PlacementBindingMetrics::snapshot().into_iter().collect()
}
#[test]
fn binding_metrics_accumulate_by_operation_outcome_and_reason() {
PlacementBindingMetrics::reset();
PlacementBindingMetrics::record(
PlacementBindingMetricOperation::Resolve,
PlacementBindingMetricOutcome::Started,
PlacementBindingMetricReason::Ok,
);
PlacementBindingMetrics::record(
PlacementBindingMetricOperation::Classify,
PlacementBindingMetricOutcome::Completed,
PlacementBindingMetricReason::PendingFresh,
);
PlacementBindingMetrics::record(
PlacementBindingMetricOperation::Classify,
PlacementBindingMetricOutcome::Completed,
PlacementBindingMetricReason::PendingFresh,
);
let map = snapshot_map();
assert_eq!(
map.get(&PlacementBindingMetricKey {
operation: PlacementBindingMetricOperation::Resolve,
outcome: PlacementBindingMetricOutcome::Started,
reason: PlacementBindingMetricReason::Ok,
}),
Some(&1)
);
assert_eq!(
map.get(&PlacementBindingMetricKey {
operation: PlacementBindingMetricOperation::Classify,
outcome: PlacementBindingMetricOutcome::Completed,
reason: PlacementBindingMetricReason::PendingFresh,
}),
Some(&2)
);
}
}