From b0801b156542ed4e2b39625c01fe890ba220d5c4 Mon Sep 17 00:00:00 2001 From: Hartigan Date: Wed, 20 Mar 2024 11:42:46 +0100 Subject: [PATCH] Add benchmark for DatadogExporter Add benchmark for DatadogExporter --- opentelemetry-datadog/Cargo.toml | 6 + .../benches/datadog_exporter.rs | 229 ++++++++++++++++++ 2 files changed, 235 insertions(+) create mode 100644 opentelemetry-datadog/benches/datadog_exporter.rs diff --git a/opentelemetry-datadog/Cargo.toml b/opentelemetry-datadog/Cargo.toml index 10c37029..23547e74 100644 --- a/opentelemetry-datadog/Cargo.toml +++ b/opentelemetry-datadog/Cargo.toml @@ -45,6 +45,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" diff --git a/opentelemetry-datadog/benches/datadog_exporter.rs b/opentelemetry-datadog/benches/datadog_exporter.rs new file mode 100644 index 00000000..5e9251d0 --- /dev/null +++ b/opentelemetry-datadog/benches/datadog_exporter.rs @@ -0,0 +1,229 @@ +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>, + ) -> Result, 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}"), + format!("/v3/awesome/endpoint"), + ]; + 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).into_iter().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).into_iter().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) + .into_iter() + .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 { + let mut rng = thread_rng(); + + let mut result: Vec = (0..number_of_traces) + .into_iter() + .flat_map(|trace_id| { + let id = &trace_id; + (0..spans_per_trace) + .into_iter() + .map(|span_id| get_span(*id as u128, span_id as u64, span_id as u64, &mut rng)) + .collect::>() + }) + .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);