use std::sync::Arc; use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering}; use std::time::Instant; use serde::Serialize; mod carrier; use carrier::{CARRIER_FAILURE_SLOTS, CARRIER_LEARNING_SLOTS, CARRIER_SELECTION_SLOTS}; pub(crate) use carrier::{ WebCarrierFailureCounter, WebCarrierFailurePhase, WebCarrierLearningCounter, WebCarrierLearningOutcome, WebCarrierSelectionCounter, WebCarrierSelectionDisposition, }; // Decoy fast-track counters retain a fixed process-owned disposition set. mod fasttrack; use fasttrack::DECOY_FASTTRACK_SLOTS; pub(crate) use fasttrack::{WebDecoyFastTrackCounter, WebDecoyFastTrackDisposition}; mod lifecycle; use lifecycle::{SESSION_CLOSE_SLOTS, SESSION_OBSERVATION_SLOTS}; pub(crate) use lifecycle::{ WebBridgeRecoveryCounter, WebBridgeRecoveryEvent, WebSessionCloseCounter, WebSessionLifecycleObservation, WebSessionLifecycleObservationCounter, }; const LAST_DECOY_OUTCOME_BITS: u32 = 4; const LAST_DECOY_OUTCOME_MASK: u64 = (1 << LAST_DECOY_OUTCOME_BITS) - 1; const LAST_DECOY_ELAPSED_MAX: u64 = u64::MAX >> LAST_DECOY_OUTCOME_BITS; /// Stable operational rejection reason recorded at the decision point. #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(usize)] pub(crate) enum WebRejectionReason { /// Ordinary accepted HTTP connection slots were exhausted. HttpConnectionCapacity, /// Concurrent HTTP request handler slots were exhausted. HttpHandlerCapacity, /// Primary long-poll slots were exhausted. LanePollCapacity, /// Auxiliary lane-open wait slots were exhausted. LaneAuxPollCapacity, /// Concurrent collected-body reader slots were exhausted. BodyReaderCapacity, /// Process-wide collected-body bytes were exhausted. BodyBytesCapacity, /// Bootstrap credential quota or identifier space was exhausted. BootstrapCapacity, /// Bootstrap issuance rate policy rejected the operation. BootstrapRate, /// Live session quota or identifier space was exhausted. SessionCapacity, /// Session creation rate policy rejected the operation. SessionRate, /// One session reached its logical-stream ceiling. StreamSessionCapacity, /// Global or profile logical-stream capacity was exhausted. StreamCapacity, /// Logical-stream creation rate policy rejected the operation. StreamRate, /// No synthetic source port remained for the exact relay tuple. StreamTupleExhausted, /// Concurrent inner-handshake slots were exhausted. StreamHandshakeCapacity, /// The active relay generation had no connection permit. GenerationConnectionCapacity, /// One session reached its queued data ceiling. QueueSessionCapacity, /// The process-wide queued data ceiling was exhausted. QueueGlobalCapacity, /// Process-wide WebSocket connection slots were exhausted. WebSocketConnectionCapacity, /// Shared WebSocket byte capacity was exhausted. WebSocketBytesCapacity, /// Bounded WebSocket replacement capacity was exhausted. WebSocketEvictionCapacity, /// Effective WEB configuration disabled new work. ConfigDisabled, /// Effective user policy disabled new work. UserDisabled, /// Operator admission was paused. OperatorPaused, /// Operator admission was gracefully draining. OperatorDraining, /// Operator drain had committed forced close signals. OperatorForceClosing, /// Operator drain had reached confirmed zero. OperatorDrained, /// The active relay generation closed new admission. GenerationAdmissionClosed, /// The active relay generation could not own a new task. GenerationScopeClosed, /// Terminal runtime closure rejected the operation. RuntimeClosed, /// A bounded operation could not acquire or complete its deadline. Deadline, /// Concurrent ownership rejected the operation. Concurrent, } impl WebRejectionReason { /// Complete fixed rejection set in stable metric order. pub(crate) const ALL: [Self; 32] = [ Self::HttpConnectionCapacity, Self::HttpHandlerCapacity, Self::LanePollCapacity, Self::LaneAuxPollCapacity, Self::BodyReaderCapacity, Self::BodyBytesCapacity, Self::BootstrapCapacity, Self::BootstrapRate, Self::SessionCapacity, Self::SessionRate, Self::StreamSessionCapacity, Self::StreamCapacity, Self::StreamRate, Self::StreamTupleExhausted, Self::StreamHandshakeCapacity, Self::GenerationConnectionCapacity, Self::QueueSessionCapacity, Self::QueueGlobalCapacity, Self::WebSocketConnectionCapacity, Self::WebSocketBytesCapacity, Self::WebSocketEvictionCapacity, Self::ConfigDisabled, Self::UserDisabled, Self::OperatorPaused, Self::OperatorDraining, Self::OperatorForceClosing, Self::OperatorDrained, Self::GenerationAdmissionClosed, Self::GenerationScopeClosed, Self::RuntimeClosed, Self::Deadline, Self::Concurrent, ]; /// Returns the stable API and Prometheus label token. pub(crate) const fn as_str(self) -> &'static str { match self { Self::HttpConnectionCapacity => "http_connection_capacity", Self::HttpHandlerCapacity => "http_handler_capacity", Self::LanePollCapacity => "lane_poll_capacity", Self::LaneAuxPollCapacity => "lane_aux_poll_capacity", Self::BodyReaderCapacity => "body_reader_capacity", Self::BodyBytesCapacity => "body_bytes_capacity", Self::BootstrapCapacity => "bootstrap_capacity", Self::BootstrapRate => "bootstrap_rate", Self::SessionCapacity => "session_capacity", Self::SessionRate => "session_rate", Self::StreamSessionCapacity => "stream_session_capacity", Self::StreamCapacity => "stream_capacity", Self::StreamRate => "stream_rate", Self::StreamTupleExhausted => "stream_tuple_exhausted", Self::StreamHandshakeCapacity => "stream_handshake_capacity", Self::GenerationConnectionCapacity => "generation_connection_capacity", Self::QueueSessionCapacity => "queue_session_capacity", Self::QueueGlobalCapacity => "queue_global_capacity", Self::WebSocketConnectionCapacity => "websocket_connection_capacity", Self::WebSocketBytesCapacity => "websocket_bytes_capacity", Self::WebSocketEvictionCapacity => "websocket_eviction_capacity", Self::ConfigDisabled => "config_disabled", Self::UserDisabled => "user_disabled", Self::OperatorPaused => "operator_paused", Self::OperatorDraining => "operator_draining", Self::OperatorForceClosing => "operator_force_closing", Self::OperatorDrained => "operator_drained", Self::GenerationAdmissionClosed => "generation_admission_closed", Self::GenerationScopeClosed => "generation_scope_closed", Self::RuntimeClosed => "runtime_closed", Self::Deadline => "deadline", Self::Concurrent => "concurrent", } } } /// Terminal result for one accepted socket that found normal HTTP capacity exhausted. #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(usize)] pub(crate) enum WebHttpConnectionOverloadOutcome { /// Legacy policy closed the accepted socket immediately. Dropped, /// Bounded waiting acquired ordinary connection capacity. WaitAdmitted, /// Bounded waiting expired and emitted the retryable response. WaitTimeout503, /// Immediate response policy emitted the retryable response. Responded503, /// The bounded overload socket pool was already full. OverflowCapacityDrop, /// Writing or closing the retryable response failed. ResponseErrorDrop, /// Listener or process shutdown cancelled overload handling. ShutdownDrop, } impl WebHttpConnectionOverloadOutcome { /// Complete fixed accepted-socket outcome set in stable metric order. pub(crate) const ALL: [Self; 7] = [ Self::Dropped, Self::WaitAdmitted, Self::WaitTimeout503, Self::Responded503, Self::OverflowCapacityDrop, Self::ResponseErrorDrop, Self::ShutdownDrop, ]; /// Returns the stable API and Prometheus label token. pub(crate) const fn as_str(self) -> &'static str { match self { Self::Dropped => "dropped", Self::WaitAdmitted => "wait_admitted", Self::WaitTimeout503 => "wait_timeout_503", Self::Responded503 => "responded_503", Self::OverflowCapacityDrop => "overflow_capacity_drop", Self::ResponseErrorDrop => "response_error_drop", Self::ShutdownDrop => "shutdown_drop", } } } /// Passive outcome for one plain-HTTP decoy upstream request. #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(usize)] pub(crate) enum WebDecoyUpstreamOutcome { /// The internal decoy origin produced a response head. Success, /// The request activity plane could not grant a deadline lease. DeadlineExhausted, /// The configured origin actively refused the TCP connection. ConnectRefused, /// The bounded TCP connect phase timed out. ConnectTimeout, /// The TCP connect failed for another I/O reason. ConnectError, /// The bounded HTTP client handshake timed out. HttpHandshakeTimeout, /// The HTTP client handshake failed. HttpHandshakeError, /// The origin did not produce a response head before the deadline. ResponseHeadTimeout, /// URI preparation or the HTTP request failed. RequestError, } impl WebDecoyUpstreamOutcome { /// Complete fixed decoy-origin outcome set in stable metric order. pub(crate) const ALL: [Self; 9] = [ Self::Success, Self::DeadlineExhausted, Self::ConnectRefused, Self::ConnectTimeout, Self::ConnectError, Self::HttpHandshakeTimeout, Self::HttpHandshakeError, Self::ResponseHeadTimeout, Self::RequestError, ]; /// Returns the stable API and Prometheus label token. pub(crate) const fn as_str(self) -> &'static str { match self { Self::Success => "success", Self::DeadlineExhausted => "deadline_exhausted", Self::ConnectRefused => "connect_refused", Self::ConnectTimeout => "connect_timeout", Self::ConnectError => "connect_error", Self::HttpHandshakeTimeout => "http_handshake_timeout", Self::HttpHandshakeError => "http_handshake_error", Self::ResponseHeadTimeout => "response_head_timeout", Self::RequestError => "request_error", } } } /// API-safe typed rejection counter. #[derive(Clone, Serialize)] pub(crate) struct WebRejectionCounter { /// Stable closed-set rejection token. pub(crate) reason: &'static str, /// Monotonic process-lifetime count. pub(crate) total: u64, } /// API-safe typed outcome counter. #[derive(Clone, Serialize)] pub(crate) struct WebOutcomeCounter { /// Stable closed-set outcome token. pub(crate) outcome: &'static str, /// Monotonic process-lifetime count. pub(crate) total: u64, } /// Existing aggregate WEB totals retained with their original semantics. #[derive(Clone, Copy)] pub(crate) struct WebAggregateSnapshot { /// Created session incarnations. pub(crate) sessions_created: u64, /// Closed session incarnations. pub(crate) sessions_closed: u64, /// Admitted logical streams. pub(crate) streams_opened: u64, /// Rejected logical streams covered by the legacy aggregate. pub(crate) streams_rejected: u64, /// Accepted carrier uplink payload bytes. pub(crate) bytes_up: u64, /// Emitted carrier downlink payload bytes. pub(crate) bytes_down: u64, /// Legacy aggregate capacity-hit count. pub(crate) limit_hits: u64, } /// Process-owned operational counters shared by ingress, API, and metrics. pub(crate) struct WebTelemetry { started: Instant, live_acceptors: AtomicUsize, accepted: AtomicU64, accept_errors: AtomicU64, rejections: [AtomicU64; WebRejectionReason::ALL.len()], overload_outcomes: [AtomicU64; WebHttpConnectionOverloadOutcome::ALL.len()], decoy_outcomes: [AtomicU64; WebDecoyUpstreamOutcome::ALL.len()], carrier_selections: [AtomicU64; CARRIER_SELECTION_SLOTS], carrier_failures: [AtomicU64; CARRIER_FAILURE_SLOTS], carrier_learning_outcomes: [AtomicU64; CARRIER_LEARNING_SLOTS], decoy_fasttrack_requests: [AtomicU64; DECOY_FASTTRACK_SLOTS], session_closures: [AtomicU64; SESSION_CLOSE_SLOTS], session_observations: [AtomicU64; SESSION_OBSERVATION_SLOTS], bridge_recovery_events: [AtomicU64; WebBridgeRecoveryEvent::ALL.len()], last_decoy: AtomicU64, sessions_created: AtomicU64, sessions_closed: AtomicU64, streams_opened: AtomicU64, streams_rejected: AtomicU64, bytes_up: AtomicU64, bytes_down: AtomicU64, limit_hits: AtomicU64, } impl WebTelemetry { /// Creates zeroed process-lifetime WEB telemetry. pub(crate) fn new() -> Arc { Arc::new(Self { started: Instant::now(), live_acceptors: AtomicUsize::new(0), accepted: AtomicU64::new(0), accept_errors: AtomicU64::new(0), rejections: std::array::from_fn(|_| AtomicU64::new(0)), overload_outcomes: std::array::from_fn(|_| AtomicU64::new(0)), decoy_outcomes: std::array::from_fn(|_| AtomicU64::new(0)), carrier_selections: std::array::from_fn(|_| AtomicU64::new(0)), carrier_failures: std::array::from_fn(|_| AtomicU64::new(0)), carrier_learning_outcomes: std::array::from_fn(|_| AtomicU64::new(0)), decoy_fasttrack_requests: std::array::from_fn(|_| AtomicU64::new(0)), session_closures: std::array::from_fn(|_| AtomicU64::new(0)), session_observations: std::array::from_fn(|_| AtomicU64::new(0)), bridge_recovery_events: std::array::from_fn(|_| AtomicU64::new(0)), last_decoy: AtomicU64::new(0), sessions_created: AtomicU64::new(0), sessions_closed: AtomicU64::new(0), streams_opened: AtomicU64::new(0), streams_rejected: AtomicU64::new(0), bytes_up: AtomicU64::new(0), bytes_down: AtomicU64::new(0), limit_hits: AtomicU64::new(0), }) } /// Registers one live accept loop before its task can be cancelled unpolled. pub(crate) fn acceptor_guard(self: &Arc) -> WebAcceptorGuard { self.live_acceptors.fetch_add(1, Ordering::AcqRel); WebAcceptorGuard { telemetry: Arc::clone(self), } } /// Returns currently registered WEB accept loops. pub(crate) fn live_acceptors(&self) -> usize { self.live_acceptors.load(Ordering::Acquire) } /// Records one successfully accepted WEB socket. pub(crate) fn record_accept(&self) { self.accepted.fetch_add(1, Ordering::Relaxed); } /// Returns successfully accepted WEB sockets. pub(crate) fn accepted(&self) -> u64 { self.accepted.load(Ordering::Relaxed) } /// Records one WEB listener `accept` error. pub(crate) fn record_accept_error(&self) { self.accept_errors.fetch_add(1, Ordering::Relaxed); } /// Returns WEB listener `accept` errors. pub(crate) fn accept_errors(&self) -> u64 { self.accept_errors.load(Ordering::Relaxed) } /// Records one operational rejection at its admission decision point. pub(crate) fn record_rejection(&self, reason: WebRejectionReason) { self.rejections[reason as usize].fetch_add(1, Ordering::Relaxed); } /// Returns one fixed rejection counter. pub(crate) fn rejection_total(&self, reason: WebRejectionReason) -> u64 { self.rejections[reason as usize].load(Ordering::Relaxed) } /// Captures the complete fixed rejection set for API serialization. pub(crate) fn rejection_counters(&self) -> Vec { WebRejectionReason::ALL .into_iter() .map(|reason| WebRejectionCounter { reason: reason.as_str(), total: self.rejection_total(reason), }) .collect() } /// Records one terminal accepted-socket overload outcome. pub(crate) fn record_overload(&self, outcome: WebHttpConnectionOverloadOutcome) { self.overload_outcomes[outcome as usize].fetch_add(1, Ordering::Relaxed); } /// Returns one fixed accepted-socket overload counter. pub(crate) fn overload_total(&self, outcome: WebHttpConnectionOverloadOutcome) -> u64 { self.overload_outcomes[outcome as usize].load(Ordering::Relaxed) } /// Captures the complete fixed overload outcome set for API serialization. pub(crate) fn overload_counters(&self) -> Vec { WebHttpConnectionOverloadOutcome::ALL .into_iter() .map(|outcome| WebOutcomeCounter { outcome: outcome.as_str(), total: self.overload_total(outcome), }) .collect() } /// Records one internal plain-HTTP decoy origin outcome. pub(crate) fn record_decoy(&self, outcome: WebDecoyUpstreamOutcome) { self.decoy_outcomes[outcome as usize].fetch_add(1, Ordering::Relaxed); let elapsed_ms = self .started .elapsed() .as_millis() .min(u128::from(LAST_DECOY_ELAPSED_MAX)) as u64; let packed = (elapsed_ms << LAST_DECOY_OUTCOME_BITS) | (outcome as u64 + 1); self.last_decoy.store(packed, Ordering::Release); } /// Returns one fixed internal decoy origin counter. pub(crate) fn decoy_total(&self, outcome: WebDecoyUpstreamOutcome) -> u64 { self.decoy_outcomes[outcome as usize].load(Ordering::Relaxed) } /// Captures the complete fixed decoy outcome set for API serialization. pub(crate) fn decoy_counters(&self) -> Vec { WebDecoyUpstreamOutcome::ALL .into_iter() .map(|outcome| WebOutcomeCounter { outcome: outcome.as_str(), total: self.decoy_total(outcome), }) .collect() } /// Returns the last decoy outcome and its monotonic age in milliseconds. pub(crate) fn last_decoy(&self) -> Option<(&'static str, u64)> { let packed = self.last_decoy.load(Ordering::Acquire); let outcome_index = (packed & LAST_DECOY_OUTCOME_MASK).checked_sub(1)? as usize; let outcome = WebDecoyUpstreamOutcome::ALL.get(outcome_index).copied()?; let recorded = packed >> LAST_DECOY_OUTCOME_BITS; let now = self .started .elapsed() .as_millis() .min(u128::from(LAST_DECOY_ELAPSED_MAX)) as u64; Some((outcome.as_str(), now.saturating_sub(recorded))) } /// Records one created session incarnation. pub(crate) fn record_session_created(&self) { self.sessions_created.fetch_add(1, Ordering::Relaxed); } /// Records one admitted logical stream. pub(crate) fn record_stream_opened(&self) { self.streams_opened.fetch_add(1, Ordering::Relaxed); } /// Records one logical stream in the legacy rejection aggregate. pub(crate) fn record_stream_rejected(&self) { self.streams_rejected.fetch_add(1, Ordering::Relaxed); } /// Adds accepted carrier uplink payload bytes. pub(crate) fn record_up(&self, bytes: usize) { self.bytes_up.fetch_add(bytes as u64, Ordering::Relaxed); } /// Adds emitted carrier downlink payload bytes. pub(crate) fn record_down(&self, bytes: usize) { self.bytes_down.fetch_add(bytes as u64, Ordering::Relaxed); } /// Records one legacy aggregate capacity hit. pub(crate) fn record_limit_hit(&self) { self.limit_hits.fetch_add(1, Ordering::Relaxed); } /// Captures legacy process-owned aggregate totals. pub(crate) fn aggregates(&self) -> WebAggregateSnapshot { WebAggregateSnapshot { sessions_created: self.sessions_created.load(Ordering::Relaxed), sessions_closed: self.sessions_closed.load(Ordering::Relaxed), streams_opened: self.streams_opened.load(Ordering::Relaxed), streams_rejected: self.streams_rejected.load(Ordering::Relaxed), bytes_up: self.bytes_up.load(Ordering::Relaxed), bytes_down: self.bytes_down.load(Ordering::Relaxed), limit_hits: self.limit_hits.load(Ordering::Relaxed), } } } /// Cancellation-safe live-acceptor registration. pub(crate) struct WebAcceptorGuard { telemetry: Arc, } impl Drop for WebAcceptorGuard { fn drop(&mut self) { self.telemetry.live_acceptors.fetch_sub(1, Ordering::AcqRel); } } #[cfg(test)] #[path = "telemetry/tests.rs"] mod tests;