Skip to content

Commit cd97ee1

Browse files
committed
Implement datadog v0.7 traces api
1 parent e8a88d9 commit cd97ee1

File tree

4 files changed

+720
-34
lines changed

4 files changed

+720
-34
lines changed

opentelemetry-datadog/benches/datadog_exporter.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -195,10 +195,10 @@ fn generate_traces(number_of_traces: usize, spans_per_trace: usize) -> Vec<SpanD
195195
result
196196
}
197197

198-
fn criterion_benchmark(c: &mut Criterion) {
198+
fn bench_export(c: &mut Criterion, api_version: ApiVersion) {
199199
let exporter = new_pipeline()
200200
.with_service_name("trace-demo")
201-
.with_api_version(ApiVersion::Version05)
201+
.with_api_version(api_version)
202202
.with_http_client(DummyClient)
203203
.build_exporter()
204204
.unwrap();
@@ -210,11 +210,22 @@ fn criterion_benchmark(c: &mut Criterion) {
210210
let data_ref = &data;
211211

212212
c.bench_function(
213-
format!("export {number_of_traces} traces with {spans_per_trace} spans").as_str(),
213+
format!(
214+
"export {number_of_traces} traces with {spans_per_trace} spans for {api_version:?}"
215+
)
216+
.as_str(),
214217
|b| b.iter(|| exporter.export(black_box(data_ref.clone()))),
215218
);
216219
}
217220
}
218221

219-
criterion_group!(benches, criterion_benchmark);
222+
fn bench_export_v05(c: &mut Criterion) {
223+
bench_export(c, ApiVersion::Version05);
224+
}
225+
226+
fn bench_export_v07(c: &mut Criterion) {
227+
bench_export(c, ApiVersion::Version07);
228+
}
229+
230+
criterion_group!(benches, bench_export_v05, bench_export_v07);
220231
criterion_main!(benches);

opentelemetry-datadog/src/exporter/model/mod.rs

Lines changed: 161 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
use crate::exporter::ModelConfig;
1+
use crate::{exporter::ModelConfig, DatadogTraceState};
22
use http::uri;
3+
use opentelemetry::Value;
34
use opentelemetry_sdk::{
45
trace::{self, SpanData},
56
ExportError, Resource,
@@ -14,15 +15,38 @@ use super::Mapping;
1415
pub mod unified_tags;
1516
mod v03;
1617
mod v05;
18+
mod v07;
1719

1820
// todo: we should follow the same mapping defined in https://github.com/DataDog/datadog-agent/blob/main/pkg/trace/api/otlp.go
1921

2022
// https://github.com/DataDog/dd-trace-js/blob/c89a35f7d27beb4a60165409376e170eacb194c5/packages/dd-trace/src/constants.js#L4
2123
static SAMPLING_PRIORITY_KEY: &str = "_sampling_priority_v1";
2224

25+
#[cfg(not(feature = "agent-sampling"))]
26+
fn get_sampling_priority(_span: &SpanData) -> f64 {
27+
1.0
28+
}
29+
30+
#[cfg(feature = "agent-sampling")]
31+
fn get_sampling_priority(span: &SpanData) -> f64 {
32+
if span.span_context.trace_state().priority_sampling_enabled() {
33+
1.0
34+
} else {
35+
0.0
36+
}
37+
}
38+
2339
// https://github.com/DataDog/datadog-agent/blob/ec96f3c24173ec66ba235bda7710504400d9a000/pkg/trace/traceutil/span.go#L20
2440
static DD_MEASURED_KEY: &str = "_dd.measured";
2541

42+
fn get_measuring(span: &SpanData) -> f64 {
43+
if span.span_context.trace_state().measuring_enabled() {
44+
1.0
45+
} else {
46+
0.0
47+
}
48+
}
49+
2650
/// Custom mapping between opentelemetry spans and datadog spans.
2751
///
2852
/// User can provide custom function to change the mapping. It currently supports customizing the following
@@ -77,6 +101,16 @@ fn default_resource_mapping<'a>(span: &'a SpanData, _config: &'a ModelConfig) ->
77101
span.name.as_ref()
78102
}
79103

104+
fn get_span_type(span: &SpanData) -> Option<&Value> {
105+
for kv in &span.attributes {
106+
if kv.key.as_str() == "span.type" {
107+
return Some(&kv.value);
108+
}
109+
}
110+
111+
None
112+
}
113+
80114
/// Wrap type for errors from opentelemetry datadog exporter
81115
#[derive(Debug, thiserror::Error)]
82116
pub enum Error {
@@ -129,20 +163,24 @@ pub enum ApiVersion {
129163
Version03,
130164
/// Version 0.5 - requires datadog-agent v7.22.0 or above
131165
Version05,
166+
/// Version 0.7
167+
Version07,
132168
}
133169

134170
impl ApiVersion {
135171
pub(crate) fn path(self) -> &'static str {
136172
match self {
137173
ApiVersion::Version03 => "/v0.3/traces",
138174
ApiVersion::Version05 => "/v0.5/traces",
175+
ApiVersion::Version07 => "/v0.7/traces",
139176
}
140177
}
141178

142179
pub(crate) fn content_type(self) -> &'static str {
143180
match self {
144181
ApiVersion::Version03 => "application/msgpack",
145182
ApiVersion::Version05 => "application/msgpack",
183+
ApiVersion::Version07 => "application/msgpack",
146184
}
147185
}
148186

@@ -190,6 +228,24 @@ impl ApiVersion {
190228
unified_tags,
191229
resource,
192230
),
231+
Self::Version07 => v07::encode(
232+
model_config,
233+
traces,
234+
|span, config| match &mapping.service_name {
235+
Some(f) => f(span, config),
236+
None => default_service_name_mapping(span, config),
237+
},
238+
|span, config| match &mapping.name {
239+
Some(f) => f(span, config),
240+
None => default_name_mapping(span, config),
241+
},
242+
|span, config| match &mapping.resource {
243+
Some(f) => f(span, config),
244+
None => default_resource_mapping(span, config),
245+
},
246+
unified_tags,
247+
resource,
248+
),
193249
}
194250
}
195251
}
@@ -198,6 +254,7 @@ impl ApiVersion {
198254
pub(crate) mod tests {
199255
use super::*;
200256
use base64::{engine::general_purpose::STANDARD, Engine};
257+
use opentelemetry::trace::Event;
201258
use opentelemetry::InstrumentationScope;
202259
use opentelemetry::{
203260
trace::{SpanContext, SpanId, SpanKind, Status, TraceFlags, TraceId, TraceState},
@@ -213,7 +270,43 @@ pub(crate) mod tests {
213270
vec![vec![get_span(7, 1, 99)]]
214271
}
215272

273+
fn get_traces_with_events() -> Vec<Vec<trace::SpanData>> {
274+
let event = Event::new(
275+
"myevent",
276+
SystemTime::UNIX_EPOCH
277+
.checked_add(Duration::from_secs(5))
278+
.unwrap(),
279+
vec![
280+
KeyValue::new("mykey", 1),
281+
KeyValue::new(
282+
"myarray",
283+
Value::Array(opentelemetry::Array::String(vec![
284+
"myvalue1".into(),
285+
"myvalue2".into(),
286+
])),
287+
),
288+
KeyValue::new("mybool", true),
289+
KeyValue::new("myint", 2.5),
290+
KeyValue::new("myboolfalse", false),
291+
],
292+
0,
293+
);
294+
let mut events = SpanEvents::default();
295+
events.events.push(event);
296+
297+
vec![vec![get_span_with_events(7, 1, 99, events)]]
298+
}
299+
216300
pub(crate) fn get_span(trace_id: u128, parent_span_id: u64, span_id: u64) -> trace::SpanData {
301+
get_span_with_events(trace_id, parent_span_id, span_id, SpanEvents::default())
302+
}
303+
304+
pub(crate) fn get_span_with_events(
305+
trace_id: u128,
306+
parent_span_id: u64,
307+
span_id: u64,
308+
events: SpanEvents,
309+
) -> trace::SpanData {
217310
let span_context = SpanContext::new(
218311
TraceId::from_u128(trace_id),
219312
SpanId::from_u64(span_id),
@@ -226,7 +319,6 @@ pub(crate) mod tests {
226319
let end_time = start_time.checked_add(Duration::from_secs(1)).unwrap();
227320

228321
let attributes = vec![KeyValue::new("span.type", "web")];
229-
let events = SpanEvents::default();
230322
let links = SpanLinks::default();
231323
let instrumentation_scope = InstrumentationScope::builder("component").build();
232324

@@ -305,4 +397,71 @@ pub(crate) mod tests {
305397

306398
Ok(())
307399
}
400+
401+
#[test]
402+
fn test_encode_v07() {
403+
let traces = get_traces_with_events();
404+
let model_config = ModelConfig {
405+
service_name: "service_name".to_string(),
406+
..Default::default()
407+
};
408+
409+
// we use an empty builder with a single attribute because the attributes are in a hashmap
410+
// which causes the order to change every test
411+
let resource = Resource::builder_empty()
412+
.with_attribute(KeyValue::new("host.name", "test"))
413+
.build();
414+
415+
let mut unified_tags = UnifiedTags::new();
416+
unified_tags.set_env(Some(String::from("test-env")));
417+
unified_tags.set_version(Some(String::from("test-version")));
418+
unified_tags.set_service(Some(String::from("test-service")));
419+
420+
let encoded = STANDARD.encode(
421+
ApiVersion::Version07
422+
.encode(
423+
&model_config,
424+
traces.iter().map(|x| &x[..]).collect(),
425+
&Mapping::empty(),
426+
&unified_tags,
427+
Some(&resource),
428+
)
429+
.unwrap(),
430+
);
431+
432+
// A very nice way to check the encoded values is to use
433+
// https://github.com/DataDog/dd-apm-test-agent
434+
// Which is a test http server that receives and validates sent traces
435+
let expected = "ha1sYW5ndWFnZV9uYW1lpHJ1c3SmY2h1bmtzkYOocHJpb3JpdHnSAAAAAaZvcmlnaW6gpXNwY\
436+
W5zkY6kbmFtZaljb21wb25lbnSnc3Bhbl9pZM8AAAAAAAAAY6h0cmFjZV9pZM8AAAAAAAAAB6VzdGFydNMAAAAAAAAAAKhk\
437+
dXJhdGlvbtMAAAAAO5rKAKlwYXJlbnRfaWTPAAAAAAAAAAGnc2VydmljZaxzZXJ2aWNlX25hbWWocmVzb3VyY2WocmVzb3V\
438+
yY2WkdHlwZaN3ZWKlZXJyb3LSAAAAAKRtZXRhgqlob3N0Lm5hbWWkdGVzdKlzcGFuLnR5cGWjd2Vip21ldHJpY3OCtV9zYW\
439+
1wbGluZ19wcmlvcml0eV92Mcs/8AAAAAAAAKxfZGQubWVhc3VyZWTLAAAAAAAAAACqc3Bhbl9saW5rc5Crc3Bhbl9ldmVud\
440+
HORg6RuYW1lp215ZXZlbnSudGltZV91bml4X25hbm/TAAAAASoF8gCqYXR0cmlidXRlc4WlbXlrZXmCpHR5cGXSAAAAAqlp\
441+
bnRfdmFsdWXTAAAAAAAAAAGnbXlhcnJheYKkdHlwZdIAAAAEq2FycmF5X3ZhbHVlkoKkdHlwZQCsc3RyaW5nX3ZhbHVlqG1\
442+
5dmFsdWUxgqR0eXBlAKxzdHJpbmdfdmFsdWWobXl2YWx1ZTKmbXlib29sgqR0eXBl0gAAAAGqYm9vbF92YWx1ZcOlbXlpbn\
443+
SCpHR5cGXSAAAAA6xkb3VibGVfdmFsdWXLQAQAAAAAAACrbXlib29sZmFsc2WCpHR5cGXSAAAAAapib29sX3ZhbHVlwqR0Y\
444+
Wdzg6dzZXJ2aWNlrHRlc3Qtc2VydmljZad2ZXJzaW9urHRlc3QtdmVyc2lvbqNlbnaodGVzdC1lbnajZW52qHRlc3QtZW52\
445+
q2FwcF92ZXJzaW9urHRlc3QtdmVyc2lvbg==";
446+
assert_eq!(encoded.as_str(), expected);
447+
448+
// change to a different resource and make sure the encoded value changes and that we actually encode stuff
449+
let other_resource = Resource::builder_empty()
450+
.with_attribute(KeyValue::new("host.name", "thisissometingelse"))
451+
.build();
452+
453+
let encoded = STANDARD.encode(
454+
ApiVersion::Version07
455+
.encode(
456+
&model_config,
457+
traces.iter().map(|x| &x[..]).collect(),
458+
&Mapping::empty(),
459+
&unified_tags,
460+
Some(&other_resource),
461+
)
462+
.unwrap(),
463+
);
464+
465+
assert_ne!(encoded.as_str(), expected);
466+
}
308467
}

opentelemetry-datadog/src/exporter/model/v05.rs

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
use crate::exporter::intern::StringInterner;
22
use crate::exporter::model::{DD_MEASURED_KEY, SAMPLING_PRIORITY_KEY};
33
use crate::exporter::{Error, ModelConfig};
4-
use crate::propagator::DatadogTraceState;
54
use opentelemetry::trace::Status;
65
use opentelemetry_sdk::trace::SpanData;
76
use opentelemetry_sdk::Resource;
87
use std::time::SystemTime;
98

109
use super::unified_tags::{UnifiedTagField, UnifiedTags};
10+
use super::{get_measuring, get_sampling_priority, get_span_type};
1111

1212
const SPAN_NUM_ELEMENTS: u32 = 12;
1313
const METRICS_LEN: u32 = 2;
@@ -127,28 +127,6 @@ fn write_unified_tag<'a>(
127127
Ok(())
128128
}
129129

130-
#[cfg(not(feature = "agent-sampling"))]
131-
fn get_sampling_priority(_span: &SpanData) -> f64 {
132-
1.0
133-
}
134-
135-
#[cfg(feature = "agent-sampling")]
136-
fn get_sampling_priority(span: &SpanData) -> f64 {
137-
if span.span_context.trace_state().priority_sampling_enabled() {
138-
1.0
139-
} else {
140-
0.0
141-
}
142-
}
143-
144-
fn get_measuring(span: &SpanData) -> f64 {
145-
if span.span_context.trace_state().measuring_enabled() {
146-
1.0
147-
} else {
148-
0.0
149-
}
150-
}
151-
152130
#[allow(clippy::too_many_arguments)]
153131
fn encode_traces<'interner, S, N, R>(
154132
interner: &mut StringInterner<'interner>,
@@ -186,11 +164,8 @@ where
186164
.unwrap_or(0);
187165

188166
let mut span_type = interner.intern("");
189-
for kv in &span.attributes {
190-
if kv.key.as_str() == "span.type" {
191-
span_type = interner.intern_value(&kv.value);
192-
break;
193-
}
167+
if let Some(value) = get_span_type(span) {
168+
span_type = interner.intern_value(value);
194169
}
195170

196171
// Datadog span name is OpenTelemetry component name - see module docs for more information

0 commit comments

Comments
 (0)