1use crate::span::{v04, v05, v1, BytesData, SharedDictBytes, TraceData};
5use crate::trace_utils::convert_trace_chunks_v04_to_v05;
6use crate::{msgpack_decoder, trace_utils::cmp_send_data_payloads};
7use libdd_trace_protobuf::pb;
8use std::cmp::Ordering;
9use std::iter::Iterator;
10use tracing::warn;
11
12pub type TracerPayloadV04 = Vec<v04::SpanBytes>;
13pub type TracerPayloadV05 = Vec<v05::Span>;
14
15#[derive(Debug, Clone, Copy)]
16pub enum TraceEncoding {
18 V04,
20 V05,
22 V1,
24}
25
26#[derive(Debug)]
27pub enum TraceChunks<T: TraceData> {
28 V04(Vec<Vec<v04::Span<T>>>),
30 V05((SharedDictBytes, Vec<Vec<v05::Span>>)),
36 V1(Box<v1::TracerPayload<BytesData>>),
38}
39
40impl TraceChunks<BytesData> {
41 pub fn into_tracer_payload_collection(self) -> TracerPayloadCollection {
42 match self {
43 TraceChunks::V04(traces) => TracerPayloadCollection::V04(traces),
44 TraceChunks::V05(traces) => TracerPayloadCollection::V05(traces),
45 TraceChunks::V1(traces) => TracerPayloadCollection::V1(traces),
46 }
47 }
48}
49
50impl<T: TraceData> TraceChunks<T> {
51 pub fn size(&self) -> usize {
53 match self {
54 TraceChunks::V04(traces) => traces.len(),
55 TraceChunks::V05((_, traces)) => traces.len(),
56 TraceChunks::V1(trace) => trace.chunks.len(),
57 }
58 }
59}
60
61#[derive(Debug)]
62pub enum TracerPayloadCollection {
64 V07(Vec<pb::TracerPayload>),
66 V04(Vec<Vec<v04::SpanBytes>>),
68 V05((SharedDictBytes, Vec<Vec<v05::Span>>)),
70 V1(Box<v1::TracerPayload<BytesData>>),
72}
73
74impl TracerPayloadCollection {
75 pub fn append(&mut self, other: &mut Self) -> bool {
97 match (self, other) {
98 (TracerPayloadCollection::V07(dest), TracerPayloadCollection::V07(src)) => {
99 dest.append(src);
100 true
101 }
102 (TracerPayloadCollection::V04(dest), TracerPayloadCollection::V04(src)) => {
103 dest.append(src);
104 true
105 }
106 (TracerPayloadCollection::V1(dest), TracerPayloadCollection::V1(src)) => {
107 if metadata_matches_v1(dest, src) {
113 dest.chunks.append(&mut src.chunks);
114 true
115 } else {
116 false
117 }
118 }
119 #[allow(clippy::unimplemented)]
121 (TracerPayloadCollection::V05(_), _) => {
122 unimplemented!("Append for V05 not implemented")
123 }
124 _ => false,
125 }
126 }
127
128 pub fn merge(&mut self) {
140 if let TracerPayloadCollection::V07(collection) = self {
141 collection.sort_unstable_by(cmp_send_data_payloads);
142 collection.dedup_by(|a, b| {
143 if cmp_send_data_payloads(a, b) == Ordering::Equal {
144 b.chunks.append(&mut a.chunks);
146 return true;
147 }
148 false
149 })
150 }
151 }
152
153 pub fn size(&self) -> usize {
168 match self {
169 TracerPayloadCollection::V07(collection) => {
170 collection.iter().map(|s| s.chunks.len()).sum()
171 }
172 TracerPayloadCollection::V04(collection) => collection.len(),
173 TracerPayloadCollection::V05((_, collection)) => collection.len(),
174 TracerPayloadCollection::V1(collection) => collection.chunks.len(),
175 }
176 }
177}
178
179pub trait TraceChunkProcessor {
209 fn process(&mut self, chunk: &mut pb::TraceChunk, index: usize);
210}
211
212#[derive(Default)]
213pub struct DefaultTraceChunkProcessor;
217
218impl TraceChunkProcessor for DefaultTraceChunkProcessor {
219 fn process(&mut self, _chunk: &mut pb::TraceChunk, _index: usize) {
220 }
222}
223
224pub fn decode_to_trace_chunks(
256 data: libdd_tinybytes::Bytes,
257 encoding_type: TraceEncoding,
258) -> Result<(TraceChunks<BytesData>, usize), anyhow::Error> {
259 match encoding_type {
260 TraceEncoding::V04 => {
261 let (data, size) = msgpack_decoder::v04::from_bytes(data).map_err(|e| {
262 anyhow::format_err!("Error deserializing trace from request body: {e}")
263 })?;
264 Ok((TraceChunks::V04(data), size))
265 }
266 TraceEncoding::V05 => {
267 let (data, size) = msgpack_decoder::v05::from_bytes(data).map_err(|e| {
268 anyhow::format_err!("Error deserializing trace from request body: {e}")
269 })?;
270 Ok((convert_trace_chunks_v04_to_v05(data)?, size))
271 }
272 TraceEncoding::V1 => {
273 let (data, size) = msgpack_decoder::v1::from_bytes(data).map_err(|e| {
274 anyhow::format_err!("Error deserializing trace from request body: {e}")
275 })?;
276 Ok((TraceChunks::V1(Box::new(data)), size))
277 }
278 }
279}
280
281fn metadata_matches_v1(
290 dest: &v1::TracerPayload<BytesData>,
291 src: &v1::TracerPayload<BytesData>,
292) -> bool {
293 let fields = [
294 ("container_id", dest.container_id == src.container_id),
295 ("language_name", dest.language_name == src.language_name),
296 (
297 "language_version",
298 dest.language_version == src.language_version,
299 ),
300 ("tracer_version", dest.tracer_version == src.tracer_version),
301 ("runtime_id", dest.runtime_id == src.runtime_id),
302 ("env", dest.env == src.env),
303 ("hostname", dest.hostname == src.hostname),
304 ("app_version", dest.app_version == src.app_version),
305 ("attributes", dest.attributes.slow_compare(&src.attributes)),
306 ];
307
308 if fields.iter().any(|(_, eq)| !eq) {
309 warn!(
310 "Skipping V1 TracerPayload append: diverging metadata fields {:?}",
311 fields
312 .iter()
313 .filter_map(|(label, eq)| (!eq).then_some(*label))
314 .collect::<Vec<_>>()
315 );
316 return false;
317 }
318 true
319}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324 use crate::span::v04::{SpanBytes, VecMap};
325 use crate::test_utils::create_test_no_alloc_span;
326 use libdd_tinybytes::BytesString;
327 use libdd_trace_protobuf::pb;
328 use serde_json::json;
329
330 fn create_dummy_collection_v07() -> TracerPayloadCollection {
331 TracerPayloadCollection::V07(vec![pb::TracerPayload {
332 container_id: "".to_string(),
333 language_name: "".to_string(),
334 language_version: "".to_string(),
335 tracer_version: "".to_string(),
336 runtime_id: "".to_string(),
337 chunks: vec![pb::TraceChunk {
338 priority: 0,
339 origin: "".to_string(),
340 spans: vec![],
341 tags: Default::default(),
342 dropped_trace: false,
343 }],
344 tags: Default::default(),
345 env: "".to_string(),
346 hostname: "".to_string(),
347 app_version: "".to_string(),
348 container_debug: None,
349 }])
350 }
351
352 fn create_trace() -> Vec<SpanBytes> {
353 vec![
354 create_test_no_alloc_span(1234, 12341, 0, 1, true),
356 create_test_no_alloc_span(1234, 12342, 12341, 1, false),
357 create_test_no_alloc_span(1234, 12343, 12342, 1, false),
358 ]
359 }
360
361 #[test]
362 fn test_append_traces_v07() {
363 let mut two_traces = create_dummy_collection_v07();
364 two_traces.append(&mut create_dummy_collection_v07());
365
366 let mut trace = create_dummy_collection_v07();
367
368 let mut empty = TracerPayloadCollection::V07(vec![]);
369
370 trace.append(&mut create_dummy_collection_v07());
371 assert_eq!(2, trace.size());
372
373 trace.append(&mut two_traces);
374 assert_eq!(4, trace.size());
375
376 trace.append(&mut empty);
377 assert_eq!(4, trace.size());
378 }
379
380 #[test]
381 fn test_append_traces_v04() {
382 fn create_trace() -> TracerPayloadCollection {
383 TracerPayloadCollection::V04(vec![vec![create_test_no_alloc_span(0, 1, 0, 2, true)]])
384 }
385
386 let mut two_traces = create_trace();
387 two_traces.append(&mut create_trace());
388
389 let mut trace = create_trace();
390
391 let mut empty = TracerPayloadCollection::V04(vec![]);
392
393 trace.append(&mut create_trace());
394 assert_eq!(2, trace.size());
395
396 trace.append(&mut two_traces);
397 assert_eq!(4, trace.size());
398
399 trace.append(&mut empty);
400 assert_eq!(4, trace.size());
401 }
402
403 #[test]
404 fn test_merge_traces() {
405 let mut trace = create_dummy_collection_v07();
406
407 trace.append(&mut create_dummy_collection_v07());
408 assert_eq!(2, trace.size());
409
410 trace.merge();
411 assert_eq!(2, trace.size());
412 if let TracerPayloadCollection::V07(collection) = trace {
413 assert_eq!(1, collection.len());
414 } else {
415 panic!("Unexpected type");
416 }
417 }
418
419 #[test]
420 fn test_try_into_success() {
421 let span_data1 = json!([{
422 "service": "test-service",
423 "name": "test-service-name",
424 "resource": "test-service-resource",
425 "trace_id": 111,
426 "span_id": 222,
427 "parent_id": 100,
428 "start": 1,
429 "duration": 5,
430 "error": 0,
431 "meta": {},
432 "metrics": {},
433 "type": "serverless",
434 }]);
435
436 let expected_serialized_span_data1 = vec![SpanBytes {
437 service: BytesString::from_slice("test-service".as_ref()).unwrap(),
438 name: BytesString::from_slice("test-service-name".as_ref()).unwrap(),
439 resource: BytesString::from_slice("test-service-resource".as_ref()).unwrap(),
440 trace_id: 111,
441 span_id: 222,
442 parent_id: 100,
443 start: 1,
444 duration: 5,
445 error: 0,
446 meta: VecMap::new(),
447 metrics: VecMap::new(),
448 meta_struct: VecMap::new(),
449 r#type: BytesString::from_slice("serverless".as_ref()).unwrap(),
450 span_links: vec![],
451 span_events: vec![],
452 }];
453
454 let span_data2 = json!([{
455 "service": "test-service",
456 "name": "test-service-name",
457 "resource": "test-service-resource",
458 "trace_id": 111,
459 "span_id": 333,
460 "parent_id": 100,
461 "start": 1,
462 "duration": 5,
463 "error": 1,
464 "meta": {},
465 "metrics": {},
466 "type": "",
467 }]);
468
469 let expected_serialized_span_data2 = vec![SpanBytes {
470 service: BytesString::from_slice("test-service".as_ref()).unwrap(),
471 name: BytesString::from_slice("test-service-name".as_ref()).unwrap(),
472 resource: BytesString::from_slice("test-service-resource".as_ref()).unwrap(),
473 trace_id: 111,
474 span_id: 333,
475 parent_id: 100,
476 start: 1,
477 duration: 5,
478 error: 1,
479 meta: VecMap::new(),
480 metrics: VecMap::new(),
481 meta_struct: VecMap::new(),
482 r#type: BytesString::default(),
483 span_links: vec![],
484 span_events: vec![],
485 }];
486
487 let data = rmp_serde::to_vec(&vec![span_data1, span_data2])
488 .expect("Failed to serialize test span.");
489 let data = libdd_tinybytes::Bytes::from(data);
490
491 let result = decode_to_trace_chunks(data, TraceEncoding::V04);
492
493 assert!(result.is_ok());
494
495 let (chunks, _) = result.unwrap();
496 assert_eq!(2, chunks.size());
497
498 if let TraceChunks::V04(traces) = chunks {
499 assert_eq!(expected_serialized_span_data1, traces[0]);
500 assert_eq!(expected_serialized_span_data2, traces[1]);
501 } else {
502 panic!("Invalid collection type returned for try_into");
503 }
504 }
505
506 #[cfg_attr(miri, ignore)]
507 #[test]
508 fn test_try_into_empty() {
509 let empty_data = vec![0x90];
510 let data = libdd_tinybytes::Bytes::from(empty_data);
511
512 let result = decode_to_trace_chunks(data, TraceEncoding::V04);
513
514 assert!(result.is_ok());
515
516 let (collection, _) = result.unwrap();
517 assert_eq!(0, collection.size());
518 }
519
520 #[test]
521 fn test_try_into_meta_metrics_success() {
522 let dummy_trace = create_trace();
523 let expected = vec![create_trace()];
524 let payload = rmp_serde::to_vec_named(&expected).unwrap();
525 let payload = libdd_tinybytes::Bytes::from(payload);
526
527 let result = decode_to_trace_chunks(payload, TraceEncoding::V04);
528
529 assert!(result.is_ok());
530
531 let (collection, _size) = result.unwrap();
532 assert_eq!(1, collection.size());
533 if let TraceChunks::V04(traces) = collection {
534 assert_eq!(dummy_trace, traces[0]);
535 } else {
536 panic!("Invalid collection type returned for try_into");
537 }
538 }
539}