mirror of
https://github.com/telemt/telemt.git
synced 2026-10-11 03:45:58 +03:00
20a4d50524
Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
541 lines
21 KiB
Rust
541 lines
21 KiB
Rust
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<Self> {
|
|
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<Self>) -> 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<WebRejectionCounter> {
|
|
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<WebOutcomeCounter> {
|
|
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<WebOutcomeCounter> {
|
|
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<WebTelemetry>,
|
|
}
|
|
|
|
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;
|