saluki_components/decoders/otlp/
mod.rs1use std::time::Duration;
2
3use agent_data_plane_config::domains;
4use async_trait::async_trait;
5use datadog_agent_apm::semantics::SemanticRegistryProvider;
6use otlp_protos::opentelemetry::proto::collector::trace::v1::ExportTraceServiceRequest;
7use prost::Message;
8use saluki_core::accounting::{MemoryBounds, MemoryBoundsBuilder};
9use saluki_core::{
10 components::{
11 decoders::{Decoder, DecoderBuilder, DecoderContext},
12 BuildContext,
13 },
14 data_model::{event::EventType, payload::PayloadType},
15 topology::interconnect::EventBufferManager,
16};
17use saluki_error::GenericError;
18use tokio::{
19 select,
20 time::{interval, MissedTickBehavior},
21};
22use tracing::{debug, error, warn};
23
24use crate::common::otlp::traces::translator::OtlpTracesTranslator;
25use crate::common::otlp::{
26 build_metrics, Metrics, OTLP_LOGS_GRPC_SERVICE_PATH, OTLP_METRICS_GRPC_SERVICE_PATH, OTLP_TRACES_GRPC_SERVICE_PATH,
27};
28
29pub struct OtlpDecoderConfiguration {
31 traces: domains::otlp::Traces,
33
34 max_resource_len: usize,
38
39 semantic_registry: SemanticRegistryProvider,
41}
42
43impl OtlpDecoderConfiguration {
44 pub fn from_configuration(traces: &domains::otlp::Traces) -> Self {
46 Self {
47 traces: traces.clone(),
48 max_resource_len: usize::MAX,
49 semantic_registry: SemanticRegistryProvider::default(),
50 }
51 }
52
53 pub fn with_max_resource_len(mut self, max_resource_len: usize) -> Self {
61 self.max_resource_len = max_resource_len;
62 self
63 }
64
65 pub fn with_semantic_registry(mut self, semantic_registry: SemanticRegistryProvider) -> Self {
72 self.semantic_registry = semantic_registry;
73 self
74 }
75}
76
77#[async_trait]
78impl DecoderBuilder for OtlpDecoderConfiguration {
79 fn input_payload_type(&self) -> PayloadType {
80 PayloadType::Grpc
81 }
82
83 fn output_event_type(&self) -> EventType {
84 EventType::Trace
85 }
86
87 async fn build(&self, context: BuildContext) -> Result<Box<dyn Decoder + Send>, GenericError> {
88 let metrics = build_metrics(context.component_context());
89 Ok(Box::new(OtlpDecoder {
90 traces_translator: self.traces_translator(),
91 metrics,
92 }))
93 }
94}
95
96impl OtlpDecoderConfiguration {
97 fn traces_translator(&self) -> OtlpTracesTranslator {
98 OtlpTracesTranslator::new(
99 self.traces.clone(),
100 self.max_resource_len,
101 self.semantic_registry.clone(),
102 )
103 }
104}
105
106impl MemoryBounds for OtlpDecoderConfiguration {
107 fn specify_bounds(&self, builder: &mut MemoryBoundsBuilder) {
108 builder.minimum().with_single_value::<OtlpDecoder>("decoder struct");
109 }
110}
111
112pub struct OtlpDecoder {
114 traces_translator: OtlpTracesTranslator,
115 metrics: Metrics,
116}
117
118#[async_trait]
119impl Decoder for OtlpDecoder {
120 async fn run(self: Box<Self>, mut context: DecoderContext) -> Result<(), GenericError> {
121 let Self {
122 mut traces_translator,
123 metrics,
124 } = *self;
125 let mut health = context.take_health_handle();
126 health.mark_ready();
127
128 debug!("OTLP decoder started.");
129
130 let mut buffer_flush = interval(Duration::from_millis(100));
132 buffer_flush.set_missed_tick_behavior(MissedTickBehavior::Delay);
133
134 let mut event_buffer_manager = EventBufferManager::default();
135
136 loop {
137 select! {
138 maybe_payload = context.payloads().next() => {
139 let payload = match maybe_payload {
140 Some(payload) => payload,
141 None => {
142 debug!("Payloads stream closed, shutting down decoder.");
143 break;
144 }
145 };
146
147 let grpc_payload = match payload.try_into_grpc_payload() {
148 Some(grpc) => grpc,
149 None => {
150 warn!("Received non-gRPC payload in OTLP decoder. Dropping payload.");
151 continue;
152 }
153 };
154
155 match grpc_payload.service_path() {
156 path if path == &*OTLP_TRACES_GRPC_SERVICE_PATH => {
157 let (_, _, _, body) = grpc_payload.into_parts();
158 let request = match ExportTraceServiceRequest::decode(body.into_bytes()) {
159 Ok(req) => req,
160 Err(e) => {
161 error!(error = %e, "Failed to decode OTLP trace request.");
162 continue;
163 }
164 };
165
166 for resource_spans in request.resource_spans {
167 for trace_event in traces_translator.translate_spans(resource_spans, &metrics) {
168 if let Some(event_buffer) = event_buffer_manager.try_push(trace_event) {
169 if let Err(e) = context.dispatcher().dispatch(event_buffer).await {
170 error!(error = %e, "Failed to dispatch trace events.");
171 }
172 }
173 }
174 }
175 }
176 path if path == &*OTLP_METRICS_GRPC_SERVICE_PATH => {
177 warn!("OTLP metrics decoding not yet implemented. Dropping metrics payload.");
178 }
179 path if path == &*OTLP_LOGS_GRPC_SERVICE_PATH => {
180 warn!("OTLP logs decoding not yet implemented. Dropping logs payload.");
181 }
182 path => {
183 warn!(service_path = path, "Received gRPC payload with unknown service path. Dropping payload.");
184 }
185 }
186 },
187 _ = buffer_flush.tick() => {
188 if let Some(event_buffer) = event_buffer_manager.consume() {
189 if let Err(e) = context.dispatcher().dispatch(event_buffer).await {
190 error!(error = %e, "Failed to dispatch buffered trace events.");
191 }
192 }
193 },
194 _ = health.live() => continue,
195 }
196 }
197
198 if let Some(event_buffer) = event_buffer_manager.consume() {
199 if let Err(e) = context.dispatcher().dispatch(event_buffer).await {
200 error!(error = %e, "Failed to dispatch final trace events.");
201 }
202 }
203
204 debug!("OTLP decoder stopped.");
205
206 Ok(())
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use datadog_agent_apm::semantics::test_util::TestRegistryPublisher;
213
214 use super::*;
215 use crate::common::otlp::traces::translator::{custom_status_code_registry, translate_custom_status_code};
216
217 #[test]
218 fn injected_provider_reaches_the_traces_translator() {
219 let (publisher, provider) = TestRegistryPublisher::new();
220 let traces = domains::otlp::Traces {
221 string_interner_size: std::num::NonZeroUsize::new(64 * 1024).unwrap(),
222 ..Default::default()
223 };
224 let mut translator = OtlpDecoderConfiguration::from_configuration(&traces)
225 .with_semantic_registry(provider)
226 .traces_translator();
227 let metrics = Metrics::for_tests();
228 assert_eq!(translate_custom_status_code(&mut translator, &metrics), None);
229
230 publisher.assign([("a", custom_status_code_registry())]);
231 assert_eq!(translate_custom_status_code(&mut translator, &metrics), Some(418.0));
232 }
233}