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}