-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Expand file tree
/
Copy pathmetrics.rs
More file actions
306 lines (275 loc) · 10.4 KB
/
Copy pathmetrics.rs
File metadata and controls
306 lines (275 loc) · 10.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
//! Metrics extension point for xds-client.
//!
//! Defines a framework-agnostic [`MetricsRecorder`] trait that backends implement
//! to receive metric measurements emitted by the client. Modeled after gRFC A79's
//! `MetricsRecorder` abstraction.
//!
//! A bundled OpenTelemetry implementation is available behind the `otel` Cargo
//! feature (see `OtelMetricsRecorder`). Consumers that use a different telemetry
//! framework can implement [`MetricsRecorder`] themselves.
//!
//! # Example
//!
//! ```ignore
//! use std::sync::Arc;
//! use xds_client::metrics::MetricsRecorder;
//!
//! struct MyRecorder;
//! impl MetricsRecorder for MyRecorder { /* ... */ }
//!
//! let recorder: Arc<dyn MetricsRecorder> = Arc::new(MyRecorder);
//! let client = XdsClient::builder(config, transport, codec, runtime)
//! .with_metrics_recorder(recorder)
//! .build();
//! ```
use std::borrow::Cow;
use std::fmt;
use std::sync::Arc;
#[cfg(feature = "otel")]
pub mod otel;
/// Static descriptor for a metric instrument.
///
/// One `Instrument` is declared per metric as a `pub static` constant. Call
/// sites reference instruments by `&'static Instrument`. Backend implementations
/// may use the instrument address as a cache key (`instrument as *const _`).
#[derive(Debug)]
pub struct Instrument {
/// Metric name (e.g. `"grpc.xds_client.connected"`).
pub name: &'static str,
/// Human-readable description.
pub description: &'static str,
/// OpenTelemetry unit notation (e.g. `"s"`, `"By"`, `"{bool}"`, `"{resource}"`).
pub unit: &'static str,
/// The kind of instrument.
pub kind: InstrumentKind,
}
/// The kind of metric instrument.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum InstrumentKind {
/// Monotonic `u64` counter.
Counter,
/// Bidirectional `i64` counter, used for gauges emitted as deltas
/// (e.g. `grpc.xds_client.resources`).
UpDownCounter,
/// Distribution of `f64` values.
Histogram,
/// Last-value `i64` gauge (push model).
Gauge,
}
/// An attribute key/value pair attached to a single measurement.
#[derive(Debug, Clone)]
pub struct KeyValue {
/// Attribute name. Keys are `'static` because the set of attribute keys
/// per metric is fixed.
pub key: &'static str,
/// Attribute value.
pub value: Value,
}
impl KeyValue {
/// Construct a string-valued attribute.
///
/// Accepts any value convertible to [`StringValue`]: `&'static str`,
/// `String`, `Box<str>`, `Arc<str>`, or `Cow<'static, str>`. The choice
/// affects allocation cost — see [`StringValue`].
pub fn str(key: &'static str, value: impl Into<StringValue>) -> Self {
Self {
key,
value: Value::Str(value.into()),
}
}
/// Construct a boolean-valued attribute.
pub fn bool(key: &'static str, value: bool) -> Self {
Self {
key,
value: Value::Bool(value),
}
}
/// Construct an integer-valued attribute.
pub fn int(key: &'static str, value: i64) -> Self {
Self {
key,
value: Value::Int(value),
}
}
/// Construct an f64-valued attribute.
pub fn f64(key: &'static str, value: f64) -> Self {
Self {
key,
value: Value::F64(value),
}
}
}
/// A typed attribute value.
#[derive(Debug, Clone)]
pub enum Value {
/// Boolean value.
Bool(bool),
/// Signed 64-bit integer value.
Int(i64),
/// 64-bit floating-point value.
F64(f64),
/// String value. See [`StringValue`] for ownership modes.
Str(StringValue),
}
/// String attribute value with three ownership modes.
///
/// Mirrors the `OtelString` design from the OpenTelemetry Rust SDK: a single
/// non-lifetime-parameterized type that supports static borrows, owned strings,
/// and refcounted strings. This keeps [`MetricsRecorder`] trait object-safe
/// while letting callers pick the cheapest representation for the value at
/// hand:
///
/// - [`Static`](Self::Static) — for compile-time-known values
/// (e.g. cache_state labels like `"acked"`). Zero allocation.
/// - [`Owned`](Self::Owned) — for runtime-built strings the recorder will own.
/// One heap allocation per value.
/// - [`RefCounted`](Self::RefCounted) — for runtime values shared across many
/// emissions (e.g. the channel target stored on the worker). Cloning is one
/// atomic op, no allocation.
///
/// `From` impls cover the common conversions, so call sites usually need only
/// `KeyValue::str(KEY, value)` with whatever string-like type they have.
#[derive(Debug, Clone)]
pub enum StringValue {
/// Compile-time known string. Zero-cost.
Static(&'static str),
/// Owned runtime-built string.
Owned(Box<str>),
/// Reference-counted string. Cheap to clone (atomic op only).
RefCounted(Arc<str>),
}
impl StringValue {
/// Borrow the string content regardless of variant.
pub fn as_str(&self) -> &str {
match self {
StringValue::Static(s) => s,
StringValue::Owned(s) => s,
StringValue::RefCounted(s) => s,
}
}
}
impl AsRef<str> for StringValue {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for StringValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl From<&'static str> for StringValue {
fn from(s: &'static str) -> Self {
StringValue::Static(s)
}
}
impl From<String> for StringValue {
fn from(s: String) -> Self {
StringValue::Owned(s.into_boxed_str())
}
}
impl From<Box<str>> for StringValue {
fn from(s: Box<str>) -> Self {
StringValue::Owned(s)
}
}
impl From<Arc<str>> for StringValue {
fn from(s: Arc<str>) -> Self {
StringValue::RefCounted(s)
}
}
impl From<Cow<'static, str>> for StringValue {
fn from(c: Cow<'static, str>) -> Self {
match c {
Cow::Borrowed(s) => StringValue::Static(s),
Cow::Owned(s) => StringValue::Owned(s.into_boxed_str()),
}
}
}
/// Backend interface for recording metric measurements.
///
/// Implementors translate calls into measurements on their telemetry backend.
/// Implementations must be cheap and lock-free where possible since these calls
/// happen on hot paths.
pub trait MetricsRecorder: Send + Sync + 'static {
/// Add a value to a monotonic counter.
fn add_counter_u64(&self, instrument: &'static Instrument, value: u64, attrs: &[KeyValue]);
/// Add a (possibly negative) delta to an up-down counter.
fn add_up_down_counter_i64(
&self,
instrument: &'static Instrument,
value: i64,
attrs: &[KeyValue],
);
/// Record a value in a histogram.
fn record_histogram_f64(&self, instrument: &'static Instrument, value: f64, attrs: &[KeyValue]);
/// Record the current value of a push-model gauge.
fn record_gauge_i64(&self, instrument: &'static Instrument, value: i64, attrs: &[KeyValue]);
}
/// Instrument descriptors for the gRFC A78 XdsClient metrics emitted by this crate.
pub mod instruments {
use super::{Instrument, InstrumentKind};
/// `grpc.xds_client.connected` — gauge indicating whether the client has an active ADS stream.
pub static XDS_CLIENT_CONNECTED: Instrument = Instrument {
name: "grpc.xds_client.connected",
description: "Whether the xDS client currently has an active ADS stream to the xDS server.",
unit: "{bool}",
kind: InstrumentKind::Gauge,
};
/// `grpc.xds_client.server_failure` — counter of xDS server failure transitions.
pub static XDS_CLIENT_SERVER_FAILURE: Instrument = Instrument {
name: "grpc.xds_client.server_failure",
description: "Number of times the xDS server transitioned from healthy to unhealthy.",
unit: "{failure}",
kind: InstrumentKind::Counter,
};
/// `grpc.xds_client.resource_updates_valid` — counter of resources received and successfully decoded.
pub static XDS_CLIENT_RESOURCE_UPDATES_VALID: Instrument = Instrument {
name: "grpc.xds_client.resource_updates_valid",
description: "Number of resources received and successfully decoded.",
unit: "{resource}",
kind: InstrumentKind::Counter,
};
/// `grpc.xds_client.resource_updates_invalid` — counter of resources that failed codec-level validation.
pub static XDS_CLIENT_RESOURCE_UPDATES_INVALID: Instrument = Instrument {
name: "grpc.xds_client.resource_updates_invalid",
description: "Number of resources received that failed codec-level validation.",
unit: "{resource}",
kind: InstrumentKind::Counter,
};
/// `grpc.xds_client.resources` — gauge of cached xDS resources, emitted as up-down-counter deltas.
///
/// Use `cache_state` attribute values from [`super::attrs::GRPC_XDS_CACHE_STATE`].
pub static XDS_CLIENT_RESOURCES: Instrument = Instrument {
name: "grpc.xds_client.resources",
description: "Number of xDS resources currently cached, broken down by cache state.",
unit: "{resource}",
kind: InstrumentKind::UpDownCounter,
};
/// Every instrument emitted by this crate.
///
/// Backends that pre-register instruments up front (e.g. the bundled
/// `OtelMetricsRecorder`) iterate this slice at construction time instead of
/// creating instruments lazily on the recording path.
pub static ALL: &[&Instrument] = &[
&XDS_CLIENT_CONNECTED,
&XDS_CLIENT_SERVER_FAILURE,
&XDS_CLIENT_RESOURCE_UPDATES_VALID,
&XDS_CLIENT_RESOURCE_UPDATES_INVALID,
&XDS_CLIENT_RESOURCES,
];
}
/// Attribute keys used by the gRFC A78 XdsClient metrics.
pub mod attrs {
/// `grpc.target` — the channel target (configured xDS URI).
pub const GRPC_TARGET: &str = "grpc.target";
/// `grpc.xds.server` — URI of the xDS server.
pub const GRPC_XDS_SERVER: &str = "grpc.xds.server";
/// `grpc.xds.authority` — xDS authority name (when bootstrap defines named authorities).
pub const GRPC_XDS_AUTHORITY: &str = "grpc.xds.authority";
/// `grpc.xds.cache_state` — cache state of a resource. Canonical values per
/// gRFC A78: `requested`, `acked`, `nacked`, `does_not_exist`, `nacked_but_cached`.
pub const GRPC_XDS_CACHE_STATE: &str = "grpc.xds.cache_state";
/// `grpc.xds.resource_type` — type URL of the resource.
pub const GRPC_XDS_RESOURCE_TYPE: &str = "grpc.xds.resource_type";
}