Skip to main content

velo_ext/
observability.rs

1// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Transport observability extension surface.
5//!
6//! Out-of-tree transport implementors integrate with the runtime's metrics
7//! pipeline through the [`TransportObservability`] trait. The runtime hands
8//! each transport a pre-bound observability handle via
9//! [`Transport::set_observability`](crate::transport::Transport::set_observability);
10//! the transport then calls [`record_frame`](TransportObservability::record_frame),
11//! [`record_rejection`](TransportObservability::record_rejection), etc. to
12//! emit data into the same `velo_transport_*` metric series as the in-tree
13//! transports — without depending on the runtime crate or its concrete
14//! metrics implementation.
15
16/// Direction of a transport frame.
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub enum Direction {
19    /// Inbound frame (received from peer).
20    Inbound,
21    /// Outbound frame (sent to peer).
22    Outbound,
23}
24
25impl Direction {
26    /// Prometheus label value.
27    pub fn as_str(self) -> &'static str {
28        match self {
29            Self::Inbound => "inbound",
30            Self::Outbound => "outbound",
31        }
32    }
33}
34
35/// Reason a transport rejected or dropped a frame.
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub enum TransportRejection {
38    /// Send operation failed.
39    SendError,
40    /// Message rejected during graceful drain.
41    DrainRejected,
42    /// Frame decode or preamble parse failed.
43    DecodeError,
44    /// Failed to route frame to adapter channel.
45    RouteFailed,
46    /// NATS message missing required headers.
47    MissingHeaders,
48    /// Velo-Type header missing (NATS).
49    MissingType,
50    /// Invalid Velo-Type value (NATS).
51    InvalidType,
52    /// Invalid Velo-HLen header (NATS).
53    InvalidHeaderLength,
54    /// Frame shorter than declared header length (NATS).
55    TruncatedFrame,
56    /// Failed to build ShuttingDown response (gRPC).
57    DrainReplyBuildFailed,
58}
59
60impl TransportRejection {
61    /// Prometheus label value.
62    pub fn as_str(self) -> &'static str {
63        match self {
64            Self::SendError => "send_error",
65            Self::DrainRejected => "drain_rejected",
66            Self::DecodeError => "decode_error",
67            Self::RouteFailed => "route_failed",
68            Self::MissingHeaders => "missing_headers",
69            Self::MissingType => "missing_type",
70            Self::InvalidType => "invalid_type",
71            Self::InvalidHeaderLength => "invalid_header_length",
72            Self::TruncatedFrame => "truncated_frame",
73            Self::DrainReplyBuildFailed => "drain_reply_build_failed",
74        }
75    }
76}
77
78/// Observability hook handed to a [`Transport`](crate::transport::Transport)
79/// during [`set_observability`](crate::transport::Transport::set_observability).
80///
81/// Implementors of `Transport` invoke these methods to publish metrics into
82/// the runtime's collectors. The runtime's concrete handle pre-binds the
83/// transport's `key` label so the trait surface stays free of label-management
84/// concerns.
85///
86/// All methods take `&self` and have implementations that are typically lock-free
87/// — they are safe to call from any hot path. Default impls are intentionally
88/// not provided: every method represents an observable signal a real
89/// implementation would care about.
90pub trait TransportObservability: Send + Sync {
91    /// Record an accepted frame.
92    ///
93    /// `message_type` is one of the well-known
94    /// [`MessageType`](crate::transport::MessageType) label strings:
95    /// `"message"`, `"response"`, `"ack"`, `"event"`, or `"shutting_down"`.
96    fn record_frame(&self, direction: Direction, message_type: &str, bytes: usize);
97
98    /// Record a rejected or dropped frame.
99    fn record_rejection(&self, reason: TransportRejection);
100
101    /// Set the gauge for the number of registered peers on this transport.
102    fn set_registered_peers(&self, count: usize);
103
104    /// Set the gauge for the number of active connections on this transport.
105    fn set_active_connections(&self, count: usize);
106
107    /// Record a send that found the bounded per-target channel full and was
108    /// queued in the target's [`AdmissionGate`](crate::admission::AdmissionGate)
109    /// instead of admitted on the spot.
110    fn record_send_backpressure(&self);
111}