mod attestation;
mod sessions;
use std::{cell::RefCell, collections::HashMap};
pub use attestation::{record_attestation_epoch_rejected, record_attestation_verify_failed};
pub use sessions::{
record_application_session_cleanup, record_application_session_clear,
record_application_session_created, record_application_session_establishment_started,
record_application_session_expired_observation,
record_application_session_generation_invalidation, record_application_session_idempotent,
record_application_session_rejected, record_application_session_replaced,
};
thread_local! {
static AUTH_METRICS: RefCell<HashMap<AuthMetricKey, u64>> = RefCell::new(HashMap::new());
}
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[remain::sorted]
pub enum AuthMetricSurface {
ApplicationSession,
Attestation,
}
impl AuthMetricSurface {
#[must_use]
pub const fn metric_label(self) -> &'static str {
match self {
Self::ApplicationSession => "application_session",
Self::Attestation => "attestation",
}
}
}
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[remain::sorted]
pub enum AuthMetricOperation {
Cleanup,
Clear,
Establish,
ExpiryObservation,
GenerationInvalidation,
Verify,
}
impl AuthMetricOperation {
#[must_use]
pub const fn metric_label(self) -> &'static str {
match self {
Self::Cleanup => "cleanup",
Self::Clear => "clear",
Self::Establish => "establish",
Self::ExpiryObservation => "expiry_observation",
Self::GenerationInvalidation => "generation_invalidation",
Self::Verify => "verify",
}
}
}
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[remain::sorted]
pub enum AuthMetricOutcome {
Completed,
Failed,
Idempotent,
Rejected,
Started,
}
impl AuthMetricOutcome {
#[must_use]
pub const fn metric_label(self) -> &'static str {
match self {
Self::Completed => "completed",
Self::Failed => "failed",
Self::Idempotent => "idempotent",
Self::Rejected => "rejected",
Self::Started => "started",
}
}
}
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[remain::sorted]
pub enum AuthMetricReason {
AuthorityConflict,
Capacity,
Cleared,
Created,
EpochRejected,
Expired,
GenerationAdvanced,
InvalidRequest,
ProofInvalid,
Replaced,
Replay,
ReplayConflict,
Request,
StateUnavailable,
VerifyFailed,
}
impl AuthMetricReason {
#[must_use]
pub const fn metric_label(self) -> &'static str {
match self {
Self::AuthorityConflict => "authority_conflict",
Self::Capacity => "capacity",
Self::Cleared => "cleared",
Self::Created => "created",
Self::EpochRejected => "epoch_rejected",
Self::Expired => "expired",
Self::GenerationAdvanced => "generation_advanced",
Self::InvalidRequest => "invalid_request",
Self::ProofInvalid => "proof_invalid",
Self::Replaced => "replaced",
Self::Replay => "replay",
Self::ReplayConflict => "replay_conflict",
Self::Request => "request",
Self::StateUnavailable => "state_unavailable",
Self::VerifyFailed => "verify_failed",
}
}
}
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub struct AuthMetricKey {
pub surface: AuthMetricSurface,
pub operation: AuthMetricOperation,
pub outcome: AuthMetricOutcome,
pub reason: AuthMetricReason,
}
pub struct AuthMetrics;
impl AuthMetrics {
pub fn record(
surface: AuthMetricSurface,
operation: AuthMetricOperation,
outcome: AuthMetricOutcome,
reason: AuthMetricReason,
) {
AUTH_METRICS.with_borrow_mut(|counts| {
let key = AuthMetricKey {
surface,
operation,
outcome,
reason,
};
let entry = counts.entry(key).or_insert(0);
*entry = entry.saturating_add(1);
});
}
#[must_use]
pub fn snapshot() -> Vec<(AuthMetricKey, u64)> {
AUTH_METRICS
.with_borrow(std::clone::Clone::clone)
.into_iter()
.collect()
}
#[cfg(test)]
pub fn reset() {
AUTH_METRICS.with_borrow_mut(HashMap::clear);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn auth_metric_count(
surface: AuthMetricSurface,
operation: AuthMetricOperation,
outcome: AuthMetricOutcome,
reason: AuthMetricReason,
) -> u64 {
AuthMetrics::snapshot()
.into_iter()
.find_map(|(key, count)| {
if key.surface == surface
&& key.operation == operation
&& key.outcome == outcome
&& key.reason == reason
{
Some(count)
} else {
None
}
})
.unwrap_or(0)
}
fn assert_metric_count(
surface: AuthMetricSurface,
operation: AuthMetricOperation,
outcome: AuthMetricOutcome,
reason: AuthMetricReason,
expected: u64,
) {
assert_eq!(
auth_metric_count(surface, operation, outcome, reason),
expected
);
}
fn assert_session_metric_count(
operation: AuthMetricOperation,
outcome: AuthMetricOutcome,
reason: AuthMetricReason,
expected: u64,
) {
assert_metric_count(
AuthMetricSurface::ApplicationSession,
operation,
outcome,
reason,
expected,
);
}
#[test]
fn application_session_metrics_use_only_bounded_outcome_dimensions() {
AuthMetrics::reset();
record_application_session_establishment_started();
record_application_session_created();
record_application_session_replaced();
record_application_session_idempotent();
record_application_session_rejected(AuthMetricReason::Capacity);
record_application_session_clear(true);
record_application_session_clear(false);
record_application_session_expired_observation();
record_application_session_cleanup(2);
record_application_session_generation_invalidation();
assert_session_metric_count(
AuthMetricOperation::Establish,
AuthMetricOutcome::Started,
AuthMetricReason::Request,
1,
);
assert_session_metric_count(
AuthMetricOperation::Establish,
AuthMetricOutcome::Completed,
AuthMetricReason::Created,
1,
);
assert_session_metric_count(
AuthMetricOperation::Establish,
AuthMetricOutcome::Idempotent,
AuthMetricReason::Replay,
1,
);
assert_session_metric_count(
AuthMetricOperation::Cleanup,
AuthMetricOutcome::Completed,
AuthMetricReason::Expired,
2,
);
assert_session_metric_count(
AuthMetricOperation::Clear,
AuthMetricOutcome::Idempotent,
AuthMetricReason::Cleared,
1,
);
assert_session_metric_count(
AuthMetricOperation::GenerationInvalidation,
AuthMetricOutcome::Completed,
AuthMetricReason::GenerationAdvanced,
1,
);
}
#[test]
fn attestation_metrics_increment_expected_auth_dimensions() {
AuthMetrics::reset();
record_attestation_verify_failed();
record_attestation_epoch_rejected();
assert_metric_count(
AuthMetricSurface::Attestation,
AuthMetricOperation::Verify,
AuthMetricOutcome::Failed,
AuthMetricReason::VerifyFailed,
1,
);
assert_metric_count(
AuthMetricSurface::Attestation,
AuthMetricOperation::Verify,
AuthMetricOutcome::Failed,
AuthMetricReason::EpochRejected,
1,
);
}
}