agent_data_plane_config/domains/
otlp.rs

1//! OTLP domain: the OTLP receiver (gRPC/HTTP transports, logs/metrics activation), the OTLP proxy
2//! gating, and OTLP context sizing. OTLP trace handling lives in the `traces` domain.
3
4use std::{num::NonZeroUsize, str::FromStr};
5
6use serde::Serialize;
7
8use crate::defaults::DEFAULT_STRING_INTERNER_SIZE_BYTES;
9use crate::Error;
10
11/// Resolved OTLP configuration.
12#[derive(Clone, Debug, Default, PartialEq, Serialize)]
13pub struct Domain {
14    /// OTLP receiver transports and per-signal activation.
15    pub receiver: Receiver,
16
17    /// OTLP metrics translation settings.
18    pub metrics: Metrics,
19
20    /// OTLP trace ingestion settings.
21    pub traces: Traces,
22
23    /// OTLP proxy gating and endpoint.
24    pub proxy: Proxy,
25
26    /// OTLP context cache sizing.
27    pub contexts: Contexts,
28}
29
30/// OTLP metrics translation settings.
31#[derive(Clone, Debug, Default, PartialEq, Serialize)]
32pub struct Metrics {
33    /// How explicit histogram buckets are reported.
34    pub histogram_mode: HistogramMode,
35
36    /// Whether histogram count, sum, minimum, and maximum metrics are emitted when available.
37    ///
38    /// The `nobuckets` mode requires this setting. Defaults to `false`.
39    pub send_histogram_aggregations: bool,
40
41    /// Whether every resource attribute is added as a raw metric tag, in addition to the
42    /// semantic-convention mappings that are always applied.
43    pub resource_attributes_as_tags: bool,
44
45    /// OTLP sum translation settings.
46    pub sums: Sums,
47
48    /// Comma-separated list of tags to add to every emitted metric.
49    pub tags: String,
50
51    /// OTLP summary translation settings.
52    pub summaries: Summaries,
53}
54
55/// How explicit OTLP histogram buckets are reported.
56#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize)]
57pub enum HistogramMode {
58    /// Omit bucket metrics.
59    NoBuckets,
60
61    /// Report each bucket as a counter.
62    Counters,
63
64    /// Report buckets as distributions.
65    #[default]
66    Distributions,
67}
68
69impl FromStr for HistogramMode {
70    type Err = Error;
71
72    fn from_str(value: &str) -> Result<Self, Self::Err> {
73        match value {
74            "nobuckets" => Ok(Self::NoBuckets),
75            "counters" => Ok(Self::Counters),
76            "distributions" => Ok(Self::Distributions),
77            other => Err(Error::new_without_source(format!(
78                "unknown histogram mode `{other}`; expected `nobuckets`, `counters`, or `distributions`"
79            ))),
80        }
81    }
82}
83
84/// How cumulative monotonic sums are reported.
85#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize)]
86pub enum CumulativeMonotonicMode {
87    /// Converts cumulative values to deltas and reports them as counts.
88    #[default]
89    ToDelta,
90
91    /// Reports cumulative values as gauges without converting them to deltas.
92    RawValue,
93}
94
95impl FromStr for CumulativeMonotonicMode {
96    type Err = Error;
97
98    fn from_str(value: &str) -> Result<Self, Self::Err> {
99        match value {
100            "to_delta" => Ok(Self::ToDelta),
101            "raw_value" => Ok(Self::RawValue),
102            other => Err(Error::new_without_source(format!(
103                "unknown cumulative monotonic sum mode `{other}`; expected `to_delta` or `raw_value`"
104            ))),
105        }
106    }
107}
108
109/// Controls how the first value of a cumulative monotonic sum is reported.
110#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize)]
111pub enum InitialCumulativeMonotonicValue {
112    /// Reports the first value when its series started after the translator process.
113    #[default]
114    Auto,
115
116    /// Always drops the first value.
117    Drop,
118
119    /// Always reports the first value.
120    Keep,
121}
122
123impl FromStr for InitialCumulativeMonotonicValue {
124    type Err = Error;
125
126    fn from_str(value: &str) -> Result<Self, Self::Err> {
127        match value {
128            "auto" => Ok(Self::Auto),
129            "drop" => Ok(Self::Drop),
130            "keep" => Ok(Self::Keep),
131            other => Err(Error::new_without_source(format!(
132                "unknown initial cumulative monotonic value `{other}`; expected `auto`, `drop`, or `keep`"
133            ))),
134        }
135    }
136}
137
138/// OTLP sum translation settings.
139#[derive(Clone, Debug, Default, PartialEq, Serialize)]
140pub struct Sums {
141    /// Cumulative monotonic sum reporting mode.
142    ///
143    /// Defaults to `to_delta`, which converts cumulative values to delta counts. Set to `raw_value` to emit
144    /// cumulative values as gauges.
145    pub cumulative_monotonic_mode: CumulativeMonotonicMode,
146
147    /// Initial cumulative monotonic sum reporting behavior.
148    ///
149    /// Defaults to `auto`, which reports the value only when its series started after the translator process.
150    /// Set this to `drop` to always discard the first value or `keep` to always report it.
151    pub initial_cumulative_monotonic_value: InitialCumulativeMonotonicValue,
152}
153
154/// OTLP summary translation settings.
155#[derive(Clone, Debug, Default, PartialEq, Serialize)]
156pub struct Summaries {
157    /// How summary quantiles are reported.
158    ///
159    /// Defaults to `gauges`, which emits one gauge metric per quantile. Set to `noquantiles` to omit quantile
160    /// metrics.
161    pub mode: SummaryMode,
162}
163
164/// How OTLP summary quantiles are reported.
165#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize)]
166pub enum SummaryMode {
167    /// Report one gauge metric per quantile.
168    #[default]
169    Gauges,
170
171    /// Omit quantile metrics.
172    NoQuantiles,
173}
174
175impl FromStr for SummaryMode {
176    type Err = Error;
177
178    fn from_str(value: &str) -> Result<Self, Self::Err> {
179        match value {
180            "gauges" => Ok(Self::Gauges),
181            "noquantiles" => Ok(Self::NoQuantiles),
182            other => Err(Error::new_without_source(format!(
183                "unknown summary mode `{other}`; expected `gauges` or `noquantiles`"
184            ))),
185        }
186    }
187}
188
189/// OTLP receiver transports and per-signal activation.
190#[derive(Clone, Debug, Default, PartialEq, Serialize)]
191pub struct Receiver {
192    /// Whether the receiver accepts OTLP logs.
193    pub logs_enabled: bool,
194
195    /// Whether the receiver accepts OTLP metrics.
196    pub metrics_enabled: bool,
197
198    /// gRPC receiver settings.
199    pub grpc: GrpcReceiver,
200
201    /// HTTP receiver settings.
202    pub http: HttpReceiver,
203}
204
205/// Default gRPC maximum inbound message size, in MiB.
206///
207/// The Datadog schema default for `max_recv_msg_size_mib` is `0`, which grpc-go treats as "apply the
208/// built-in 4 MiB limit". Translation substitutes this constant for a configured `0` so the model
209/// always carries an effective limit.
210pub const DEFAULT_GRPC_MAX_RECV_MSG_SIZE_MIB: u64 = 4;
211
212/// OTLP gRPC receiver.
213#[derive(Clone, Debug, Default, PartialEq, Serialize)]
214pub struct GrpcReceiver {
215    /// Address the gRPC receiver listens on.
216    pub endpoint: String,
217
218    /// Maximum inbound message size, in MiB.
219    pub max_recv_msg_size_mib: u64,
220
221    /// Transport the gRPC receiver binds (for example, `tcp` or `unix`).
222    pub transport: String,
223}
224
225/// OTLP HTTP receiver.
226#[derive(Clone, Debug, PartialEq, Serialize)]
227pub struct HttpReceiver {
228    /// Address the HTTP receiver listens on.
229    pub endpoint: String,
230
231    /// Transport the HTTP receiver binds (for example, `tcp` or `unix`). (not in Datadog Agent
232    /// config schema)
233    pub transport: String,
234}
235
236impl Default for HttpReceiver {
237    fn default() -> Self {
238        Self {
239            // Witnessed; overwritten during drive.
240            endpoint: String::new(),
241            transport: "tcp".to_string(),
242        }
243    }
244}
245
246/// OTLP trace ingestion settings.
247#[derive(Clone, Debug, PartialEq, Serialize)]
248pub struct Traces {
249    /// Whether OTLP trace ingestion is enabled.
250    pub enabled: bool,
251
252    /// Internal port the OTLP trace receiver forwards to.
253    pub internal_port: u16,
254
255    /// Percentage of OTLP traces the probabilistic sampler keeps.
256    pub probabilistic_sampler_sampling_percentage: f64,
257
258    /// Non-zero byte budget for the OTLP trace context interner. (not in Datadog Agent config schema)
259    ///
260    /// Defaults to 512 KiB and cannot exceed 1 GiB.
261    pub string_interner_size: NonZeroUsize,
262
263    /// Whether top-level spans are computed from span kind on OTLP traces. (not in Datadog Agent
264    /// config schema)
265    pub enable_compute_top_level_by_span_kind: bool,
266
267    /// Whether spans missing intake-required fields are ingested rather than rejected. (not in
268    /// Datadog Agent config schema)
269    pub ignore_missing_datadog_fields: bool,
270}
271
272impl Default for Traces {
273    fn default() -> Self {
274        Self {
275            enabled: false,
276            internal_port: 0,
277            probabilistic_sampler_sampling_percentage: 0.0,
278            string_interner_size: DEFAULT_STRING_INTERNER_SIZE_BYTES,
279            enable_compute_top_level_by_span_kind: true,
280            ignore_missing_datadog_fields: false,
281        }
282    }
283}
284
285/// OTLP proxy gating: which signals the proxy forwards, and the proxy receiver endpoint.
286#[derive(Clone, Debug, Default, PartialEq, Serialize)]
287pub struct Proxy {
288    /// Whether the OTLP proxy is enabled.
289    pub enabled: bool,
290
291    /// Whether the proxy forwards logs.
292    pub logs_enabled: bool,
293
294    /// Whether the proxy forwards metrics.
295    pub metrics_enabled: bool,
296
297    /// Whether the proxy forwards traces.
298    pub traces_enabled: bool,
299
300    /// Address the proxy's gRPC receiver listens on.
301    pub grpc_endpoint: String,
302}
303
304/// OTLP context cache sizing.
305#[derive(Clone, Debug, PartialEq, Serialize)]
306pub struct Contexts {
307    /// Whether contexts may be heap-allocated when the interner is full. (not in Datadog Agent
308    /// config schema)
309    pub allow_context_heap_allocs: bool,
310
311    /// Maximum number of metric contexts held in the cache. (not in Datadog Agent config schema)
312    pub cached_contexts_limit: usize,
313
314    /// Maximum number of tagsets held in the cache. (not in Datadog Agent config schema)
315    pub cached_tagsets_limit: usize,
316
317    /// Size, in bytes, of the context string interner. (not in Datadog Agent config schema)
318    pub string_interner_size: u64,
319}
320
321impl Default for Contexts {
322    fn default() -> Self {
323        Self {
324            allow_context_heap_allocs: true,
325            cached_contexts_limit: 500_000,
326            cached_tagsets_limit: 500_000,
327            string_interner_size: 2 * 1024 * 1024,
328        }
329    }
330}
331
332#[cfg(test)]
333mod tests {
334    use super::{CumulativeMonotonicMode, InitialCumulativeMonotonicValue};
335
336    #[test]
337    fn cumulative_monotonic_mode_parses_known_values() {
338        assert_eq!(
339            "to_delta"
340                .parse::<CumulativeMonotonicMode>()
341                .expect("to_delta should parse"),
342            CumulativeMonotonicMode::ToDelta
343        );
344        assert_eq!(
345            "raw_value"
346                .parse::<CumulativeMonotonicMode>()
347                .expect("raw_value should parse"),
348            CumulativeMonotonicMode::RawValue
349        );
350    }
351
352    #[test]
353    fn cumulative_monotonic_mode_rejects_unknown_values() {
354        let error = "unsupported"
355            .parse::<CumulativeMonotonicMode>()
356            .expect_err("unsupported mode should be rejected");
357
358        assert_eq!(
359            error.to_string(),
360            "unknown cumulative monotonic sum mode `unsupported`; expected `to_delta` or `raw_value`"
361        );
362    }
363
364    #[test]
365    fn initial_cumulative_monotonic_value_parses_known_values() {
366        for (value, expected) in [
367            ("auto", InitialCumulativeMonotonicValue::Auto),
368            ("drop", InitialCumulativeMonotonicValue::Drop),
369            ("keep", InitialCumulativeMonotonicValue::Keep),
370        ] {
371            assert_eq!(
372                value
373                    .parse::<InitialCumulativeMonotonicValue>()
374                    .expect("known value should parse"),
375                expected
376            );
377        }
378    }
379
380    #[test]
381    fn initial_cumulative_monotonic_value_rejects_unknown_values() {
382        let error = "unsupported"
383            .parse::<InitialCumulativeMonotonicValue>()
384            .expect_err("unsupported value should be rejected");
385
386        assert_eq!(
387            error.to_string(),
388            "unknown initial cumulative monotonic value `unsupported`; expected `auto`, `drop`, or `keep`"
389        );
390    }
391}