1pub mod send_data_result;
5
6use crate::msgpack_encoder;
7use crate::send_with_retry::compression::{add_headers, compress};
8use crate::send_with_retry::{
9 send_with_retry, CompressionStrategy, RetryStrategy, SendWithRetryResult,
10};
11use crate::trace_utils::TracerHeaderTags;
12use crate::tracer_payload::TracerPayloadCollection;
13use anyhow::{anyhow, Context};
14use futures::stream::FuturesUnordered;
15use futures::StreamExt;
16use http::{header::CONTENT_TYPE, HeaderMap, HeaderValue};
17use libdd_capabilities::{HttpClientCapability, SleepCapability};
18use libdd_common::{
19 header::{
20 APPLICATION_MSGPACK, APPLICATION_PROTOBUF, DATADOG_SEND_REAL_HTTP_STATUS,
21 DATADOG_TRACE_COUNT,
22 },
23 Endpoint,
24};
25use libdd_trace_protobuf::pb::{AgentPayload, TracerPayload};
26use send_data_result::SendDataResult;
27use std::collections::HashMap;
28
29#[derive(Debug)]
30pub struct SendData {
68 pub(crate) tracer_payloads: TracerPayloadCollection,
69 pub(crate) size: usize, target: Endpoint,
71 headers: HeaderMap,
72 retry_strategy: RetryStrategy,
73 compression: CompressionStrategy,
74}
75
76pub struct SendDataBuilder {
77 pub(crate) tracer_payloads: TracerPayloadCollection,
78 pub(crate) size: usize,
79 target: Endpoint,
80 headers: HeaderMap,
81 retry_strategy: RetryStrategy,
82 compression: CompressionStrategy,
83}
84
85impl SendDataBuilder {
86 pub fn new(
87 size: usize,
88 tracer_payload: TracerPayloadCollection,
89 tracer_header_tags: TracerHeaderTags,
90 target: &Endpoint,
91 ) -> SendDataBuilder {
92 let mut headers: HeaderMap = tracer_header_tags.into();
93 headers.insert(DATADOG_SEND_REAL_HTTP_STATUS, HeaderValue::from_static("1"));
94 SendDataBuilder {
95 tracer_payloads: tracer_payload,
96 size,
97 target: target.clone(),
98 headers,
99 retry_strategy: RetryStrategy::default(),
100 compression: CompressionStrategy::None,
101 }
102 }
103
104 pub fn with_compression(mut self, compression: CompressionStrategy) -> SendDataBuilder {
105 self.compression = compression;
106 self
107 }
108
109 pub fn with_api_key(mut self, api_key: &str) -> SendDataBuilder {
110 self.target.api_key = Some(api_key.to_string().into());
111 self
112 }
113
114 pub fn with_retry_strategy(mut self, retry_strategy: RetryStrategy) -> SendDataBuilder {
115 self.retry_strategy = retry_strategy;
116 self
117 }
118
119 pub fn build(self) -> SendData {
120 SendData {
121 tracer_payloads: self.tracer_payloads,
122 size: self.size,
123 target: self.target,
124 headers: self.headers,
125 retry_strategy: self.retry_strategy,
126 compression: self.compression,
127 }
128 }
129}
130
131impl SendData {
132 #[allow(unused_variables)]
145 pub fn new(
146 size: usize,
147 tracer_payload: TracerPayloadCollection,
148 tracer_header_tags: TracerHeaderTags,
149 target: &Endpoint,
150 ) -> SendData {
151 let mut headers: HeaderMap = tracer_header_tags.into();
152 headers.insert(DATADOG_SEND_REAL_HTTP_STATUS, HeaderValue::from_static("1"));
153 SendData {
154 tracer_payloads: tracer_payload,
155 size,
156 target: target.clone(),
157 headers,
158 retry_strategy: RetryStrategy::default(),
159 compression: CompressionStrategy::None,
160 }
161 }
162
163 pub fn len(&self) -> usize {
169 self.size
170 }
171
172 pub fn is_empty(&self) -> bool {
178 self.size == 0
179 }
180
181 pub fn get_target(&self) -> &Endpoint {
187 &self.target
188 }
189
190 pub fn get_payloads(&self) -> &TracerPayloadCollection {
196 &self.tracer_payloads
197 }
198
199 pub fn set_retry_strategy(&mut self, retry_strategy: RetryStrategy) {
205 self.retry_strategy = retry_strategy;
206 }
207
208 pub async fn send<C: HttpClientCapability + SleepCapability>(
214 &self,
215 capabilities: &C,
216 ) -> SendDataResult {
217 self.send_internal(capabilities, None).await
218 }
219
220 async fn send_internal<C: HttpClientCapability + SleepCapability>(
221 &self,
222 capabilities: &C,
223 endpoint: Option<Endpoint>,
224 ) -> SendDataResult {
225 if self.use_protobuf() {
226 self.send_with_protobuf(capabilities, endpoint).await
227 } else {
228 self.send_with_msgpack(capabilities, endpoint).await
229 }
230 }
231
232 async fn send_payload<C: HttpClientCapability + SleepCapability>(
233 &self,
234 capabilities: &C,
235 chunks: u64,
236 payload: Vec<u8>,
237 mut headers: HeaderMap,
238 endpoint: Option<&Endpoint>,
239 compression_strategy: CompressionStrategy,
240 ) -> (SendWithRetryResult, u64, u64) {
241 let (payload, compression_strategy) = compress(payload, compression_strategy);
244 add_headers(&mut headers, compression_strategy);
245 #[allow(clippy::unwrap_used)]
246 let payload_len = u64::try_from(payload.len()).unwrap();
247 (
248 send_with_retry(
249 capabilities,
250 endpoint.unwrap_or(&self.target),
251 payload,
252 &headers,
253 &self.retry_strategy,
254 CompressionStrategy::None,
255 )
256 .await,
257 payload_len,
258 chunks,
259 )
260 }
261
262 fn use_protobuf(&self) -> bool {
263 self.target.api_key.is_some()
264 }
265
266 async fn send_with_protobuf<C: HttpClientCapability + SleepCapability>(
267 &self,
268 capabilities: &C,
269 endpoint: Option<Endpoint>,
270 ) -> SendDataResult {
271 let mut result = SendDataResult::default();
272
273 #[allow(clippy::unwrap_used)]
274 let chunks = u64::try_from(self.tracer_payloads.size()).unwrap();
275
276 match &self.tracer_payloads {
277 TracerPayloadCollection::V07(payloads) => {
278 let agent_payload = construct_agent_payload(payloads.to_vec());
279 let serialized_trace_payload = match serialize_proto_payload(&agent_payload)
280 .context("Failed to serialize trace agent payload, dropping traces")
281 {
282 Ok(p) => p,
283 Err(e) => return result.error(e),
284 };
285 let mut request_headers = self.headers.clone();
286
287 request_headers.insert(CONTENT_TYPE, APPLICATION_PROTOBUF);
288
289 let (response, bytes_sent, chunks) = self
290 .send_payload(
291 capabilities,
292 chunks,
293 serialized_trace_payload,
294 request_headers,
295 endpoint.as_ref(),
296 self.compression,
297 )
298 .await;
299
300 result.update(response, bytes_sent, chunks);
301
302 result
303 }
304 _ => result,
305 }
306 }
307
308 async fn send_with_msgpack<C: HttpClientCapability + SleepCapability>(
309 &self,
310 capabilities: &C,
311 endpoint: Option<Endpoint>,
312 ) -> SendDataResult {
313 let mut result = SendDataResult::default();
314 let mut futures = FuturesUnordered::new();
315
316 match &self.tracer_payloads {
317 TracerPayloadCollection::V07(payloads) => {
318 for tracer_payload in payloads {
319 #[allow(clippy::unwrap_used)]
320 let chunks = u64::try_from(tracer_payload.chunks.len()).unwrap();
321 let mut headers = self.headers.clone();
322 headers.reserve(2);
323 headers.insert(DATADOG_TRACE_COUNT, chunks.into());
324 headers.insert(CONTENT_TYPE, APPLICATION_MSGPACK);
325
326 let payload = match rmp_serde::to_vec_named(tracer_payload) {
327 Ok(p) => p,
328 Err(e) => return result.error(anyhow!(e)),
329 };
330
331 futures.push(self.send_payload(
332 capabilities,
333 chunks,
334 payload,
335 headers,
336 endpoint.as_ref(),
337 CompressionStrategy::None,
338 ));
339 }
340 }
341 TracerPayloadCollection::V04(payload) => {
342 #[allow(clippy::unwrap_used)]
343 let chunks = u64::try_from(self.tracer_payloads.size()).unwrap();
344 let mut headers = self.headers.clone();
345 headers.reserve(2);
346 headers.insert(DATADOG_TRACE_COUNT, chunks.into());
347 headers.insert(CONTENT_TYPE, APPLICATION_MSGPACK);
348
349 let payload = msgpack_encoder::v04::to_vec_from_v04(payload);
350
351 futures.push(self.send_payload(
352 capabilities,
353 chunks,
354 payload,
355 headers,
356 endpoint.as_ref(),
357 CompressionStrategy::None,
358 ));
359 }
360 TracerPayloadCollection::V05(payload) => {
361 #[allow(clippy::unwrap_used)]
362 let chunks = u64::try_from(self.tracer_payloads.size()).unwrap();
363 let mut headers = self.headers.clone();
364 headers.reserve(2);
365 headers.insert(DATADOG_TRACE_COUNT, chunks.into());
366 headers.insert(CONTENT_TYPE, APPLICATION_MSGPACK);
367
368 let payload = match rmp_serde::to_vec(payload) {
369 Ok(p) => p,
370 Err(e) => return result.error(anyhow!(e)),
371 };
372
373 futures.push(self.send_payload(
374 capabilities,
375 chunks,
376 payload,
377 headers,
378 endpoint.as_ref(),
379 CompressionStrategy::None,
380 ));
381 }
382 TracerPayloadCollection::V1(payload) => {
383 #[allow(clippy::unwrap_used)]
384 let chunks = u64::try_from(self.tracer_payloads.size()).unwrap();
385 let mut headers = self.headers.clone();
386 headers.reserve(2);
387 headers.insert(DATADOG_TRACE_COUNT, chunks.into());
388 headers.insert(CONTENT_TYPE, APPLICATION_MSGPACK);
389
390 let payload = msgpack_encoder::v1::to_vec_from_v1(payload);
391
392 futures.push(self.send_payload(
393 capabilities,
394 chunks,
395 payload,
396 headers,
397 endpoint.as_ref(),
398 CompressionStrategy::None,
399 ));
400 }
401 }
402
403 loop {
404 match futures.next().await {
405 Some((response, payload_len, chunks)) => {
406 result.update(response, payload_len, chunks);
407 if result.last_result.is_err() {
408 return result;
409 }
410 }
411 None => return result,
412 }
413 }
414 }
415}
416
417fn construct_agent_payload(tracer_payloads: Vec<TracerPayload>) -> AgentPayload {
418 AgentPayload {
419 host_name: "".to_string(),
420 env: "".to_string(),
421 agent_version: "".to_string(),
422 error_tps: 60.0,
423 target_tps: 60.0,
424 tags: HashMap::new(),
425 tracer_payloads,
426 rare_sampler_enabled: false,
427 idx_tracer_payloads: Vec::new(),
428 }
429}
430
431fn serialize_proto_payload<T>(payload: &T) -> anyhow::Result<Vec<u8>>
432where
433 T: prost::Message,
434{
435 let mut buf = Vec::with_capacity(payload.encoded_len());
436 payload.encode(&mut buf)?;
437 Ok(buf)
438}
439
440#[cfg(test)]
441mod tests {
442 use super::*;
443 use crate::send_with_retry::{RetryBackoffType, RetryStrategy};
444 use crate::test_utils::create_test_no_alloc_span;
445 use crate::trace_utils::{construct_trace_chunk, construct_tracer_payload, TracerPayloadTags};
446 use crate::tracer_header_tags::{TracerGenericTags, TracerHeaderTags};
447 use httpmock::prelude::*;
448 use httpmock::MockServer;
449 use libdd_capabilities::HttpClientCapability;
450 use libdd_capabilities_impl::NativeCapabilities;
451 use libdd_common::Endpoint;
452 use libdd_trace_protobuf::pb::Span;
453 use std::collections::HashMap;
454 use std::time::Duration;
455
456 const ONE_SECOND: u64 = 1_000;
457 const HEADER_TAGS: TracerHeaderTags = TracerHeaderTags {
458 lang: "test-lang",
459 lang_version: "2.0",
460 lang_interpreter: "interpreter",
461 lang_vendor: "vendor",
462 tracer_version: "1.0",
463 container_id: "id",
464 generic: TracerGenericTags {
465 client_computed_top_level: false,
466 client_computed_stats: false,
467 dropped_p0_traces: 0,
468 dropped_p0_spans: 0,
469 },
470 };
471
472 fn setup_payload(header_tags: &TracerHeaderTags) -> TracerPayload {
473 let tracer_payload_tags = TracerPayloadTags {
474 env: "TEST".to_string(),
475 app_version: "1.0".to_string(),
476 hostname: "test_bench".to_string(),
477 runtime_id: "id".to_string(),
478 };
479
480 let chunk = construct_trace_chunk(vec![Span {
481 service: "test-service".to_string(),
482 name: "test-service-name".to_string(),
483 resource: "test-service-resource".to_string(),
484 trace_id: 111,
485 span_id: 222,
486 parent_id: 333,
487 start: 1,
488 duration: 5,
489 error: 0,
490 meta: HashMap::new(),
491 metrics: HashMap::new(),
492 meta_struct: HashMap::new(),
493 r#type: "".to_string(),
494 span_links: vec![],
495 span_events: vec![],
496 }]);
497
498 construct_tracer_payload(vec![chunk], header_tags, tracer_payload_tags)
499 }
500
501 fn compute_payload_len(collection: &TracerPayloadCollection) -> usize {
502 match collection {
503 TracerPayloadCollection::V07(payloads) => {
504 let agent_payload = construct_agent_payload(payloads.to_vec());
505 let serialized_trace_payload = serialize_proto_payload(&agent_payload).unwrap();
506 serialized_trace_payload.len()
507 }
508 _ => 0,
509 }
510 }
511
512 fn rmp_compute_payload_len(collection: &TracerPayloadCollection) -> usize {
513 match collection {
514 TracerPayloadCollection::V07(payloads) => {
515 let mut total: usize = 0;
516 for payload in payloads {
517 total += rmp_serde::to_vec_named(payload).unwrap().len();
518 }
519 total
520 }
521 TracerPayloadCollection::V04(payloads) => {
522 msgpack_encoder::v04::to_encoded_byte_len_from_v04(payloads) as usize
523 }
524 TracerPayloadCollection::V05(payloads) => rmp_serde::to_vec(payloads).unwrap().len(),
525 TracerPayloadCollection::V1(payload) => {
526 msgpack_encoder::v1::to_encoded_byte_len_from_v1(payload) as usize
527 }
528 }
529 }
530
531 #[test]
532 fn send_data_new_api_key() {
533 let header_tags = TracerHeaderTags::default();
534
535 let payload = setup_payload(&header_tags);
536 let data = SendData::new(
537 100,
538 TracerPayloadCollection::V07(vec![payload]),
539 HEADER_TAGS,
540 &Endpoint {
541 api_key: Some(std::borrow::Cow::Borrowed("TEST-KEY")),
542 url: "/foo/bar?baz".parse::<hyper::Uri>().unwrap(),
543 timeout_ms: ONE_SECOND,
544 ..Endpoint::default()
545 },
546 );
547
548 assert_eq!(data.size, 100);
549
550 assert_eq!(data.target.api_key.unwrap(), "TEST-KEY");
551 assert_eq!(data.target.url.path(), "/foo/bar");
552 }
553
554 #[test]
555 fn send_data_new_no_api_key() {
556 let header_tags = TracerHeaderTags::default();
557
558 let payload = setup_payload(&header_tags);
559 let data = SendData::new(
560 100,
561 TracerPayloadCollection::V07(vec![payload]),
562 header_tags.clone(),
563 &Endpoint {
564 api_key: None,
565 url: "/foo/bar?baz".parse::<hyper::Uri>().unwrap(),
566 timeout_ms: ONE_SECOND,
567 ..Endpoint::default()
568 },
569 );
570
571 assert_eq!(data.size, 100);
572
573 assert_eq!(data.target.api_key, None);
574 assert_eq!(data.target.url.path(), "/foo/bar");
575
576 for (key, value) in &HeaderMap::from(header_tags) {
577 assert_eq!(data.headers.get(key), Some(value));
578 }
579 }
580
581 #[cfg_attr(miri, ignore)]
582 #[tokio::test]
583 async fn request_protobuf() {
584 let server = MockServer::start_async().await;
585
586 let mock = server
587 .mock_async(|when, then| {
588 when.method(POST)
589 .header("Content-type", "application/x-protobuf")
590 .header("DD-API-KEY", "TEST-KEY")
591 .path("/");
592 then.status(202).body("");
593 })
594 .await;
595
596 let header_tags = TracerHeaderTags::default();
597
598 let payload = setup_payload(&header_tags);
599 let data = SendData::new(
600 100,
601 TracerPayloadCollection::V07(vec![payload.clone()]),
602 header_tags,
603 &Endpoint {
604 api_key: Some(std::borrow::Cow::Borrowed("TEST-KEY")),
605 url: server.url("/").parse::<hyper::Uri>().unwrap(),
606 timeout_ms: ONE_SECOND,
607 ..Endpoint::default()
608 },
609 );
610
611 let data_payload_len = compute_payload_len(&data.tracer_payloads);
612 let res = data.send(&NativeCapabilities::new_client()).await;
613
614 mock.assert_async().await;
615
616 assert_eq!(
617 res.last_result.unwrap().status(),
618 http::StatusCode::ACCEPTED
619 );
620 assert_eq!(res.errors_timeout, 0);
621 assert_eq!(res.errors_network, 0);
622 assert_eq!(res.errors_status_code, 0);
623 assert_eq!(res.requests_count, 1);
624 assert_eq!(res.chunks_sent, 1);
625 assert_eq!(res.bytes_sent, data_payload_len as u64);
626 assert_eq!(*res.responses_count_per_code.get(&202).unwrap(), 1_u64);
627 }
628
629 #[cfg_attr(miri, ignore)]
630 #[tokio::test]
631 async fn request_protobuf_several_payloads() {
632 let server = MockServer::start_async().await;
633
634 let mock = server
635 .mock_async(|when, then| {
636 when.method(POST)
637 .header("Content-type", "application/x-protobuf")
638 .header("DD-API-KEY", "TEST-KEY")
639 .path("/");
640 then.status(202).body("");
641 })
642 .await;
643
644 let header_tags = TracerHeaderTags::default();
645
646 let payload = setup_payload(&header_tags);
647 let data = SendData::new(
648 100,
649 TracerPayloadCollection::V07(vec![payload.clone(), payload.clone()]),
650 header_tags,
651 &Endpoint {
652 api_key: Some(std::borrow::Cow::Borrowed("TEST-KEY")),
653 url: server.url("/").parse::<hyper::Uri>().unwrap(),
654 timeout_ms: ONE_SECOND,
655 ..Endpoint::default()
656 },
657 );
658
659 let data_payload_len = compute_payload_len(&data.tracer_payloads);
660 let res = data.send(&NativeCapabilities::new_client()).await;
661
662 mock.assert_async().await;
663
664 assert_eq!(
665 res.last_result.unwrap().status(),
666 http::StatusCode::ACCEPTED
667 );
668 assert_eq!(res.errors_timeout, 0);
669 assert_eq!(res.errors_network, 0);
670 assert_eq!(res.errors_status_code, 0);
671 assert_eq!(res.requests_count, 1);
672 assert_eq!(res.chunks_sent, 2);
673 assert_eq!(res.bytes_sent, data_payload_len as u64);
674 assert_eq!(*res.responses_count_per_code.get(&202).unwrap(), 1_u64);
675 }
676
677 #[cfg_attr(miri, ignore)]
678 #[tokio::test]
679 async fn request_msgpack_v07() {
680 let server = MockServer::start_async().await;
681
682 let header_tags = HEADER_TAGS;
683 let mock = server
684 .mock_async(|when, then| {
685 when.method(POST)
686 .header(DATADOG_TRACE_COUNT.as_str(), "1")
687 .header("Content-type", "application/msgpack")
688 .header("datadog-meta-lang", header_tags.lang)
689 .header(
690 "datadog-meta-lang-interpreter",
691 header_tags.lang_interpreter,
692 )
693 .header("datadog-meta-lang-version", header_tags.lang_version)
694 .header(
695 "datadog-meta-lang-interpreter-vendor",
696 header_tags.lang_vendor,
697 )
698 .header("datadog-meta-tracer-version", header_tags.tracer_version)
699 .header("datadog-container-id", header_tags.container_id)
700 .header("Datadog-Send-Real-Http-Status", "1")
701 .path("/");
702 then.status(200).body("");
703 })
704 .await;
705
706 let header_tags = HEADER_TAGS;
707
708 let payload = setup_payload(&header_tags);
709 let data = SendData::new(
710 100,
711 TracerPayloadCollection::V07(vec![payload.clone()]),
712 header_tags,
713 &Endpoint {
714 api_key: None,
715 url: server.url("/").parse::<hyper::Uri>().unwrap(),
716 timeout_ms: ONE_SECOND,
717 ..Endpoint::default()
718 },
719 );
720
721 let data_payload_len = rmp_compute_payload_len(&data.tracer_payloads);
722 let res = data.send(&NativeCapabilities::new_client()).await;
723
724 mock.assert_async().await;
725
726 assert_eq!(res.last_result.unwrap().status(), http::StatusCode::OK);
727 assert_eq!(res.errors_timeout, 0);
728 assert_eq!(res.errors_network, 0);
729 assert_eq!(res.errors_status_code, 0);
730 assert_eq!(res.requests_count, 1);
731 assert_eq!(res.chunks_sent, 1);
732 assert_eq!(res.bytes_sent, data_payload_len as u64);
733 assert_eq!(*res.responses_count_per_code.get(&200).unwrap(), 1_u64);
734 }
735
736 #[cfg_attr(miri, ignore)]
737 #[tokio::test]
738 async fn request_msgpack_v04() {
739 let server = MockServer::start_async().await;
740
741 let header_tags = HEADER_TAGS;
742 let mock = server
743 .mock_async(|when, then| {
744 when.method(POST)
745 .header(DATADOG_TRACE_COUNT.as_str(), "1")
746 .header("Content-type", "application/msgpack")
747 .header("datadog-meta-lang", header_tags.lang)
748 .header(
749 "datadog-meta-lang-interpreter",
750 header_tags.lang_interpreter,
751 )
752 .header("datadog-meta-lang-version", header_tags.lang_version)
753 .header(
754 "datadog-meta-lang-interpreter-vendor",
755 header_tags.lang_vendor,
756 )
757 .header("datadog-meta-tracer-version", header_tags.tracer_version)
758 .header("datadog-container-id", header_tags.container_id)
759 .path("/");
760 then.status(200).body("");
761 })
762 .await;
763
764 let header_tags = HEADER_TAGS;
765
766 let trace = vec![create_test_no_alloc_span(1234, 12342, 12341, 1, false)];
767 let data = SendData::new(
768 100,
769 TracerPayloadCollection::V04(vec![trace.clone()]),
770 header_tags,
771 &Endpoint {
772 api_key: None,
773 url: server.url("/").parse::<hyper::Uri>().unwrap(),
774 timeout_ms: ONE_SECOND,
775 ..Endpoint::default()
776 },
777 );
778
779 let data_payload_len = rmp_compute_payload_len(&data.tracer_payloads);
780 let res = data.send(&NativeCapabilities::new_client()).await;
781
782 mock.assert_async().await;
783
784 assert_eq!(res.last_result.unwrap().status(), http::StatusCode::OK);
785 assert_eq!(res.errors_timeout, 0);
786 assert_eq!(res.errors_network, 0);
787 assert_eq!(res.errors_status_code, 0);
788 assert_eq!(res.requests_count, 1);
789 assert_eq!(res.chunks_sent, 1);
790 assert_eq!(res.bytes_sent, data_payload_len as u64);
791 assert_eq!(*res.responses_count_per_code.get(&200).unwrap(), 1_u64);
792 }
793
794 #[cfg_attr(miri, ignore)]
795 #[tokio::test]
796 async fn request_msgpack_several_payloads() {
797 let server = MockServer::start_async().await;
798
799 let mock = server
800 .mock_async(|when, then| {
801 when.method(POST)
802 .header("Content-type", "application/msgpack")
803 .path("/");
804 then.status(200).body("");
805 })
806 .await;
807
808 let header_tags = TracerHeaderTags::default();
809
810 let payload = setup_payload(&header_tags);
811 let data = SendData::new(
812 100,
813 TracerPayloadCollection::V07(vec![payload.clone(), payload.clone()]),
814 header_tags,
815 &Endpoint {
816 api_key: None,
817 url: server.url("/").parse::<hyper::Uri>().unwrap(),
818 timeout_ms: ONE_SECOND,
819 ..Endpoint::default()
820 },
821 );
822
823 let data_payload_len = rmp_compute_payload_len(&data.tracer_payloads);
824 let res = data.send(&NativeCapabilities::new_client()).await;
825
826 mock.assert_calls_async(2).await;
827
828 assert_eq!(res.last_result.unwrap().status(), http::StatusCode::OK);
829 assert_eq!(res.errors_timeout, 0);
830 assert_eq!(res.errors_network, 0);
831 assert_eq!(res.errors_status_code, 0);
832 assert_eq!(res.requests_count, 2);
833 assert_eq!(res.chunks_sent, 2);
834 assert_eq!(res.bytes_sent, data_payload_len as u64);
835 assert_eq!(*res.responses_count_per_code.get(&200).unwrap(), 2_u64);
836 }
837
838 #[cfg_attr(miri, ignore)]
839 #[tokio::test]
840 async fn request_error_status_code() {
841 let server = MockServer::start_async().await;
842
843 let mock = server
844 .mock_async(|when, then| {
845 when.method(POST)
846 .header("Content-type", "application/msgpack")
847 .path("/");
848 then.status(500).body("");
849 })
850 .await;
851
852 let payload = setup_payload(&HEADER_TAGS);
853 let data = SendData::new(
854 100,
855 TracerPayloadCollection::V07(vec![payload]),
856 HEADER_TAGS,
857 &Endpoint {
858 api_key: None,
859 url: server.url("/").parse::<hyper::Uri>().unwrap(),
860 timeout_ms: ONE_SECOND,
861 ..Endpoint::default()
862 },
863 );
864
865 let res = data.send(&NativeCapabilities::new_client()).await;
866
867 mock.assert_calls_async(6).await;
868
869 assert!(res.last_result.is_ok());
870 assert_eq!(
871 res.last_result.unwrap().status(),
872 http::StatusCode::INTERNAL_SERVER_ERROR
873 );
874 assert_eq!(res.errors_timeout, 0);
875 assert_eq!(res.errors_network, 0);
876 assert_eq!(res.errors_status_code, 1);
877 assert_eq!(res.requests_count, 6);
878 assert_eq!(res.chunks_sent, 0);
879 assert_eq!(res.bytes_sent, 0);
880 assert_eq!(*res.responses_count_per_code.get(&500).unwrap(), 1_u64);
881 }
882
883 #[cfg_attr(miri, ignore)]
884 #[tokio::test]
885 async fn request_error_network() {
886 let payload = setup_payload(&HEADER_TAGS);
888 let data = SendData::new(
889 100,
890 TracerPayloadCollection::V07(vec![payload]),
891 HEADER_TAGS,
892 &Endpoint {
893 api_key: None,
894 url: "http://127.0.0.1:4321/".parse::<hyper::Uri>().unwrap(),
895 timeout_ms: ONE_SECOND,
896 ..Endpoint::default()
897 },
898 );
899
900 let res = data.send(&NativeCapabilities::new_client()).await;
901
902 assert!(res.last_result.is_err());
903 match std::env::consts::OS {
904 "windows" => {
905 assert_eq!(res.errors_timeout, 1);
909 assert_eq!(res.errors_network, 0);
910 }
911 _ => {
912 assert_eq!(res.errors_timeout, 0);
913 assert_eq!(res.errors_network, 1);
914 }
915 }
916 assert_eq!(res.errors_status_code, 0);
917 assert_eq!(res.requests_count, 6);
918 assert_eq!(res.errors_status_code, 0);
919 assert_eq!(res.chunks_sent, 0);
920 assert_eq!(res.bytes_sent, 0);
921 assert_eq!(res.responses_count_per_code.len(), 0);
922 }
923
924 #[cfg_attr(miri, ignore)]
925 #[tokio::test]
926 async fn request_error_timeout_v04() {
927 let server = MockServer::start_async().await;
928
929 let header_tags = HEADER_TAGS;
930 let mock = server
931 .mock_async(|when, then| {
932 when.method(POST)
933 .header(DATADOG_TRACE_COUNT.as_str(), "2")
934 .header("Content-type", "application/msgpack")
935 .header("datadog-meta-lang", header_tags.lang)
936 .header(
937 "datadog-meta-lang-interpreter",
938 header_tags.lang_interpreter,
939 )
940 .header("datadog-meta-lang-version", header_tags.lang_version)
941 .header(
942 "datadog-meta-lang-interpreter-vendor",
943 header_tags.lang_vendor,
944 )
945 .header("datadog-meta-tracer-version", header_tags.tracer_version)
946 .header("datadog-container-id", header_tags.container_id)
947 .path("/");
948 then.status(200)
949 .body("")
950 .delay(Duration::from_millis(1_000));
951 })
952 .await;
953
954 let header_tags = HEADER_TAGS;
955
956 let trace = vec![create_test_no_alloc_span(1234, 12342, 12341, 1, false)];
957 let mut data = SendData::new(
958 100,
959 TracerPayloadCollection::V04(vec![trace.clone(), trace.clone()]),
960 header_tags,
961 &Endpoint {
962 api_key: None,
963 url: server.url("/").parse::<hyper::Uri>().unwrap(),
964 timeout_ms: 500,
965 ..Endpoint::default()
966 },
967 );
968
969 data.set_retry_strategy(RetryStrategy::new(5, 100, RetryBackoffType::Constant, None));
972
973 let res = data.send(&NativeCapabilities::new_client()).await;
974
975 mock.assert_calls_async(6).await;
976
977 assert_eq!(res.errors_timeout, 1);
978 assert_eq!(res.errors_network, 0);
979 assert_eq!(res.errors_status_code, 0);
980 assert_eq!(res.requests_count, 6);
981 assert_eq!(res.chunks_sent, 0);
982 assert_eq!(res.bytes_sent, 0);
983 assert_eq!(res.responses_count_per_code.len(), 0);
984 }
985
986 #[cfg_attr(miri, ignore)]
987 #[tokio::test]
988 async fn request_error_timeout_v07() {
989 let server = MockServer::start_async().await;
990
991 let mock = server
992 .mock_async(|when, then| {
993 when.method(POST)
994 .header("Content-type", "application/msgpack")
995 .path("/");
996 then.status(200)
997 .body("")
998 .delay(Duration::from_millis(1_000));
999 })
1000 .await;
1001
1002 let header_tags = TracerHeaderTags::default();
1003
1004 let payload = setup_payload(&header_tags);
1005 let mut data = SendData::new(
1006 100,
1007 TracerPayloadCollection::V07(vec![payload.clone(), payload.clone()]),
1008 header_tags,
1009 &Endpoint {
1010 api_key: None,
1011 url: server.url("/").parse::<hyper::Uri>().unwrap(),
1012 timeout_ms: 500,
1013 ..Endpoint::default()
1014 },
1015 );
1016
1017 data.set_retry_strategy(RetryStrategy::new(5, 100, RetryBackoffType::Constant, None));
1020
1021 let res = data.send(&NativeCapabilities::new_client()).await;
1022
1023 mock.assert_calls_async(12).await;
1024
1025 assert_eq!(res.errors_timeout, 1);
1026 assert_eq!(res.errors_network, 0);
1027 assert_eq!(res.errors_status_code, 0);
1028 assert_eq!(res.requests_count, 6);
1029 assert_eq!(res.chunks_sent, 0);
1030 assert_eq!(res.bytes_sent, 0);
1031 assert_eq!(res.responses_count_per_code.len(), 0);
1032 }
1033
1034 #[test]
1035 fn test_builder() {
1036 let header_tags = HEADER_TAGS;
1037 let payload = setup_payload(&header_tags);
1038 let retry_strategy = RetryStrategy::new(5, 100, RetryBackoffType::Constant, None);
1039
1040 let send_data = SendDataBuilder::new(
1041 100,
1042 TracerPayloadCollection::V07(vec![payload]),
1043 header_tags,
1044 &Endpoint::default(),
1045 )
1046 .with_api_key("TEST-KEY")
1048 .with_retry_strategy(retry_strategy.clone())
1050 .build();
1051
1052 assert_eq!(
1053 send_data.target.api_key,
1054 Some(std::borrow::Cow::Borrowed("TEST-KEY"))
1055 );
1056 assert_eq!(send_data.retry_strategy, retry_strategy);
1057 }
1058}