Skip to content

Commit

Permalink
Add benchmark for DatadogExporter (#55)
Browse files Browse the repository at this point in the history
  • Loading branch information
Hartigan committed Mar 21, 2024
1 parent a84e207 commit eaa0f5f
Show file tree
Hide file tree
Showing 2 changed files with 226 additions and 0 deletions.
6 changes: 6 additions & 0 deletions opentelemetry-datadog/Cargo.toml
Expand Up @@ -46,6 +46,12 @@ bytes = "1"
futures-util = { version = "0.3", default-features = false, features = ["io"] }
isahc = "1.4"
opentelemetry_sdk = { workspace = true, features = ["trace", "testing"] }
criterion = "0.5"
rand = "0.8"

[[bench]]
name = "datadog_exporter"
harness = false

[[example]]
name = "datadog"
Expand Down
220 changes: 220 additions & 0 deletions opentelemetry-datadog/benches/datadog_exporter.rs
@@ -0,0 +1,220 @@
use std::{
borrow::Cow,
time::{Duration, SystemTime},
};

use criterion::{black_box, criterion_group, criterion_main, Criterion};
use http::Request;
use opentelemetry::{
trace::{SpanContext, SpanId, SpanKind, Status, TraceFlags, TraceId, TraceState},
Array, InstrumentationLibrary, KeyValue, Value,
};
use opentelemetry_datadog::{new_pipeline, ApiVersion};
use opentelemetry_http::HttpClient;
use opentelemetry_sdk::{
export::trace::{SpanData, SpanExporter},
trace::{SpanEvents, SpanLinks},
Resource,
};
use rand::seq::SliceRandom;
use rand::{rngs::ThreadRng, thread_rng, RngCore};

#[derive(Debug)]
struct DummyClient;

#[async_trait::async_trait]
impl HttpClient for DummyClient {
async fn send(
&self,
_request: Request<Vec<u8>>,
) -> Result<http::Response<bytes::Bytes>, opentelemetry_http::HttpError> {
Ok(http::Response::new("dummy response".into()))
}
}

fn get_http_method(rng: &mut ThreadRng) -> String {
const HTTP_METHODS: [&str; 4] = ["GET", "POST", "PUT", "DELETE"];
HTTP_METHODS.choose(rng).unwrap().to_string()
}

fn get_http_route(rng: &mut ThreadRng) -> String {
const HTTP_ROUTES: [&str; 4] = [
"/v1/user/{user_id}",
"/v1/student/{student_id}",
"/v2/family/{family_id}",
"/v3/awesome/endpoint",
];
HTTP_ROUTES.choose(rng).unwrap().to_string()
}

fn get_http_target(rng: &mut ThreadRng) -> String {
let id = rng.next_u32();
let targets = [
format!("/v1/user/{id}"),
format!("/v1/student/{id}"),
format!("/v2/family/{id}"),
"/v3/awesome/endpoint".to_string(),
];
targets.choose(rng).unwrap().to_string()
}

fn get_http_scheme(rng: &mut ThreadRng) -> String {
const HTTP_SCHEME: [&str; 2] = ["http", "https"];
HTTP_SCHEME.choose(rng).unwrap().to_string()
}

fn get_http_user_agent(rng: &mut ThreadRng) -> String {
const HTTP_USER_AGENT : [&str; 7] = [
"Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 13; SM-S901B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Mobile Safari/537.36",
"Mozilla/5.0 (iPhone14,6; U; CPU iPhone OS 15_4 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Version/10.0 Mobile/19E241 Safari/602.1",
"Mozilla/5.0 (iPhone14,3; U; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Version/10.0 Mobile/19A346 Safari/602.1",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246",
"Mozilla/5.0 (X11; CrOS x86_64 8172.45.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.64 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/601.3.9 (KHTML, like Gecko) Version/9.0.2 Safari/601.3.9",
];
HTTP_USER_AGENT.choose(rng).unwrap().to_string()
}

fn get_http_flavor(rng: &mut ThreadRng) -> String {
const HTTP_FLAVOR: [&str; 3] = ["1.1", "2", "3"];
HTTP_FLAVOR.choose(rng).unwrap().to_string()
}

fn get_http_client_id(rng: &mut ThreadRng) -> String {
rng.next_u32()
.to_be_bytes()
.map(|x| x.to_string())
.join(".")
}

fn get_int_value(rng: &mut ThreadRng) -> i64 {
rng.next_u32() as i64
}

fn get_float_value(rng: &mut ThreadRng) -> f64 {
rng.next_u32() as f64
}

fn get_boolean_value(rng: &mut ThreadRng) -> bool {
rng.next_u32() % 2 == 0
}

fn get_array_of_ints(rng: &mut ThreadRng) -> Value {
let len = rng.next_u32() % 8;
Value::Array(Array::I64((0..len).map(|_| get_int_value(rng)).collect()))
}

fn get_array_of_floats(rng: &mut ThreadRng) -> Value {
let len = rng.next_u32() % 8;
Value::Array(Array::F64((0..len).map(|_| get_float_value(rng)).collect()))
}

fn get_array_of_booleans(rng: &mut ThreadRng) -> Value {
let len = rng.next_u32() % 8;
Value::Array(Array::Bool(
(0..len).map(|_| get_boolean_value(rng)).collect(),
))
}

fn get_span(trace_id: u128, parent_span_id: u64, span_id: u64, rng: &mut ThreadRng) -> SpanData {
let span_context = SpanContext::new(
TraceId::from_u128(trace_id),
SpanId::from_u64(span_id),
TraceFlags::default(),
false,
TraceState::default(),
);

let start_time = SystemTime::UNIX_EPOCH;
let end_time = start_time.checked_add(Duration::from_secs(1)).unwrap();

let attributes = vec![
KeyValue::new("span.type", "web"),
KeyValue::new("http.method", get_http_method(rng)),
KeyValue::new("http.route", get_http_route(rng)),
KeyValue::new("http.scheme", get_http_scheme(rng)),
KeyValue::new("http.host", "my.awesome.server"),
KeyValue::new("http.client_id", get_http_client_id(rng)),
KeyValue::new("http.flavor", get_http_flavor(rng)),
KeyValue::new("http.target", get_http_target(rng)),
KeyValue::new("http.user_agent", get_http_user_agent(rng)),
KeyValue::new("property_int_1", get_int_value(rng)),
KeyValue::new("property_int_2", get_int_value(rng)),
KeyValue::new("property_int_3", get_int_value(rng)),
KeyValue::new("property_float_1", get_float_value(rng)),
KeyValue::new("property_float_2", get_float_value(rng)),
KeyValue::new("property_float_3", get_float_value(rng)),
KeyValue::new("property_boolean_1", get_boolean_value(rng)),
KeyValue::new("property_boolean_2", get_boolean_value(rng)),
KeyValue::new("property_boolean_3", get_boolean_value(rng)),
KeyValue::new("property_boolean_array", get_array_of_booleans(rng)),
KeyValue::new("property_int_array", get_array_of_ints(rng)),
KeyValue::new("property_float_array", get_array_of_floats(rng)),
];
let events = SpanEvents::default();
let links = SpanLinks::default();
let resource = Resource::new(vec![KeyValue::new("host.name", "test")]);

SpanData {
span_context,
parent_span_id: SpanId::from_u64(parent_span_id),
span_kind: SpanKind::Client,
name: "resource".into(),
start_time,
end_time,
attributes,
dropped_attributes_count: 0,
events,
links,
status: Status::Ok,
resource: Cow::Owned(resource),
instrumentation_lib: InstrumentationLibrary::new(
"component",
None::<&'static str>,
None::<&'static str>,
None,
),
}
}

fn generate_traces(number_of_traces: usize, spans_per_trace: usize) -> Vec<SpanData> {
let mut rng = thread_rng();

let mut result: Vec<SpanData> = (0..number_of_traces)
.flat_map(|trace_id| {
let id = &trace_id;
(0..spans_per_trace)
.map(|span_id| get_span(*id as u128, span_id as u64, span_id as u64, &mut rng))
.collect::<Vec<_>>()
})
.collect();

result.shuffle(&mut rng);

result
}

fn criterion_benchmark(c: &mut Criterion) {
let mut exporter = new_pipeline()
.with_service_name("trace-demo")
.with_api_version(ApiVersion::Version05)
.with_http_client(DummyClient)
.build_exporter()
.unwrap();

let patterns: [(usize, usize); 5] = [(128, 4), (256, 4), (512, 4), (512, 2), (512, 1)];

for (number_of_traces, spans_per_trace) in patterns {
let data = generate_traces(number_of_traces, spans_per_trace);
let data_ref = &data;

c.bench_function(
format!("export {number_of_traces} traces with {spans_per_trace} spans").as_str(),
|b| b.iter(|| exporter.export(black_box(data_ref.clone()))),
);
}
}

criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

0 comments on commit eaa0f5f

Please sign in to comment.