Skip to content

Commit 373b778

Browse files
authored
Allow send empty arrays through the trace exporter (#796)
* Allow send empty arrays in order to support agent availability checking for some tracers. * Ignore miri in test whre httpmock is used because it can't call libc::socket foreign function.
1 parent 16c13b5 commit 373b778

File tree

4 files changed

+163
-7
lines changed

4 files changed

+163
-7
lines changed

Cargo.lock

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

data-pipeline-ffi/Cargo.toml

+5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ cbindgen = ["build_common/cbindgen", "ddcommon-ffi/cbindgen"]
2121
[build-dependencies]
2222
build_common = { path = "../build-common" }
2323

24+
[dev-dependencies]
25+
httpmock = "0.7.0"
26+
rmp-serde = "1.1.1"
27+
datadog-trace-utils = { path = "../trace-utils" }
28+
2429
[dependencies]
2530
data-pipeline = { path = "../data-pipeline" }
2631
ddcommon-ffi = { path = "../ddcommon-ffi", default-features = false }

data-pipeline-ffi/src/trace_exporter.rs

+120
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,9 @@ mod tests {
318318
use super::*;
319319
use crate::error::ddog_trace_exporter_error_free;
320320
use crate::trace_exporter::AgentResponse;
321+
use datadog_trace_utils::span_v04::Span;
322+
use httpmock::prelude::*;
323+
use httpmock::MockServer;
321324
use std::{borrow::Borrow, mem::MaybeUninit};
322325

323326
#[test]
@@ -608,4 +611,121 @@ mod tests {
608611
assert_eq!(error.unwrap().code, ErrorCode::InvalidInput);
609612
}
610613
}
614+
615+
#[test]
616+
// Ignore because it seems, at least in the version we're currently using, miri can't emulate
617+
// libc::socket function.
618+
#[cfg_attr(miri, ignore)]
619+
fn exporter_send_check_rate_test() {
620+
unsafe {
621+
let server = MockServer::start();
622+
623+
let _mock = server.mock(|when, then| {
624+
when.method(POST)
625+
.header("Content-type", "application/msgpack")
626+
.path("/v0.4/traces");
627+
then.status(200).body(
628+
r#"{
629+
"rate_by_service": {
630+
"service:foo,env:staging": 1.0,
631+
"service:,env:": 0.8
632+
}
633+
}"#,
634+
);
635+
});
636+
637+
let cfg = TraceExporterConfig {
638+
url: Some(server.url("/")),
639+
tracer_version: Some("0.1".to_string()),
640+
language: Some("lang".to_string()),
641+
language_version: Some("0.1".to_string()),
642+
language_interpreter: Some("interpreter".to_string()),
643+
hostname: Some("hostname".to_string()),
644+
env: Some("env-test".to_string()),
645+
version: Some("1.0".to_string()),
646+
service: Some("test-service".to_string()),
647+
input_format: TraceExporterInputFormat::V04,
648+
output_format: TraceExporterOutputFormat::V04,
649+
compute_stats: false,
650+
};
651+
652+
let mut ptr: MaybeUninit<Box<TraceExporter>> = MaybeUninit::uninit();
653+
let mut ret =
654+
ddog_trace_exporter_new(NonNull::new_unchecked(&mut ptr).cast(), Some(&cfg));
655+
656+
let exporter = ptr.assume_init();
657+
658+
assert_eq!(ret, None);
659+
660+
let data = rmp_serde::to_vec_named::<Vec<Vec<Span>>>(&vec![vec![]]).unwrap();
661+
let traces = ByteSlice::new(&data);
662+
let mut response = AgentResponse { rate: 0.0 };
663+
664+
ret = ddog_trace_exporter_send(Some(exporter.as_ref()), traces, 0, Some(&mut response));
665+
assert_eq!(ret, None);
666+
assert_eq!(response.rate, 0.8);
667+
668+
ddog_trace_exporter_free(exporter);
669+
}
670+
}
671+
672+
#[test]
673+
// Ignore because it seems, at least in the version we're currently using, miri can't emulate
674+
// libc::socket function.
675+
#[cfg_attr(miri, ignore)]
676+
fn exporter_send_empty_array_test() {
677+
// Test added due to ensure the exporter is able to send empty arrays because some tracers
678+
// (.NET) ping the agent with the aforementioned data type.
679+
unsafe {
680+
let server = MockServer::start();
681+
682+
let mock_traces = server.mock(|when, then| {
683+
when.method(POST)
684+
.header("Content-type", "application/msgpack")
685+
.path("/v0.4/traces");
686+
then.status(200).body(
687+
r#"{
688+
"rate_by_service": {
689+
"service:foo,env:staging": 1.0,
690+
"service:,env:": 0.8
691+
}
692+
}"#,
693+
);
694+
});
695+
696+
let cfg = TraceExporterConfig {
697+
url: Some(server.url("/")),
698+
tracer_version: Some("0.1".to_string()),
699+
language: Some("lang".to_string()),
700+
language_version: Some("0.1".to_string()),
701+
language_interpreter: Some("interpreter".to_string()),
702+
hostname: Some("hostname".to_string()),
703+
env: Some("env-test".to_string()),
704+
version: Some("1.0".to_string()),
705+
service: Some("test-service".to_string()),
706+
input_format: TraceExporterInputFormat::V04,
707+
output_format: TraceExporterOutputFormat::V04,
708+
compute_stats: false,
709+
};
710+
711+
let mut ptr: MaybeUninit<Box<TraceExporter>> = MaybeUninit::uninit();
712+
let mut ret =
713+
ddog_trace_exporter_new(NonNull::new_unchecked(&mut ptr).cast(), Some(&cfg));
714+
715+
let exporter = ptr.assume_init();
716+
717+
assert_eq!(ret, None);
718+
719+
let data = vec![0x90];
720+
let traces = ByteSlice::new(&data);
721+
let mut response = AgentResponse { rate: 0.0 };
722+
723+
ret = ddog_trace_exporter_send(Some(exporter.as_ref()), traces, 0, Some(&mut response));
724+
mock_traces.assert();
725+
assert_eq!(ret, None);
726+
assert_eq!(response.rate, 0.8);
727+
728+
ddog_trace_exporter_free(exporter);
729+
}
730+
}
611731
}

data-pipeline/src/trace_exporter/mod.rs

+35-7
Original file line numberDiff line numberDiff line change
@@ -590,13 +590,6 @@ impl TraceExporter {
590590
}
591591
};
592592

593-
if traces.is_empty() {
594-
error!("No traces deserialized from the request body.");
595-
return Err(TraceExporterError::Io(std::io::Error::from(
596-
std::io::ErrorKind::InvalidInput,
597-
)));
598-
}
599-
600593
let num_traces = traces.len();
601594

602595
self.emit_metric(
@@ -1531,6 +1524,41 @@ mod tests {
15311524
assert_eq!(result, AgentResponse::from(0.8));
15321525
}
15331526

1527+
#[test]
1528+
#[cfg_attr(miri, ignore)]
1529+
fn agent_response_empty_array() {
1530+
let server = MockServer::start();
1531+
let _agent = server.mock(|_, then| {
1532+
then.status(200)
1533+
.header("content-type", "application/json")
1534+
.body(
1535+
r#"{
1536+
"rate_by_service": {
1537+
"service:foo,env:staging": 1.0,
1538+
"service:,env:": 0.8
1539+
}
1540+
}"#,
1541+
);
1542+
});
1543+
1544+
let exporter = TraceExporterBuilder::default()
1545+
.set_url(&server.url("/"))
1546+
.set_service("foo")
1547+
.set_env("foo-env")
1548+
.set_tracer_version("v0.1")
1549+
.set_language("nodejs")
1550+
.set_language_version("1.0")
1551+
.set_language_interpreter("v8")
1552+
.build()
1553+
.unwrap();
1554+
1555+
let traces = vec![0x90];
1556+
let bytes = tinybytes::Bytes::from(traces);
1557+
let result = exporter.send(bytes, 1).unwrap();
1558+
1559+
assert_eq!(result, AgentResponse::from(0.8));
1560+
}
1561+
15341562
#[test]
15351563
#[cfg_attr(miri, ignore)]
15361564
fn builder_error() {

0 commit comments

Comments
 (0)