@@ -13,7 +13,7 @@ use arc_swap::{ArcSwap, ArcSwapOption};
13
13
use bytes:: Bytes ;
14
14
use datadog_trace_utils:: msgpack_decoder:: { self , decode:: error:: DecodeError } ;
15
15
use datadog_trace_utils:: send_with_retry:: { send_with_retry, RetryStrategy , SendWithRetryError } ;
16
- use datadog_trace_utils:: span:: SpanSlice ;
16
+ use datadog_trace_utils:: span:: { Span , SpanText } ;
17
17
use datadog_trace_utils:: trace_utils:: { self , TracerHeaderTags } ;
18
18
use datadog_trace_utils:: tracer_payload;
19
19
use ddcommon:: header:: {
@@ -199,51 +199,47 @@ pub struct TraceExporter {
199
199
telemetry : Option < TelemetryClient > ,
200
200
}
201
201
202
+ enum DeserInputFormat {
203
+ V04 ,
204
+ V05 ,
205
+ }
206
+
202
207
impl TraceExporter {
203
208
#[ allow( missing_docs) ]
204
209
pub fn builder ( ) -> TraceExporterBuilder {
205
210
TraceExporterBuilder :: default ( )
206
211
}
207
212
208
213
/// Send msgpack serialized traces to the agent
209
- #[ allow( missing_docs) ]
214
+ ///
215
+ /// # Arguments
216
+ ///
217
+ /// * data: A slice containing the serialized traces. This slice should be encoded following the
218
+ /// input_format passed to the TraceExporter on creating.
219
+ /// * trace_count: The number of traces in the data
220
+ ///
221
+ /// # Returns
222
+ /// * Ok(AgentResponse): The response from the agent
223
+ /// * Err(TraceExporterError): An error detailling what went wrong in the process
210
224
pub fn send (
211
225
& self ,
212
226
data : & [ u8 ] ,
213
227
trace_count : usize ,
214
228
) -> Result < AgentResponse , TraceExporterError > {
215
229
self . check_agent_info ( ) ;
216
230
217
- match self . input_format {
231
+ let res = match self . input_format {
218
232
TraceExporterInputFormat :: Proxy => self . send_proxy ( data. as_ref ( ) , trace_count) ,
219
- TraceExporterInputFormat :: V04 => match msgpack_decoder:: v04:: from_slice ( data) {
220
- Ok ( ( traces, _) ) => self . send_deser_ser ( traces) ,
221
- Err ( e) => Err ( TraceExporterError :: Deserialization ( e) ) ,
222
- } ,
223
- TraceExporterInputFormat :: V05 => match msgpack_decoder:: v05:: from_slice ( data) {
224
- Ok ( ( traces, _) ) => self . send_deser_ser ( traces) ,
225
- Err ( e) => Err ( TraceExporterError :: Deserialization ( e) ) ,
226
- } ,
233
+ TraceExporterInputFormat :: V04 => self . send_deser ( data, DeserInputFormat :: V04 ) ,
234
+ TraceExporterInputFormat :: V05 => self . send_deser ( data, DeserInputFormat :: V05 ) ,
235
+ } ?;
236
+ if res. is_empty ( ) {
237
+ return Err ( TraceExporterError :: Agent (
238
+ error:: AgentErrorKind :: EmptyResponse ,
239
+ ) ) ;
227
240
}
228
- . and_then ( |res| {
229
- if res. is_empty ( ) {
230
- return Err ( TraceExporterError :: Agent (
231
- error:: AgentErrorKind :: EmptyResponse ,
232
- ) ) ;
233
- }
234
241
235
- Ok ( AgentResponse :: from ( res) )
236
- } )
237
- . map_err ( |err| {
238
- if let TraceExporterError :: Deserialization ( ref e) = err {
239
- error ! ( "Error deserializing trace from request body: {e}" ) ;
240
- self . emit_metric (
241
- HealthMetric :: Count ( health_metrics:: STAT_DESER_TRACES_ERRORS , 1 ) ,
242
- None ,
243
- ) ;
244
- }
245
- err
246
- } )
242
+ Ok ( AgentResponse :: from ( res) )
247
243
}
248
244
249
245
/// Safely shutdown the TraceExporter and all related tasks
@@ -425,6 +421,35 @@ impl TraceExporter {
425
421
}
426
422
}
427
423
424
+ /// !!! This function is only for testing purposes !!!
425
+ ///
426
+ /// Waits the agent info to be ready by checking the agent_info state.
427
+ /// It will only return Ok after the agent info has been fetched at least once or Err if timeout
428
+ /// has been reached
429
+ ///
430
+ /// In production:
431
+ /// 1) We should not synchronously wait for this to be ready before sending traces
432
+ /// 2) It's not guaranteed to not block forever, since the /info endpoint might not be
433
+ /// available.
434
+ ///
435
+ /// The `send`` function will check agent_info when running, which will only be available if the
436
+ /// fetcher had time to reach to the agent.
437
+ /// Since agent_info can enable CSS computation, waiting for this during testing can make
438
+ /// snapshots non-determinitic.
439
+ #[ cfg( feature = "test-utils" ) ]
440
+ pub fn wait_agent_info_ready ( & self , timeout : Duration ) -> anyhow:: Result < ( ) > {
441
+ let start = std:: time:: Instant :: now ( ) ;
442
+ loop {
443
+ if std:: time:: Instant :: now ( ) . duration_since ( start) > timeout {
444
+ anyhow:: bail!( "Timeout waiting for agent info to be ready" , ) ;
445
+ }
446
+ if self . agent_info . load ( ) . is_some ( ) {
447
+ return Ok ( ( ) ) ;
448
+ }
449
+ std:: thread:: sleep ( Duration :: from_millis ( 10 ) ) ;
450
+ }
451
+ }
452
+
428
453
fn send_proxy ( & self , data : & [ u8 ] , trace_count : usize ) -> Result < String , TraceExporterError > {
429
454
self . send_data_to_url (
430
455
data,
@@ -546,42 +571,79 @@ impl TraceExporter {
546
571
/// Add all spans from the given iterator into the stats concentrator
547
572
/// # Panic
548
573
/// Will panic if another thread panicked will holding the lock on `stats_concentrator`
549
- fn add_spans_to_stats ( & self , traces : & [ Vec < SpanSlice > ] ) {
550
- if let StatsComputationStatus :: Enabled {
551
- stats_concentrator,
552
- cancellation_token : _,
553
- exporter_handle : _,
554
- } = & * * self . client_side_stats . load ( )
555
- {
556
- #[ allow( clippy:: unwrap_used) ]
557
- let mut stats_concentrator = stats_concentrator. lock ( ) . unwrap ( ) ;
558
-
559
- let spans = traces. iter ( ) . flat_map ( |trace| trace. iter ( ) ) ;
560
- for span in spans {
561
- stats_concentrator. add_span ( span) ;
562
- }
574
+ fn add_spans_to_stats < T : SpanText > (
575
+ & self ,
576
+ stats_concentrator : & Mutex < SpanConcentrator > ,
577
+ traces : & [ Vec < Span < T > > ] ,
578
+ ) {
579
+ #[ allow( clippy:: unwrap_used) ]
580
+ let mut stats_concentrator = stats_concentrator. lock ( ) . unwrap ( ) ;
581
+
582
+ let spans = traces. iter ( ) . flat_map ( |trace| trace. iter ( ) ) ;
583
+ for span in spans {
584
+ stats_concentrator. add_span ( span) ;
563
585
}
564
586
}
565
587
566
- fn send_deser_ser (
588
+ /// Send a list of trace chunks to the agent
589
+ ///
590
+ /// # Arguments
591
+ /// * trace_chunks: A list of trace chunks. Each trace chunk is a list of spans.
592
+ ///
593
+ /// # Returns
594
+ /// * Ok(String): The response from the agent
595
+ /// * Err(TraceExporterError): An error detailing what went wrong in the process
596
+ pub fn send_trace_chunks < T : SpanText > (
597
+ & self ,
598
+ trace_chunks : Vec < Vec < Span < T > > > ,
599
+ ) -> Result < String , TraceExporterError > {
600
+ self . check_agent_info ( ) ;
601
+ self . send_trace_chunks_inner ( trace_chunks)
602
+ }
603
+
604
+ /// Deserializes, processes and sends trace chunks to the agent
605
+ fn send_deser (
567
606
& self ,
568
- mut traces : Vec < Vec < SpanSlice > > ,
607
+ data : & [ u8 ] ,
608
+ format : DeserInputFormat ,
569
609
) -> Result < String , TraceExporterError > {
610
+ let ( traces, _) = match format {
611
+ DeserInputFormat :: V04 => msgpack_decoder:: v04:: from_slice ( data) ,
612
+ DeserInputFormat :: V05 => msgpack_decoder:: v05:: from_slice ( data) ,
613
+ }
614
+ . map_err ( |e| {
615
+ error ! ( "Error deserializing trace from request body: {e}" ) ;
616
+ self . emit_metric (
617
+ HealthMetric :: Count ( health_metrics:: STAT_DESER_TRACES_ERRORS , 1 ) ,
618
+ None ,
619
+ ) ;
620
+ TraceExporterError :: Deserialization ( e)
621
+ } ) ?;
570
622
self . emit_metric (
571
623
HealthMetric :: Count ( health_metrics:: STAT_DESER_TRACES , traces. len ( ) as i64 ) ,
572
624
None ,
573
625
) ;
574
626
627
+ self . send_trace_chunks_inner ( traces)
628
+ }
629
+
630
+ fn send_trace_chunks_inner < T : SpanText > (
631
+ & self ,
632
+ mut traces : Vec < Vec < Span < T > > > ,
633
+ ) -> Result < String , TraceExporterError > {
575
634
let mut header_tags: TracerHeaderTags = self . metadata . borrow ( ) . into ( ) ;
576
635
577
636
// Stats computation
578
- if let StatsComputationStatus :: Enabled { .. } = & * * self . client_side_stats . load ( ) {
637
+ if let StatsComputationStatus :: Enabled {
638
+ stats_concentrator, ..
639
+ } = & * * self . client_side_stats . load ( )
640
+ {
579
641
if !self . client_computed_top_level {
580
642
for chunk in traces. iter_mut ( ) {
581
643
datadog_trace_utils:: span:: trace_utils:: compute_top_level_span ( chunk) ;
582
644
}
583
645
}
584
- self . add_spans_to_stats ( & traces) ;
646
+ self . add_spans_to_stats ( stats_concentrator , & traces) ;
585
647
// Once stats have been computed we can drop all chunks that are not going to be
586
648
// sampled by the agent
587
649
let datadog_trace_utils:: span:: trace_utils:: DroppedP0Stats {
@@ -597,18 +659,9 @@ impl TraceExporter {
597
659
header_tags. dropped_p0_spans = dropped_p0_spans;
598
660
}
599
661
600
- let use_v05_format = match ( self . input_format , self . output_format ) {
601
- ( TraceExporterInputFormat :: V04 , TraceExporterOutputFormat :: V04 ) => false ,
602
- ( TraceExporterInputFormat :: V04 , TraceExporterOutputFormat :: V05 )
603
- | ( TraceExporterInputFormat :: V05 , TraceExporterOutputFormat :: V05 ) => true ,
604
- ( TraceExporterInputFormat :: V05 , TraceExporterOutputFormat :: V04 ) => {
605
- // TODO: Properly handle non-OK states to prevent possible panics (APMSP-18190).
606
- unreachable ! ( "Conversion from v05 to v04 not implemented" )
607
- }
608
- ( TraceExporterInputFormat :: Proxy , _) => {
609
- // TODO: Properly handle non-OK states to prevent possible panics (APMSP-18190).
610
- unreachable ! ( "Codepath invalid for proxy mode" , )
611
- }
662
+ let use_v05_format = match self . output_format {
663
+ TraceExporterOutputFormat :: V05 => true ,
664
+ TraceExporterOutputFormat :: V04 => false ,
612
665
} ;
613
666
let payload = trace_utils:: collect_trace_chunks ( traces, use_v05_format) . map_err ( |e| {
614
667
TraceExporterError :: Deserialization ( DecodeError :: InvalidFormat ( e. to_string ( ) ) )
0 commit comments