Files
telemt/src/web/telemetry/lifecycle.rs
T
2026-09-04 19:01:56 +03:00

224 lines
7.8 KiB
Rust

use std::sync::atomic::{AtomicU64, Ordering};
use serde::Serialize;
use crate::config::WebCarrier;
use crate::web::session::SessionCloseReason;
use super::WebTelemetry;
pub(super) const SESSION_CLOSE_SLOTS: usize = WebCarrier::ALL.len() * SessionCloseReason::ALL.len();
pub(super) const SESSION_OBSERVATION_SLOTS: usize =
WebCarrier::ALL.len() * WebSessionLifecycleObservation::ALL.len();
/// Stable authenticated session lifecycle observation.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(usize)]
pub(crate) enum WebSessionLifecycleObservation {
/// A valid HTTP carrier request resumed after a suspicious peer gap.
HttpActivityAfterGap,
/// A valid WebSocket message resumed after a suspicious peer gap.
WebSocketActivityAfterGap,
/// A valid retained bearer was used after its session closed.
RequestAfterClose,
}
impl WebSessionLifecycleObservation {
/// Complete fixed observation set in stable metric order.
pub(crate) const ALL: [Self; 3] = [
Self::HttpActivityAfterGap,
Self::WebSocketActivityAfterGap,
Self::RequestAfterClose,
];
/// Returns the stable API and Prometheus token.
pub(crate) const fn as_str(self) -> &'static str {
match self {
Self::HttpActivityAfterGap => "http_activity_after_gap",
Self::WebSocketActivityAfterGap => "websocket_activity_after_gap",
Self::RequestAfterClose => "request_after_close",
}
}
}
/// Stable server-side milestone for one bridge recovery incarnation.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(usize)]
pub(crate) enum WebBridgeRecoveryEvent {
/// A recovery request received a fresh bootstrap.
BootstrapIssued,
/// A fresh recovery session was registered.
SessionCreated,
/// The recovery session committed a real carrier.
Committed,
/// An issued recovery bootstrap expired without session creation.
ExpiredUnused,
/// A recovery session closed before carrier commit.
ClosedBeforeCommit,
}
impl WebBridgeRecoveryEvent {
/// Complete fixed recovery event set in stable metric order.
pub(crate) const ALL: [Self; 5] = [
Self::BootstrapIssued,
Self::SessionCreated,
Self::Committed,
Self::ExpiredUnused,
Self::ClosedBeforeCommit,
];
/// Returns the stable API and Prometheus token.
pub(crate) const fn as_str(self) -> &'static str {
match self {
Self::BootstrapIssued => "bootstrap_issued",
Self::SessionCreated => "session_created",
Self::Committed => "committed",
Self::ExpiredUnused => "expired_unused",
Self::ClosedBeforeCommit => "closed_before_commit",
}
}
}
/// API-safe typed session-close counter.
#[derive(Clone, Serialize)]
pub(crate) struct WebSessionCloseCounter {
/// Fixed carrier owning the closed incarnation.
pub(crate) carrier: &'static str,
/// Fixed terminal close cause.
pub(crate) reason: &'static str,
/// Monotonic process-lifetime count.
pub(crate) total: u64,
}
/// API-safe typed lifecycle-observation counter.
#[derive(Clone, Serialize)]
pub(crate) struct WebSessionLifecycleObservationCounter {
/// Fixed carrier associated with the observation.
pub(crate) carrier: &'static str,
/// Fixed lifecycle observation.
pub(crate) observation: &'static str,
/// Monotonic process-lifetime count.
pub(crate) total: u64,
}
/// API-safe typed bridge-recovery counter.
#[derive(Clone, Serialize)]
pub(crate) struct WebBridgeRecoveryCounter {
/// Fixed recovery milestone.
pub(crate) event: &'static str,
/// Monotonic process-lifetime count.
pub(crate) total: u64,
}
pub(super) const fn session_close_slot(carrier: WebCarrier, reason: SessionCloseReason) -> usize {
carrier.index() * SessionCloseReason::ALL.len() + reason as usize
}
pub(super) const fn session_observation_slot(
carrier: WebCarrier,
observation: WebSessionLifecycleObservation,
) -> usize {
carrier.index() * WebSessionLifecycleObservation::ALL.len() + observation as usize
}
pub(super) fn load(counter: &AtomicU64) -> u64 {
counter.load(Ordering::Relaxed)
}
impl WebTelemetry {
/// Records one closed session incarnation and its exact terminal cause.
pub(crate) fn record_session_closed(&self, carrier: WebCarrier, reason: SessionCloseReason) {
self.sessions_closed.fetch_add(1, Ordering::Relaxed);
self.session_closures[session_close_slot(carrier, reason)].fetch_add(1, Ordering::Relaxed);
}
/// Returns one fixed session-close counter.
pub(crate) fn session_close_total(
&self,
carrier: WebCarrier,
reason: SessionCloseReason,
) -> u64 {
load(&self.session_closures[session_close_slot(carrier, reason)])
}
/// Captures the complete fixed session-close matrix for API serialization.
pub(crate) fn session_close_counters(&self) -> Vec<WebSessionCloseCounter> {
WebCarrier::ALL
.into_iter()
.flat_map(|carrier| {
SessionCloseReason::ALL
.into_iter()
.map(move |reason| WebSessionCloseCounter {
carrier: carrier.as_str(),
reason: reason.as_str(),
total: self.session_close_total(carrier, reason),
})
})
.collect()
}
/// Records one fixed authenticated lifecycle observation.
pub(crate) fn record_session_observation(
&self,
carrier: WebCarrier,
observation: WebSessionLifecycleObservation,
) {
self.session_observations[session_observation_slot(carrier, observation)]
.fetch_add(1, Ordering::Relaxed);
}
/// Returns one fixed authenticated lifecycle observation counter.
pub(crate) fn session_observation_total(
&self,
carrier: WebCarrier,
observation: WebSessionLifecycleObservation,
) -> u64 {
load(&self.session_observations[session_observation_slot(carrier, observation)])
}
/// Captures the complete fixed lifecycle-observation matrix.
pub(crate) fn session_observation_counters(
&self,
) -> Vec<WebSessionLifecycleObservationCounter> {
WebCarrier::ALL
.into_iter()
.flat_map(|carrier| {
WebSessionLifecycleObservation::ALL
.into_iter()
.map(move |observation| WebSessionLifecycleObservationCounter {
carrier: carrier.as_str(),
observation: observation.as_str(),
total: self.session_observation_total(carrier, observation),
})
})
.collect()
}
/// Records one fixed bridge-recovery milestone.
pub(crate) fn record_bridge_recovery(&self, event: WebBridgeRecoveryEvent) {
self.bridge_recovery_events[event as usize].fetch_add(1, Ordering::Relaxed);
}
/// Adds a bounded batch of identical recovery milestones.
pub(crate) fn record_bridge_recovery_count(&self, event: WebBridgeRecoveryEvent, count: usize) {
self.bridge_recovery_events[event as usize]
.fetch_add(u64::try_from(count).unwrap_or(u64::MAX), Ordering::Relaxed);
}
/// Returns one fixed bridge-recovery counter.
pub(crate) fn bridge_recovery_total(&self, event: WebBridgeRecoveryEvent) -> u64 {
load(&self.bridge_recovery_events[event as usize])
}
/// Captures the complete fixed bridge-recovery event set.
pub(crate) fn bridge_recovery_counters(&self) -> Vec<WebBridgeRecoveryCounter> {
WebBridgeRecoveryEvent::ALL
.into_iter()
.map(|event| WebBridgeRecoveryCounter {
event: event.as_str(),
total: self.bridge_recovery_total(event),
})
.collect()
}
}