Skip to main content

fast_telemetry/
runtime.rs

1//! Runtime service for sharing telemetry across crate boundaries.
2//!
3//! The runtime owns metric groups for export/snapshot traversal and one shared
4//! span collector. Recording remains a direct call on metric handles owned by
5//! the registered metric type, and span recording forwards to the embedded
6//! collector without a registry lookup.
7
8use crate::{CompletedSpan, ExportMetrics, MetricVisitor, Span, SpanCollector, SpanKind};
9use parking_lot::RwLock;
10use std::borrow::Cow;
11use std::ops::Deref;
12use std::sync::Arc;
13
14/// Configuration for [`Runtime`].
15///
16/// This is currently reserved for future runtime tuning. Use
17/// [`RuntimeConfig::default`] when creating a runtime.
18#[derive(Clone, Debug, Default)]
19#[non_exhaustive]
20pub struct RuntimeConfig {}
21
22/// Logical scope for a registered metric group.
23///
24/// A scope is registry metadata for grouping/filtering registered metric
25/// groups. It does not rewrite metric names emitted by the metric group.
26///
27/// Downstream crates should expose their accepted runtime type and scope names
28/// rather than asking callers to guess the exact dependency edge.
29#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
30pub struct MetricScope {
31    name: Arc<str>,
32}
33
34impl MetricScope {
35    /// Create a metric scope from a stable logical name.
36    pub fn new(name: impl Into<Arc<str>>) -> Self {
37        Self { name: name.into() }
38    }
39
40    /// Return the scope name.
41    pub fn name(&self) -> &str {
42        &self.name
43    }
44}
45
46impl From<&'static str> for MetricScope {
47    fn from(name: &'static str) -> Self {
48        Self::new(name)
49    }
50}
51
52impl From<String> for MetricScope {
53    fn from(name: String) -> Self {
54        Self::new(Arc::<str>::from(name))
55    }
56}
57
58/// Shared telemetry runtime.
59///
60/// Share one runtime across crate boundaries so metric registration, span
61/// collection, and export setup are owned once per service. Register metric
62/// groups during construction, then keep updating the returned metric handles
63/// directly on hot paths.
64pub struct Runtime {
65    config: RuntimeConfig,
66    registry: RwLock<Vec<RegisteredMetricGroup>>,
67    span_collector: Arc<SpanCollector>,
68}
69
70impl Runtime {
71    /// Create a runtime wrapped in [`Arc`] for parent/child sharing.
72    pub fn new(config: RuntimeConfig) -> Arc<Self> {
73        Arc::new(Self {
74            config,
75            registry: RwLock::new(Vec::new()),
76            span_collector: Arc::new(SpanCollector::new(8, 4096)),
77        })
78    }
79
80    /// Return this runtime's configuration.
81    pub fn config(&self) -> &RuntimeConfig {
82        &self.config
83    }
84
85    /// Return the shared span collector owned by this runtime.
86    ///
87    /// Clone this once when wiring exporters. Hot-path span creation should use
88    /// [`start_span`](Self::start_span) or keep this borrowed through the
89    /// shared runtime rather than cloning an [`Arc`] per operation.
90    pub fn span_collector(&self) -> &Arc<SpanCollector> {
91        &self.span_collector
92    }
93
94    /// Create a new root span with a fresh trace ID.
95    ///
96    /// This forwards to the runtime's shared [`SpanCollector`], keeping the
97    /// collector service shared across crate boundaries.
98    pub fn start_span(&self, name: impl Into<Cow<'static, str>>, kind: SpanKind) -> Span {
99        self.span_collector.start_span(name, kind)
100    }
101
102    /// Create a root span from an incoming W3C `traceparent` header.
103    ///
104    /// This forwards to the runtime's shared [`SpanCollector`].
105    pub fn start_span_from_traceparent(
106        &self,
107        traceparent: Option<&str>,
108        name: impl Into<Cow<'static, str>>,
109        kind: SpanKind,
110    ) -> Span {
111        self.span_collector
112            .start_span_from_traceparent(traceparent, name, kind)
113    }
114
115    /// Flush spans recorded on the current thread into the shared collector.
116    pub fn flush_local_spans(&self) {
117        self.span_collector.flush_local();
118    }
119
120    /// Drain completed spans from the shared collector into `buf`.
121    pub fn drain_spans_into(&self, buf: &mut Vec<CompletedSpan>) {
122        self.span_collector.drain_into(buf);
123    }
124
125    /// Register a metric group and return direct handles to it.
126    ///
127    /// Registry work happens here. Recording should use the returned
128    /// [`RegisteredMetrics`] value, or handles pre-resolved inside `metrics`.
129    pub fn register_metrics<M>(&self, scope: MetricScope, metrics: M) -> RegisteredMetrics<M>
130    where
131        M: ExportMetrics + Send + Sync + 'static,
132    {
133        let metrics = Arc::new(metrics);
134        let erased_metrics: Arc<dyn ErasedExportMetrics> = metrics.clone();
135        self.registry.write().push(RegisteredMetricGroup {
136            scope: scope.clone(),
137            metrics: erased_metrics,
138        });
139
140        RegisteredMetrics { scope, metrics }
141    }
142
143    /// Visit all registered metric groups as structured cumulative metrics.
144    pub fn visit_metrics<V>(&self, visitor: &mut V)
145    where
146        V: MetricVisitor,
147    {
148        for group in self.registry.read().iter() {
149            group.metrics.visit_metrics(visitor);
150        }
151    }
152
153    /// Visit registered metric groups that match `scope`.
154    pub fn visit_metrics_for_scope<V>(&self, scope: &MetricScope, visitor: &mut V)
155    where
156        V: MetricVisitor,
157    {
158        for group in self.registry.read().iter() {
159            if &group.scope == scope {
160                group.metrics.visit_metrics(visitor);
161            }
162        }
163    }
164
165    /// Return the number of registered metric groups.
166    pub fn registered_metrics_len(&self) -> usize {
167        self.registry.read().len()
168    }
169
170    /// Return the registered metric scopes.
171    pub fn scopes(&self) -> Vec<MetricScope> {
172        self.registry
173            .read()
174            .iter()
175            .map(|group| group.scope.clone())
176            .collect()
177    }
178}
179
180/// Direct handles to a metric group registered with a [`Runtime`].
181#[must_use = "keep RegisteredMetrics so hot paths can update direct metric handles"]
182pub struct RegisteredMetrics<M> {
183    scope: MetricScope,
184    metrics: Arc<M>,
185}
186
187impl<M> RegisteredMetrics<M> {
188    /// Return this metric group's scope.
189    pub fn scope(&self) -> &MetricScope {
190        &self.scope
191    }
192
193    /// Return the shared metric group.
194    pub fn metrics(&self) -> &Arc<M> {
195        &self.metrics
196    }
197
198    /// Convert into the shared metric group.
199    pub fn into_metrics(self) -> Arc<M> {
200        self.metrics
201    }
202}
203
204impl<M> Clone for RegisteredMetrics<M> {
205    fn clone(&self) -> Self {
206        Self {
207            scope: self.scope.clone(),
208            metrics: Arc::clone(&self.metrics),
209        }
210    }
211}
212
213impl<M> Deref for RegisteredMetrics<M> {
214    type Target = M;
215
216    fn deref(&self) -> &Self::Target {
217        &self.metrics
218    }
219}
220
221struct RegisteredMetricGroup {
222    scope: MetricScope,
223    metrics: Arc<dyn ErasedExportMetrics>,
224}
225
226trait ErasedExportMetrics: Send + Sync {
227    fn visit_metrics(&self, visitor: &mut dyn MetricVisitor);
228}
229
230impl<M> ErasedExportMetrics for M
231where
232    M: ExportMetrics + Send + Sync + 'static,
233{
234    fn visit_metrics(&self, visitor: &mut dyn MetricVisitor) {
235        ExportMetrics::visit_metrics(self, visitor);
236    }
237}