From 255952c27ff9fa022b4aa735281e2d71d94c32ff Mon Sep 17 00:00:00 2001 From: Matthew Boddewyn <31598686+mattbodd@users.noreply.github.com> Date: Tue, 26 Nov 2024 12:40:26 -0800 Subject: [PATCH 01/24] Initial criterion support --- Cargo.toml | 10 +++--- opentelemetry-etw-metrics/Cargo.toml | 11 ++++++- opentelemetry-etw-metrics/benches/etw.rs | 16 ++++++++++ opentelemetry-etw-metrics/benches/exporter.rs | 31 +++++++++++++++++++ 4 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 opentelemetry-etw-metrics/benches/etw.rs create mode 100644 opentelemetry-etw-metrics/benches/exporter.rs diff --git a/Cargo.toml b/Cargo.toml index 9d0b6ff1..b217be70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,5 @@ [workspace] -members = [ - "opentelemetry-*", - "examples/*", -] +members = ["opentelemetry-*", "examples/*"] resolver = "2" [profile.bench] @@ -18,4 +15,7 @@ opentelemetry-http = "0.27" opentelemetry-proto = { version = "0.27", default-features = false } opentelemetry_sdk = { version = "0.27", default-features = false } opentelemetry-stdout = "0.27" -opentelemetry-semantic-conventions = { version = "0.27", features = ["semconv_experimental"] } +opentelemetry-semantic-conventions = { version = "0.27", features = [ + "semconv_experimental", +] } +criterion = "0.5" diff --git a/opentelemetry-etw-metrics/Cargo.toml b/opentelemetry-etw-metrics/Cargo.toml index a3eef4cb..119d67f9 100644 --- a/opentelemetry-etw-metrics/Cargo.toml +++ b/opentelemetry-etw-metrics/Cargo.toml @@ -17,7 +17,8 @@ opentelemetry-proto = { workspace = true, features = ["gen-tonic", "metrics"] } async-trait = "0.1" prost = "0.13" tracelogging = "1.2.1" -tracing = {version = "0.1", optional = true} +tracing = { version = "0.1", optional = true } +criterion = { workspace = true, features = ["html_reports"] } [dev-dependencies] tokio = { version = "1.0", features = ["full"] } @@ -28,3 +29,11 @@ default = ["internal-logs"] [package.metadata.cargo-machete] ignored = ["tracing"] + +[[bench]] +name = "etw" +harness = false + +[[bench]] +name = "exporter" +harness = false diff --git a/opentelemetry-etw-metrics/benches/etw.rs b/opentelemetry-etw-metrics/benches/etw.rs new file mode 100644 index 00000000..feec4a11 --- /dev/null +++ b/opentelemetry-etw-metrics/benches/etw.rs @@ -0,0 +1,16 @@ +use criterion::{criterion_group, criterion_main, Criterion}; + +fn fibonacci(n: u64) -> u64 { + match n { + 0 => 0, + 1 => 1, + _ => fibonacci(n - 1) + fibonacci(n - 2), + } +} + +fn criterion_benchmark(c: &mut Criterion) { + c.bench_function("fib 20", |b| b.iter(|| fibonacci(20))); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/opentelemetry-etw-metrics/benches/exporter.rs b/opentelemetry-etw-metrics/benches/exporter.rs new file mode 100644 index 00000000..e4e3b83b --- /dev/null +++ b/opentelemetry-etw-metrics/benches/exporter.rs @@ -0,0 +1,31 @@ +//! run with `$ cargo bench --bench exporter -- --exact ` to run specific test for logs +//! So to run test named "fibonacci" you would run `$ cargo bench --bench exporter -- --exact fibonacci` +//! To run all tests for logs you would run `$ cargo bench --bench exporter` +//! +/* +The benchmark results: +criterion = "0.5.1" +OS: +Hardware: +RAM: +| Test | Average time| +|--------------------------------|-------------| +| | | +*/ + +use criterion::{criterion_group, criterion_main, Criterion}; + +fn fibonacci(n: u64) -> u64 { + match n { + 0 => 0, + 1 => 1, + _ => fibonacci(n - 1) + fibonacci(n - 2), + } +} + +fn criterion_benchmark(c: &mut Criterion) { + c.bench_function("fib 20", |b| b.iter(|| fibonacci(20))); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); From 64255ab6400e8819a9c182f2c9fee25f5cf663c0 Mon Sep 17 00:00:00 2001 From: Matthew Boddewyn <31598686+mattbodd@users.noreply.github.com> Date: Tue, 26 Nov 2024 12:42:11 -0800 Subject: [PATCH 02/24] Add doc comment to etw bench --- opentelemetry-etw-metrics/benches/etw.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/opentelemetry-etw-metrics/benches/etw.rs b/opentelemetry-etw-metrics/benches/etw.rs index feec4a11..fba195ac 100644 --- a/opentelemetry-etw-metrics/benches/etw.rs +++ b/opentelemetry-etw-metrics/benches/etw.rs @@ -1,3 +1,18 @@ +//! run with `$ cargo bench --bench etw -- --exact ` to run specific test for logs +//! So to run test named "fibonacci" you would run `$ cargo bench --bench etw -- --exact fibonacci` +//! To run all tests for logs you would run `$ cargo bench --bench etw` +//! +/* +The benchmark results: +criterion = "0.5.1" +OS: +Hardware: +RAM: +| Test | Average time| +|--------------------------------|-------------| +| | | +*/ + use criterion::{criterion_group, criterion_main, Criterion}; fn fibonacci(n: u64) -> u64 { From 7d2890f29c49a7f9f0e4bae69a3ade74e2c37f59 Mon Sep 17 00:00:00 2001 From: Matthew Boddewyn <31598686+mattbodd@users.noreply.github.com> Date: Mon, 2 Dec 2024 17:21:27 -0800 Subject: [PATCH 03/24] Add useful exporter benchmark --- opentelemetry-etw-metrics/benches/exporter.rs | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/opentelemetry-etw-metrics/benches/exporter.rs b/opentelemetry-etw-metrics/benches/exporter.rs index e4e3b83b..2f06301c 100644 --- a/opentelemetry-etw-metrics/benches/exporter.rs +++ b/opentelemetry-etw-metrics/benches/exporter.rs @@ -13,18 +13,26 @@ RAM: | | | */ +use opentelemetry_etw_metrics::MetricsExporter; +use opentelemetry_sdk::{metrics::{data::{ResourceMetrics, ScopeMetrics}, exporter::PushMetricExporter}, Resource}; + use criterion::{criterion_group, criterion_main, Criterion}; -fn fibonacci(n: u64) -> u64 { - match n { - 0 => 0, - 1 => 1, - _ => fibonacci(n - 1) + fibonacci(n - 2), - } +fn export() { + let exporter = MetricsExporter::new(); + let mut resource_metrics = ResourceMetrics { + resource: Resource::default(), + scope_metrics: vec![ScopeMetrics::default(), ScopeMetrics::default()], + }; + + let runtime = tokio::runtime::Runtime::new().unwrap(); + runtime.block_on(async { + exporter.export(&mut resource_metrics).await.unwrap(); + }); } fn criterion_benchmark(c: &mut Criterion) { - c.bench_function("fib 20", |b| b.iter(|| fibonacci(20))); + c.bench_function("export", |b| b.iter(|| { export()})); } criterion_group!(benches, criterion_benchmark); From c327437fb9027813bdc93e5ff3c29e6b2ea3a4eb Mon Sep 17 00:00:00 2001 From: Matthew Boddewyn <31598686+mattbodd@users.noreply.github.com> Date: Mon, 2 Dec 2024 17:21:45 -0800 Subject: [PATCH 04/24] Add useful etw benchmark --- opentelemetry-etw-metrics/benches/etw.rs | 12 +++++------- opentelemetry-etw-metrics/src/lib.rs | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/opentelemetry-etw-metrics/benches/etw.rs b/opentelemetry-etw-metrics/benches/etw.rs index fba195ac..85b3b3a7 100644 --- a/opentelemetry-etw-metrics/benches/etw.rs +++ b/opentelemetry-etw-metrics/benches/etw.rs @@ -13,18 +13,16 @@ RAM: | | | */ +use opentelemetry_etw_metrics::etw::write; use criterion::{criterion_group, criterion_main, Criterion}; -fn fibonacci(n: u64) -> u64 { - match n { - 0 => 0, - 1 => 1, - _ => fibonacci(n - 1) + fibonacci(n - 2), - } +fn write_event() { + let buffer = "This is a test buffer".as_bytes(); + write(buffer); } fn criterion_benchmark(c: &mut Criterion) { - c.bench_function("fib 20", |b| b.iter(|| fibonacci(20))); + c.bench_function("write_event", |b| b.iter(|| write_event())); } criterion_group!(benches, criterion_benchmark); diff --git a/opentelemetry-etw-metrics/src/lib.rs b/opentelemetry-etw-metrics/src/lib.rs index 56689e9a..5b6029fc 100644 --- a/opentelemetry-etw-metrics/src/lib.rs +++ b/opentelemetry-etw-metrics/src/lib.rs @@ -1,4 +1,4 @@ -mod etw; +pub mod etw; mod exporter; pub use exporter::MetricsExporter; From 391d151b2c947eb40d3ab0111c6de99845e9eaa3 Mon Sep 17 00:00:00 2001 From: Matthew Boddewyn <31598686+mattbodd@users.noreply.github.com> Date: Mon, 2 Dec 2024 18:34:48 -0800 Subject: [PATCH 05/24] Add system info and exporter and etw times --- opentelemetry-etw-metrics/benches/etw.rs | 8 ++++---- opentelemetry-etw-metrics/benches/exporter.rs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/opentelemetry-etw-metrics/benches/etw.rs b/opentelemetry-etw-metrics/benches/etw.rs index 85b3b3a7..f4b5cade 100644 --- a/opentelemetry-etw-metrics/benches/etw.rs +++ b/opentelemetry-etw-metrics/benches/etw.rs @@ -5,12 +5,12 @@ /* The benchmark results: criterion = "0.5.1" -OS: -Hardware: -RAM: +OS: Windows 11 Enterprise N, 23H2, Build 22631.4460 +Hardware: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz 2.79 GHz, 16vCPUs +RAM: 64.0 GB | Test | Average time| |--------------------------------|-------------| -| | | +| write_event | 2.0649ns | */ use opentelemetry_etw_metrics::etw::write; diff --git a/opentelemetry-etw-metrics/benches/exporter.rs b/opentelemetry-etw-metrics/benches/exporter.rs index 2f06301c..4fb62818 100644 --- a/opentelemetry-etw-metrics/benches/exporter.rs +++ b/opentelemetry-etw-metrics/benches/exporter.rs @@ -5,12 +5,12 @@ /* The benchmark results: criterion = "0.5.1" -OS: -Hardware: -RAM: +OS: Windows 11 Enterprise N, 23H2, Build 22631.4460 +Hardware: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz 2.79 GHz, 16vCPUs +RAM: 64.0 GB | Test | Average time| |--------------------------------|-------------| -| | | +| exporter | 1.2927ms | */ use opentelemetry_etw_metrics::MetricsExporter; From fe5e7d065001285e6a8adf32dd93c17b8f82845a Mon Sep 17 00:00:00 2001 From: Matthew Boddewyn <31598686+mattbodd@users.noreply.github.com> Date: Mon, 2 Dec 2024 19:14:01 -0800 Subject: [PATCH 06/24] Run cargo fmt --- opentelemetry-etw-metrics/benches/etw.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opentelemetry-etw-metrics/benches/etw.rs b/opentelemetry-etw-metrics/benches/etw.rs index f4b5cade..fdb1a842 100644 --- a/opentelemetry-etw-metrics/benches/etw.rs +++ b/opentelemetry-etw-metrics/benches/etw.rs @@ -13,8 +13,8 @@ RAM: 64.0 GB | write_event | 2.0649ns | */ -use opentelemetry_etw_metrics::etw::write; use criterion::{criterion_group, criterion_main, Criterion}; +use opentelemetry_etw_metrics::etw::write; fn write_event() { let buffer = "This is a test buffer".as_bytes(); From bada2949933c0185a1ae63f5b63683eaf5a0495d Mon Sep 17 00:00:00 2001 From: Matthew Boddewyn <31598686+mattbodd@users.noreply.github.com> Date: Mon, 2 Dec 2024 19:57:36 -0800 Subject: [PATCH 07/24] Update benchmark --- opentelemetry-etw-metrics/benches/exporter.rs | 50 ++++++++++++++----- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/opentelemetry-etw-metrics/benches/exporter.rs b/opentelemetry-etw-metrics/benches/exporter.rs index 4fb62818..335e0efb 100644 --- a/opentelemetry-etw-metrics/benches/exporter.rs +++ b/opentelemetry-etw-metrics/benches/exporter.rs @@ -10,29 +10,55 @@ Hardware: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz 2.79 GHz, 16vCPUs RAM: 64.0 GB | Test | Average time| |--------------------------------|-------------| -| exporter | 1.2927ms | +| exporter | 847.38µs | */ +use opentelemetry::metrics::Gauge; +use opentelemetry::{metrics::MeterProvider as _, KeyValue}; +use opentelemetry::{InstrumentationScope, InstrumentationScopeBuilder}; use opentelemetry_etw_metrics::MetricsExporter; -use opentelemetry_sdk::{metrics::{data::{ResourceMetrics, ScopeMetrics}, exporter::PushMetricExporter}, Resource}; +use opentelemetry_sdk::metrics::reader::MetricReader; +use opentelemetry_sdk::metrics::{ManualReader, PeriodicReader, SdkMeterProvider}; +use opentelemetry_sdk::{ + metrics::{ + data::{ResourceMetrics, ScopeMetrics}, + exporter::PushMetricExporter, + }, + runtime, Resource, +}; use criterion::{criterion_group, criterion_main, Criterion}; fn export() { - let exporter = MetricsExporter::new(); - let mut resource_metrics = ResourceMetrics { - resource: Resource::default(), - scope_metrics: vec![ScopeMetrics::default(), ScopeMetrics::default()], - }; + // Create a new tokio runtime that blocks on the async execution + tokio::runtime::Builder::new_multi_thread() + .worker_threads(1) + .enable_all() + .build() + .unwrap() + .block_on(async { + let exporter = MetricsExporter::new(); + let reader = PeriodicReader::builder(exporter, runtime::Tokio).build(); + let meter_provider = SdkMeterProvider::builder() + .with_resource(Resource::new(vec![KeyValue::new( + "service.name", + "service-name", + )])) + .with_reader(reader.clone()) + .build(); + let meter = meter_provider.meter("etw-bench"); + let gauge = meter.u64_gauge("gauge").build(); - let runtime = tokio::runtime::Runtime::new().unwrap(); - runtime.block_on(async { - exporter.export(&mut resource_metrics).await.unwrap(); - }); + for _ in 0..10_000 { + gauge.record(1, &[KeyValue::new("key", "value")]); + } + + reader.force_flush().unwrap(); + }); } fn criterion_benchmark(c: &mut Criterion) { - c.bench_function("export", |b| b.iter(|| { export()})); + c.bench_function("export", |b| b.iter(|| export())); } criterion_group!(benches, criterion_benchmark); From 18f550a571cb7031719968ae70d09ef47822fbf4 Mon Sep 17 00:00:00 2001 From: Matthew Boddewyn <31598686+mattbodd@users.noreply.github.com> Date: Mon, 2 Dec 2024 19:57:47 -0800 Subject: [PATCH 08/24] Try to be more efficient in exporter --- opentelemetry-etw-metrics/src/exporter/mod.rs | 70 ++++++++++--------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/opentelemetry-etw-metrics/src/exporter/mod.rs b/opentelemetry-etw-metrics/src/exporter/mod.rs index 26a6155c..f3508973 100644 --- a/opentelemetry-etw-metrics/src/exporter/mod.rs +++ b/opentelemetry-etw-metrics/src/exporter/mod.rs @@ -39,13 +39,38 @@ impl Debug for MetricsExporter { } } +fn emit_metric(resource_metric: &ResourceMetrics, buffer: &mut Vec) -> MetricResult<()> { + // Zero the buffer to ensure no data is left over from previous writes + buffer.clear(); + + let proto_message: ExportMetricsServiceRequest = (&*resource_metric).into(); + proto_message + .encode(buffer) + .map_err(|err| MetricError::Other(err.to_string()))?; + + if (proto_message.encoded_len()) > etw::MAX_EVENT_SIZE { + otel_warn!(name: "MetricExportFailedDueToMaxSizeLimit", size = proto_message.encoded_len(), max_size = etw::MAX_EVENT_SIZE); + } else { + let result = etw::write(&buffer); + // TODO: Better logging/internal metrics needed here for non-failure + // case Uncomment the line below to see the exported bytes until a + // better logging solution is implemented + // println!("Exported {} bytes to ETW", byte_array.len()); + if result != 0 { + otel_warn!(name: "MetricExportFailed", error_code = result); + } + } + + Ok(()) +} + #[async_trait] impl PushMetricExporter for MetricsExporter { async fn export(&self, metrics: &mut ResourceMetrics) -> MetricResult<()> { + let mut encoding_buffer: Vec = Vec::with_capacity(1024); + for scope_metric in &metrics.scope_metrics { for metric in &scope_metric.metrics { - let mut resource_metrics = Vec::new(); - let data = &metric.data.as_any(); if let Some(hist) = data.downcast_ref::>() { for data_point in &hist.data_points { @@ -64,7 +89,7 @@ impl PushMetricExporter for MetricsExporter { }], }], }; - resource_metrics.push(resource_metric); + emit_metric(&resource_metric, &mut encoding_buffer)?; } } else if let Some(hist) = data.downcast_ref::>() { for data_point in &hist.data_points { @@ -83,7 +108,7 @@ impl PushMetricExporter for MetricsExporter { }], }], }; - resource_metrics.push(resource_metric); + emit_metric(&resource_metric, &mut encoding_buffer)?; } } else if let Some(hist) = data.downcast_ref::>() { for data_point in &hist.data_points { @@ -122,7 +147,7 @@ impl PushMetricExporter for MetricsExporter { }], }], }; - resource_metrics.push(resource_metric); + emit_metric(&resource_metric, &mut encoding_buffer)?; } } else if let Some(hist) = data.downcast_ref::>() { for data_point in &hist.data_points { @@ -161,7 +186,7 @@ impl PushMetricExporter for MetricsExporter { }], }], }; - resource_metrics.push(resource_metric); + emit_metric(&resource_metric, &mut encoding_buffer)?; } } else if let Some(sum) = data.downcast_ref::>() { for data_point in &sum.data_points { @@ -181,7 +206,7 @@ impl PushMetricExporter for MetricsExporter { }], }], }; - resource_metrics.push(resource_metric); + emit_metric(&resource_metric, &mut encoding_buffer)?; } } else if let Some(sum) = data.downcast_ref::>() { for data_point in &sum.data_points { @@ -201,7 +226,7 @@ impl PushMetricExporter for MetricsExporter { }], }], }; - resource_metrics.push(resource_metric); + emit_metric(&resource_metric, &mut encoding_buffer)?; } } else if let Some(sum) = data.downcast_ref::>() { for data_point in &sum.data_points { @@ -221,7 +246,7 @@ impl PushMetricExporter for MetricsExporter { }], }], }; - resource_metrics.push(resource_metric); + emit_metric(&resource_metric, &mut encoding_buffer)?; } } else if let Some(gauge) = data.downcast_ref::>() { for data_point in &gauge.data_points { @@ -239,7 +264,7 @@ impl PushMetricExporter for MetricsExporter { }], }], }; - resource_metrics.push(resource_metric); + emit_metric(&resource_metric, &mut encoding_buffer)?; } } else if let Some(gauge) = data.downcast_ref::>() { for data_point in &gauge.data_points { @@ -257,7 +282,7 @@ impl PushMetricExporter for MetricsExporter { }], }], }; - resource_metrics.push(resource_metric); + emit_metric(&resource_metric, &mut encoding_buffer)?; } } else if let Some(gauge) = data.downcast_ref::>() { for data_point in &gauge.data_points { @@ -275,32 +300,11 @@ impl PushMetricExporter for MetricsExporter { }], }], }; - resource_metrics.push(resource_metric); + emit_metric(&resource_metric, &mut encoding_buffer)?; } } else { otel_warn!(name: "MetricExportFailedDueToUnsupportedMetricType", metric_type = format!("{:?}", data)); } - - for resource_metric in resource_metrics { - let mut byte_array = Vec::new(); - let proto_message: ExportMetricsServiceRequest = (&resource_metric).into(); - proto_message - .encode(&mut byte_array) - .map_err(|err| MetricError::Other(err.to_string()))?; - - if (byte_array.len()) > etw::MAX_EVENT_SIZE { - otel_warn!(name: "MetricExportFailedDueToMaxSizeLimit", size = byte_array.len(), max_size = etw::MAX_EVENT_SIZE); - } else { - let result = etw::write(&byte_array); - // TODO: Better logging/internal metrics needed here for non-failure - // case Uncomment the line below to see the exported bytes until a - // better logging solution is implemented - // println!("Exported {} bytes to ETW", byte_array.len()); - if result != 0 { - otel_warn!(name: "MetricExportFailed", error_code = result); - } - } - } } } From 8dd5df6b1f6994bbce9bc603a8d436b3f281dccd Mon Sep 17 00:00:00 2001 From: Matthew Boddewyn <31598686+mattbodd@users.noreply.github.com> Date: Mon, 2 Dec 2024 20:01:32 -0800 Subject: [PATCH 09/24] Apply clippy lints --- opentelemetry-etw-metrics/benches/exporter.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/opentelemetry-etw-metrics/benches/exporter.rs b/opentelemetry-etw-metrics/benches/exporter.rs index 335e0efb..5a4fd7e6 100644 --- a/opentelemetry-etw-metrics/benches/exporter.rs +++ b/opentelemetry-etw-metrics/benches/exporter.rs @@ -13,17 +13,10 @@ RAM: 64.0 GB | exporter | 847.38µs | */ -use opentelemetry::metrics::Gauge; use opentelemetry::{metrics::MeterProvider as _, KeyValue}; -use opentelemetry::{InstrumentationScope, InstrumentationScopeBuilder}; use opentelemetry_etw_metrics::MetricsExporter; -use opentelemetry_sdk::metrics::reader::MetricReader; -use opentelemetry_sdk::metrics::{ManualReader, PeriodicReader, SdkMeterProvider}; use opentelemetry_sdk::{ - metrics::{ - data::{ResourceMetrics, ScopeMetrics}, - exporter::PushMetricExporter, - }, + metrics::{reader::MetricReader, PeriodicReader, SdkMeterProvider}, runtime, Resource, }; From 7b18b0187f9c64cf8e2e790e49ebcb71e457d2f2 Mon Sep 17 00:00:00 2001 From: Matthew Boddewyn <31598686+mattbodd@users.noreply.github.com> Date: Tue, 3 Dec 2024 20:25:13 -0800 Subject: [PATCH 10/24] Create ExportMetricsServiceRequest for each metric and replace the data for each data point --- opentelemetry-etw-metrics/src/exporter/mod.rs | 365 ++++++------------ 1 file changed, 117 insertions(+), 248 deletions(-) diff --git a/opentelemetry-etw-metrics/src/exporter/mod.rs b/opentelemetry-etw-metrics/src/exporter/mod.rs index f3508973..40639012 100644 --- a/opentelemetry-etw-metrics/src/exporter/mod.rs +++ b/opentelemetry-etw-metrics/src/exporter/mod.rs @@ -1,13 +1,16 @@ use opentelemetry::otel_warn; -use opentelemetry_proto::tonic::collector::metrics::v1::ExportMetricsServiceRequest; -use opentelemetry_sdk::metrics::{ - data::{ - self, ExponentialBucket, ExponentialHistogramDataPoint, Metric, ResourceMetrics, - ScopeMetrics, +use opentelemetry_proto::tonic::{ + collector::metrics::v1::ExportMetricsServiceRequest, + metrics::v1::{ + metric::Data as TonicMetricData, ExponentialHistogram as TonicExponentialHistogram, + Gauge as TonicGauge, Histogram as TonicHistogram, Metric as TonicMetric, + ResourceMetrics as TonicResourceMetrics, ScopeMetrics as TonicScopeMetrics, + Sum as TonicSum, Summary as TonicSummary, }, - exporter::PushMetricExporter, - MetricError, MetricResult, Temporality, +}; +use opentelemetry_sdk::metrics::{ + data::ResourceMetrics, exporter::PushMetricExporter, MetricError, MetricResult, Temporality, }; use prost::Message; @@ -39,19 +42,19 @@ impl Debug for MetricsExporter { } } -fn emit_metric(resource_metric: &ResourceMetrics, buffer: &mut Vec) -> MetricResult<()> { - // Zero the buffer to ensure no data is left over from previous writes - buffer.clear(); +fn emit_export_metric_service_request( + export_metric_service_request: &ExportMetricsServiceRequest, +) -> MetricResult<()> { + let mut encoding_buffer = Vec::new(); - let proto_message: ExportMetricsServiceRequest = (&*resource_metric).into(); - proto_message - .encode(buffer) + export_metric_service_request + .encode(&mut encoding_buffer) .map_err(|err| MetricError::Other(err.to_string()))?; - if (proto_message.encoded_len()) > etw::MAX_EVENT_SIZE { - otel_warn!(name: "MetricExportFailedDueToMaxSizeLimit", size = proto_message.encoded_len(), max_size = etw::MAX_EVENT_SIZE); + if (encoding_buffer.len()) > etw::MAX_EVENT_SIZE { + otel_warn!(name: "MetricExportFailedDueToMaxSizeLimit", size = encoding_buffer.len(), max_size = etw::MAX_EVENT_SIZE); } else { - let result = etw::write(&buffer); + let result = etw::write(&encoding_buffer); // TODO: Better logging/internal metrics needed here for non-failure // case Uncomment the line below to see the exported bytes until a // better logging solution is implemented @@ -67,243 +70,109 @@ fn emit_metric(resource_metric: &ResourceMetrics, buffer: &mut Vec) -> Metri #[async_trait] impl PushMetricExporter for MetricsExporter { async fn export(&self, metrics: &mut ResourceMetrics) -> MetricResult<()> { - let mut encoding_buffer: Vec = Vec::with_capacity(1024); + let schema_url: String = metrics + .resource + .schema_url() + .map(Into::into) + .unwrap_or_default(); for scope_metric in &metrics.scope_metrics { for metric in &scope_metric.metrics { - let data = &metric.data.as_any(); - if let Some(hist) = data.downcast_ref::>() { - for data_point in &hist.data_points { - let resource_metric = ResourceMetrics { - resource: metrics.resource.clone(), - scope_metrics: vec![ScopeMetrics { - scope: scope_metric.scope.clone(), - metrics: vec![Metric { - name: metric.name.clone(), - description: metric.description.clone(), - unit: metric.unit.clone(), - data: Box::new(data::Histogram { - temporality: hist.temporality, - data_points: vec![data_point.clone()], - }), - }], - }], - }; - emit_metric(&resource_metric, &mut encoding_buffer)?; - } - } else if let Some(hist) = data.downcast_ref::>() { - for data_point in &hist.data_points { - let resource_metric = ResourceMetrics { - resource: metrics.resource.clone(), - scope_metrics: vec![ScopeMetrics { - scope: scope_metric.scope.clone(), - metrics: vec![Metric { - name: metric.name.clone(), - description: metric.description.clone(), - unit: metric.unit.clone(), - data: Box::new(data::Histogram { - temporality: hist.temporality, - data_points: vec![data_point.clone()], - }), - }], - }], - }; - emit_metric(&resource_metric, &mut encoding_buffer)?; - } - } else if let Some(hist) = data.downcast_ref::>() { - for data_point in &hist.data_points { - let resource_metric = ResourceMetrics { - resource: metrics.resource.clone(), - scope_metrics: vec![ScopeMetrics { - scope: scope_metric.scope.clone(), - metrics: vec![Metric { - name: metric.name.clone(), - description: metric.description.clone(), - unit: metric.unit.clone(), - data: Box::new(data::ExponentialHistogram { - temporality: hist.temporality, - data_points: vec![ExponentialHistogramDataPoint { - attributes: data_point.attributes.clone(), - count: data_point.count, - start_time: data_point.start_time, - time: data_point.time, - min: data_point.min, - max: data_point.max, - sum: data_point.sum, - scale: data_point.scale, - zero_count: data_point.zero_count, - zero_threshold: data_point.zero_threshold, - positive_bucket: ExponentialBucket { - offset: data_point.positive_bucket.offset, - counts: data_point.positive_bucket.counts.clone(), - }, - negative_bucket: ExponentialBucket { - offset: data_point.negative_bucket.offset, - counts: data_point.negative_bucket.counts.clone(), - }, - exemplars: data_point.exemplars.clone(), - }], - }), - }], - }], - }; - emit_metric(&resource_metric, &mut encoding_buffer)?; - } - } else if let Some(hist) = data.downcast_ref::>() { - for data_point in &hist.data_points { - let resource_metric = ResourceMetrics { - resource: metrics.resource.clone(), - scope_metrics: vec![ScopeMetrics { - scope: scope_metric.scope.clone(), - metrics: vec![Metric { - name: metric.name.clone(), - description: metric.description.clone(), - unit: metric.unit.clone(), - data: Box::new(data::ExponentialHistogram { - temporality: hist.temporality, - data_points: vec![ExponentialHistogramDataPoint { - attributes: data_point.attributes.clone(), - count: data_point.count, - start_time: data_point.start_time, - time: data_point.time, - min: data_point.min, - max: data_point.max, - sum: data_point.sum, - scale: data_point.scale, - zero_count: data_point.zero_count, - zero_threshold: data_point.zero_threshold, - positive_bucket: ExponentialBucket { - offset: data_point.positive_bucket.offset, - counts: data_point.positive_bucket.counts.clone(), - }, - negative_bucket: ExponentialBucket { - offset: data_point.negative_bucket.offset, - counts: data_point.negative_bucket.counts.clone(), - }, - exemplars: data_point.exemplars.clone(), - }], - }), - }], - }], - }; - emit_metric(&resource_metric, &mut encoding_buffer)?; - } - } else if let Some(sum) = data.downcast_ref::>() { - for data_point in &sum.data_points { - let resource_metric = ResourceMetrics { - resource: metrics.resource.clone(), - scope_metrics: vec![ScopeMetrics { - scope: scope_metric.scope.clone(), - metrics: vec![Metric { - name: metric.name.clone(), - description: metric.description.clone(), - unit: metric.unit.clone(), - data: Box::new(data::Sum { - temporality: sum.temporality, - data_points: vec![data_point.clone()], - is_monotonic: sum.is_monotonic, - }), - }], - }], - }; - emit_metric(&resource_metric, &mut encoding_buffer)?; - } - } else if let Some(sum) = data.downcast_ref::>() { - for data_point in &sum.data_points { - let resource_metric = ResourceMetrics { - resource: metrics.resource.clone(), - scope_metrics: vec![ScopeMetrics { - scope: scope_metric.scope.clone(), - metrics: vec![Metric { - name: metric.name.clone(), - description: metric.description.clone(), - unit: metric.unit.clone(), - data: Box::new(data::Sum { - temporality: sum.temporality, - data_points: vec![data_point.clone()], - is_monotonic: sum.is_monotonic, - }), - }], - }], - }; - emit_metric(&resource_metric, &mut encoding_buffer)?; - } - } else if let Some(sum) = data.downcast_ref::>() { - for data_point in &sum.data_points { - let resource_metric = ResourceMetrics { - resource: metrics.resource.clone(), - scope_metrics: vec![ScopeMetrics { - scope: scope_metric.scope.clone(), - metrics: vec![Metric { - name: metric.name.clone(), - description: metric.description.clone(), - unit: metric.unit.clone(), - data: Box::new(data::Sum { - temporality: sum.temporality, - data_points: vec![data_point.clone()], - is_monotonic: sum.is_monotonic, - }), - }], - }], - }; - emit_metric(&resource_metric, &mut encoding_buffer)?; - } - } else if let Some(gauge) = data.downcast_ref::>() { - for data_point in &gauge.data_points { - let resource_metric = ResourceMetrics { - resource: metrics.resource.clone(), - scope_metrics: vec![ScopeMetrics { - scope: scope_metric.scope.clone(), - metrics: vec![Metric { - name: metric.name.clone(), - description: metric.description.clone(), - unit: metric.unit.clone(), - data: Box::new(data::Gauge { - data_points: vec![data_point.clone()], - }), - }], - }], - }; - emit_metric(&resource_metric, &mut encoding_buffer)?; - } - } else if let Some(gauge) = data.downcast_ref::>() { - for data_point in &gauge.data_points { - let resource_metric = ResourceMetrics { - resource: metrics.resource.clone(), - scope_metrics: vec![ScopeMetrics { - scope: scope_metric.scope.clone(), - metrics: vec![Metric { - name: metric.name.clone(), - description: metric.description.clone(), - unit: metric.unit.clone(), - data: Box::new(data::Gauge { - data_points: vec![data_point.clone()], - }), - }], - }], - }; - emit_metric(&resource_metric, &mut encoding_buffer)?; - } - } else if let Some(gauge) = data.downcast_ref::>() { - for data_point in &gauge.data_points { - let resource_metric = ResourceMetrics { - resource: metrics.resource.clone(), - scope_metrics: vec![ScopeMetrics { - scope: scope_metric.scope.clone(), - metrics: vec![Metric { - name: metric.name.clone(), - description: metric.description.clone(), - unit: metric.unit.clone(), - data: Box::new(data::Gauge { - data_points: vec![data_point.clone()], - }), - }], + let proto_data: Option = metric.data.as_any().try_into().ok(); + + // This ExportMetricsServiceRequest is created for each metric and will hold a single data point. + let mut export_metrics_service_request = ExportMetricsServiceRequest { + resource_metrics: vec![TonicResourceMetrics { + resource: Some((&metrics.resource).into()), + scope_metrics: vec![TonicScopeMetrics { + scope: Some((&scope_metric.scope, None).into()), + metrics: vec![TonicMetric { + name: metric.name.to_string(), + description: metric.description.to_string(), + unit: metric.unit.to_string(), + metadata: vec![], + data: None, // Initially data is None, it will be set based on the type of metric }], - }; - emit_metric(&resource_metric, &mut encoding_buffer)?; + schema_url: schema_url.clone(), + }], + schema_url: schema_url.clone(), + }], + }; + + if let Some(proto_data) = proto_data { + match proto_data { + TonicMetricData::Histogram(hist) => { + for data_point in hist.data_points { + export_metrics_service_request.resource_metrics[0].scope_metrics + [0] + .metrics[0] + .data = Some(TonicMetricData::Histogram(TonicHistogram { + aggregation_temporality: hist.aggregation_temporality, + data_points: vec![data_point], + })); + emit_export_metric_service_request( + &export_metrics_service_request, + )?; + } + } + TonicMetricData::ExponentialHistogram(exp_hist) => { + for data_point in exp_hist.data_points { + export_metrics_service_request.resource_metrics[0].scope_metrics + [0] + .metrics[0] + .data = Some(TonicMetricData::ExponentialHistogram( + TonicExponentialHistogram { + aggregation_temporality: exp_hist.aggregation_temporality, + data_points: vec![data_point], + }, + )); + emit_export_metric_service_request( + &export_metrics_service_request, + )?; + } + } + TonicMetricData::Gauge(gauge) => { + for data_point in gauge.data_points { + export_metrics_service_request.resource_metrics[0].scope_metrics + [0] + .metrics[0] + .data = Some(TonicMetricData::Gauge(TonicGauge { + data_points: vec![data_point], + })); + emit_export_metric_service_request( + &export_metrics_service_request, + )?; + } + } + TonicMetricData::Sum(sum) => { + for data_point in sum.data_points { + export_metrics_service_request.resource_metrics[0].scope_metrics + [0] + .metrics[0] + .data = Some(TonicMetricData::Sum(TonicSum { + data_points: vec![data_point], + aggregation_temporality: sum.aggregation_temporality, + is_monotonic: sum.is_monotonic, + })); + emit_export_metric_service_request( + &export_metrics_service_request, + )?; + } + } + TonicMetricData::Summary(summary) => { + for data in summary.data_points { + export_metrics_service_request.resource_metrics[0].scope_metrics + [0] + .metrics[0] + .data = Some(TonicMetricData::Summary(TonicSummary { + data_points: vec![data], + })); + emit_export_metric_service_request( + &export_metrics_service_request, + )?; + } + } } - } else { - otel_warn!(name: "MetricExportFailedDueToUnsupportedMetricType", metric_type = format!("{:?}", data)); } } } From d1671014199ee3e226ca97acd4a5415a6929ef08 Mon Sep 17 00:00:00 2001 From: Matthew Boddewyn <31598686+mattbodd@users.noreply.github.com> Date: Tue, 3 Dec 2024 20:28:11 -0800 Subject: [PATCH 11/24] Reorder imports --- opentelemetry-etw-metrics/src/exporter/mod.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/opentelemetry-etw-metrics/src/exporter/mod.rs b/opentelemetry-etw-metrics/src/exporter/mod.rs index 40639012..13fb4249 100644 --- a/opentelemetry-etw-metrics/src/exporter/mod.rs +++ b/opentelemetry-etw-metrics/src/exporter/mod.rs @@ -1,5 +1,6 @@ -use opentelemetry::otel_warn; +use crate::etw; +use opentelemetry::otel_warn; use opentelemetry_proto::tonic::{ collector::metrics::v1::ExportMetricsServiceRequest, metrics::v1::{ @@ -12,13 +13,11 @@ use opentelemetry_proto::tonic::{ use opentelemetry_sdk::metrics::{ data::ResourceMetrics, exporter::PushMetricExporter, MetricError, MetricResult, Temporality, }; -use prost::Message; - -use async_trait::async_trait; use std::fmt::{Debug, Formatter}; -use crate::etw; +use async_trait::async_trait; +use prost::Message; pub struct MetricsExporter {} From afac194cea7d64c4b4a31bda948bd516a8b31d0c Mon Sep 17 00:00:00 2001 From: Matthew Boddewyn <31598686+mattbodd@users.noreply.github.com> Date: Tue, 17 Dec 2024 12:38:35 -0800 Subject: [PATCH 12/24] Attempt to bench mark export function more explicitly --- opentelemetry-etw-metrics/benches/exporter.rs | 80 ++++++++++++------- 1 file changed, 51 insertions(+), 29 deletions(-) diff --git a/opentelemetry-etw-metrics/benches/exporter.rs b/opentelemetry-etw-metrics/benches/exporter.rs index 5a4fd7e6..81cf7626 100644 --- a/opentelemetry-etw-metrics/benches/exporter.rs +++ b/opentelemetry-etw-metrics/benches/exporter.rs @@ -13,45 +13,67 @@ RAM: 64.0 GB | exporter | 847.38µs | */ -use opentelemetry::{metrics::MeterProvider as _, KeyValue}; +use opentelemetry::{InstrumentationScope, KeyValue}; use opentelemetry_etw_metrics::MetricsExporter; + use opentelemetry_sdk::{ - metrics::{reader::MetricReader, PeriodicReader, SdkMeterProvider}, - runtime, Resource, + metrics::{ + data::{DataPoint, Metric, ResourceMetrics, ScopeMetrics, Sum}, + exporter::PushMetricExporter, + Temporality, + }, + Resource, }; use criterion::{criterion_group, criterion_main, Criterion}; -fn export() { - // Create a new tokio runtime that blocks on the async execution - tokio::runtime::Builder::new_multi_thread() +async fn export(mut resource_metrics: ResourceMetrics) { + let exporter = MetricsExporter::new(); + exporter.export(&mut resource_metrics).await.unwrap(); +} + +fn create_resource_metrics() -> ResourceMetrics { + let data_point = DataPoint { + attributes: vec![KeyValue::new("datapoint key", "datapoint value")], + start_time: Some(std::time::SystemTime::now()), + time: Some(std::time::SystemTime::now()), + value: 1.0_f64, + exemplars: vec![], + }; + + let sum: Sum = Sum { + data_points: vec![data_point.clone(), data_point.clone(), data_point], + temporality: Temporality::Delta, + is_monotonic: true, + }; + + let resource_metrics = ResourceMetrics { + resource: Resource::new(vec![KeyValue::new("service.name", "my-service")]), + scope_metrics: vec![ScopeMetrics { + scope: InstrumentationScope::default(), + metrics: vec![Metric { + name: "metric_name".into(), + description: "metric description".into(), + unit: "metric unit".into(), + data: Box::new(sum), + }], + }], + }; + + resource_metrics +} + +fn criterion_benchmark(c: &mut Criterion) { + let runtime = tokio::runtime::Builder::new_multi_thread() .worker_threads(1) .enable_all() .build() - .unwrap() - .block_on(async { - let exporter = MetricsExporter::new(); - let reader = PeriodicReader::builder(exporter, runtime::Tokio).build(); - let meter_provider = SdkMeterProvider::builder() - .with_resource(Resource::new(vec![KeyValue::new( - "service.name", - "service-name", - )])) - .with_reader(reader.clone()) - .build(); - let meter = meter_provider.meter("etw-bench"); - let gauge = meter.u64_gauge("gauge").build(); - - for _ in 0..10_000 { - gauge.record(1, &[KeyValue::new("key", "value")]); - } - - reader.force_flush().unwrap(); - }); -} + .unwrap(); -fn criterion_benchmark(c: &mut Criterion) { - c.bench_function("export", |b| b.iter(|| export())); + c.bench_function("export", |b| { + b.to_async(&runtime) + .iter(|| export(create_resource_metrics())) + }); } criterion_group!(benches, criterion_benchmark); From 4bb3bf1560e8a4552fc7aee2688615aab3ff03a9 Mon Sep 17 00:00:00 2001 From: Matthew Boddewyn <31598686+mattbodd@users.noreply.github.com> Date: Tue, 17 Dec 2024 12:38:54 -0800 Subject: [PATCH 13/24] Use tokio async with criterion --- opentelemetry-etw-metrics/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opentelemetry-etw-metrics/Cargo.toml b/opentelemetry-etw-metrics/Cargo.toml index 119d67f9..d2bf3b46 100644 --- a/opentelemetry-etw-metrics/Cargo.toml +++ b/opentelemetry-etw-metrics/Cargo.toml @@ -18,7 +18,7 @@ async-trait = "0.1" prost = "0.13" tracelogging = "1.2.1" tracing = { version = "0.1", optional = true } -criterion = { workspace = true, features = ["html_reports"] } +criterion = { workspace = true, features = ["html_reports", "async_tokio"] } [dev-dependencies] tokio = { version = "1.0", features = ["full"] } From 3bbe76a744269208fac940d31dae367994f149a6 Mon Sep 17 00:00:00 2001 From: Matthew Boddewyn <31598686+mattbodd@users.noreply.github.com> Date: Tue, 17 Dec 2024 12:57:01 -0800 Subject: [PATCH 14/24] Do no create a new exporter for each exporter benchmark --- opentelemetry-etw-metrics/benches/exporter.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/opentelemetry-etw-metrics/benches/exporter.rs b/opentelemetry-etw-metrics/benches/exporter.rs index 81cf7626..e4ce56aa 100644 --- a/opentelemetry-etw-metrics/benches/exporter.rs +++ b/opentelemetry-etw-metrics/benches/exporter.rs @@ -27,8 +27,7 @@ use opentelemetry_sdk::{ use criterion::{criterion_group, criterion_main, Criterion}; -async fn export(mut resource_metrics: ResourceMetrics) { - let exporter = MetricsExporter::new(); +async fn export(exporter: &MetricsExporter, mut resource_metrics: ResourceMetrics) { exporter.export(&mut resource_metrics).await.unwrap(); } @@ -70,9 +69,11 @@ fn criterion_benchmark(c: &mut Criterion) { .build() .unwrap(); + let exporter = MetricsExporter::new(); + c.bench_function("export", |b| { b.to_async(&runtime) - .iter(|| export(create_resource_metrics())) + .iter(|| export(&exporter, create_resource_metrics())) }); } From c817fa2948796815a2b313ce367230370e26cfda Mon Sep 17 00:00:00 2001 From: Matthew Boddewyn <31598686+mattbodd@users.noreply.github.com> Date: Tue, 17 Dec 2024 13:01:10 -0800 Subject: [PATCH 15/24] Remove etw benchmark and revert etw module to be private --- opentelemetry-etw-metrics/benches/etw.rs | 29 ------------------------ opentelemetry-etw-metrics/src/lib.rs | 2 +- 2 files changed, 1 insertion(+), 30 deletions(-) delete mode 100644 opentelemetry-etw-metrics/benches/etw.rs diff --git a/opentelemetry-etw-metrics/benches/etw.rs b/opentelemetry-etw-metrics/benches/etw.rs deleted file mode 100644 index fdb1a842..00000000 --- a/opentelemetry-etw-metrics/benches/etw.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! run with `$ cargo bench --bench etw -- --exact ` to run specific test for logs -//! So to run test named "fibonacci" you would run `$ cargo bench --bench etw -- --exact fibonacci` -//! To run all tests for logs you would run `$ cargo bench --bench etw` -//! -/* -The benchmark results: -criterion = "0.5.1" -OS: Windows 11 Enterprise N, 23H2, Build 22631.4460 -Hardware: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz 2.79 GHz, 16vCPUs -RAM: 64.0 GB -| Test | Average time| -|--------------------------------|-------------| -| write_event | 2.0649ns | -*/ - -use criterion::{criterion_group, criterion_main, Criterion}; -use opentelemetry_etw_metrics::etw::write; - -fn write_event() { - let buffer = "This is a test buffer".as_bytes(); - write(buffer); -} - -fn criterion_benchmark(c: &mut Criterion) { - c.bench_function("write_event", |b| b.iter(|| write_event())); -} - -criterion_group!(benches, criterion_benchmark); -criterion_main!(benches); diff --git a/opentelemetry-etw-metrics/src/lib.rs b/opentelemetry-etw-metrics/src/lib.rs index 5b6029fc..56689e9a 100644 --- a/opentelemetry-etw-metrics/src/lib.rs +++ b/opentelemetry-etw-metrics/src/lib.rs @@ -1,4 +1,4 @@ -pub mod etw; +mod etw; mod exporter; pub use exporter::MetricsExporter; From 4b995d1c71f029462968f8a2fbb639007d8a2114 Mon Sep 17 00:00:00 2001 From: Matthew Boddewyn <31598686+mattbodd@users.noreply.github.com> Date: Tue, 17 Dec 2024 13:16:19 -0800 Subject: [PATCH 16/24] Remove etw benchmark from Cargo.toml --- opentelemetry-etw-metrics/Cargo.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/opentelemetry-etw-metrics/Cargo.toml b/opentelemetry-etw-metrics/Cargo.toml index d2bf3b46..f4e2d655 100644 --- a/opentelemetry-etw-metrics/Cargo.toml +++ b/opentelemetry-etw-metrics/Cargo.toml @@ -30,10 +30,6 @@ default = ["internal-logs"] [package.metadata.cargo-machete] ignored = ["tracing"] -[[bench]] -name = "etw" -harness = false - [[bench]] name = "exporter" harness = false From 36102a863c7d92a490abc507579c2e1f86b1ad82 Mon Sep 17 00:00:00 2001 From: Matthew Boddewyn <31598686+mattbodd@users.noreply.github.com> Date: Tue, 17 Dec 2024 13:37:57 -0800 Subject: [PATCH 17/24] Use 10 metrics in the ResourceMetrics that is exported --- opentelemetry-etw-metrics/benches/exporter.rs | 52 ++++++++++++------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/opentelemetry-etw-metrics/benches/exporter.rs b/opentelemetry-etw-metrics/benches/exporter.rs index e4ce56aa..dc95ec72 100644 --- a/opentelemetry-etw-metrics/benches/exporter.rs +++ b/opentelemetry-etw-metrics/benches/exporter.rs @@ -32,30 +32,46 @@ async fn export(exporter: &MetricsExporter, mut resource_metrics: ResourceMetric } fn create_resource_metrics() -> ResourceMetrics { - let data_point = DataPoint { - attributes: vec![KeyValue::new("datapoint key", "datapoint value")], - start_time: Some(std::time::SystemTime::now()), - time: Some(std::time::SystemTime::now()), - value: 1.0_f64, - exemplars: vec![], - }; + // Metric does not implement clone so this helper function is used to create a metric + fn create_metric() -> Metric { + let data_point = DataPoint { + attributes: vec![KeyValue::new("datapoint key", "datapoint value")], + start_time: Some(std::time::SystemTime::now()), + time: Some(std::time::SystemTime::now()), + value: 1.0_f64, + exemplars: vec![], + }; - let sum: Sum = Sum { - data_points: vec![data_point.clone(), data_point.clone(), data_point], - temporality: Temporality::Delta, - is_monotonic: true, - }; + let sum: Sum = Sum { + data_points: vec![data_point.clone(); 2_000], + temporality: Temporality::Delta, + is_monotonic: true, + }; + + Metric { + name: "metric_name".into(), + description: "metric description".into(), + unit: "metric unit".into(), + data: Box::new(sum), + } + } let resource_metrics = ResourceMetrics { resource: Resource::new(vec![KeyValue::new("service.name", "my-service")]), scope_metrics: vec![ScopeMetrics { scope: InstrumentationScope::default(), - metrics: vec![Metric { - name: "metric_name".into(), - description: "metric description".into(), - unit: "metric unit".into(), - data: Box::new(sum), - }], + metrics: vec![ + create_metric(), + create_metric(), + create_metric(), + create_metric(), + create_metric(), + create_metric(), + create_metric(), + create_metric(), + create_metric(), + create_metric(), + ], }], }; From 8b395451dc5e253ba736d19fa05e7af58044f608 Mon Sep 17 00:00:00 2001 From: Matthew Boddewyn <31598686+mattbodd@users.noreply.github.com> Date: Tue, 17 Dec 2024 13:49:52 -0800 Subject: [PATCH 18/24] Reuse the same ResourceMetric for every benchmark loop iteration in export benchmark --- opentelemetry-etw-metrics/benches/exporter.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/opentelemetry-etw-metrics/benches/exporter.rs b/opentelemetry-etw-metrics/benches/exporter.rs index dc95ec72..5546aa50 100644 --- a/opentelemetry-etw-metrics/benches/exporter.rs +++ b/opentelemetry-etw-metrics/benches/exporter.rs @@ -27,8 +27,8 @@ use opentelemetry_sdk::{ use criterion::{criterion_group, criterion_main, Criterion}; -async fn export(exporter: &MetricsExporter, mut resource_metrics: ResourceMetrics) { - exporter.export(&mut resource_metrics).await.unwrap(); +async fn export(exporter: &MetricsExporter, resource_metrics: &mut ResourceMetrics) { + exporter.export(resource_metrics).await.unwrap(); } fn create_resource_metrics() -> ResourceMetrics { @@ -85,11 +85,18 @@ fn criterion_benchmark(c: &mut Criterion) { .build() .unwrap(); - let exporter = MetricsExporter::new(); + c.bench_function("export", move |b| { + b.to_async(&runtime).iter_custom(|iters| async move { + let exporter = MetricsExporter::new(); - c.bench_function("export", |b| { - b.to_async(&runtime) - .iter(|| export(&exporter, create_resource_metrics())) + let mut resource_metrics = create_resource_metrics(); + + let start = std::time::Instant::now(); + for _i in 0..iters { + export(&exporter, &mut resource_metrics).await + } + start.elapsed() + }) }); } From 651913e028a4aa053e695667a0fe61dbbaf753b6 Mon Sep 17 00:00:00 2001 From: Matthew Boddewyn <31598686+mattbodd@users.noreply.github.com> Date: Tue, 17 Dec 2024 14:15:37 -0800 Subject: [PATCH 19/24] Avoid unecessary let (clippy lint) --- opentelemetry-etw-metrics/benches/exporter.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/opentelemetry-etw-metrics/benches/exporter.rs b/opentelemetry-etw-metrics/benches/exporter.rs index 5546aa50..bdc4c1c7 100644 --- a/opentelemetry-etw-metrics/benches/exporter.rs +++ b/opentelemetry-etw-metrics/benches/exporter.rs @@ -56,7 +56,7 @@ fn create_resource_metrics() -> ResourceMetrics { } } - let resource_metrics = ResourceMetrics { + ResourceMetrics { resource: Resource::new(vec![KeyValue::new("service.name", "my-service")]), scope_metrics: vec![ScopeMetrics { scope: InstrumentationScope::default(), @@ -73,9 +73,7 @@ fn create_resource_metrics() -> ResourceMetrics { create_metric(), ], }], - }; - - resource_metrics + } } fn criterion_benchmark(c: &mut Criterion) { From b29b5306d988c02863e26045b87182bf4ba738d8 Mon Sep 17 00:00:00 2001 From: Matthew Boddewyn <31598686+mattbodd@users.noreply.github.com> Date: Tue, 17 Dec 2024 16:27:38 -0800 Subject: [PATCH 20/24] Manage own async runtime instead of relying on criterion's --- opentelemetry-etw-metrics/Cargo.toml | 2 +- opentelemetry-etw-metrics/benches/exporter.rs | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/opentelemetry-etw-metrics/Cargo.toml b/opentelemetry-etw-metrics/Cargo.toml index f4e2d655..e67284a8 100644 --- a/opentelemetry-etw-metrics/Cargo.toml +++ b/opentelemetry-etw-metrics/Cargo.toml @@ -18,7 +18,7 @@ async-trait = "0.1" prost = "0.13" tracelogging = "1.2.1" tracing = { version = "0.1", optional = true } -criterion = { workspace = true, features = ["html_reports", "async_tokio"] } +criterion = { workspace = true, features = ["html_reports"] } [dev-dependencies] tokio = { version = "1.0", features = ["full"] } diff --git a/opentelemetry-etw-metrics/benches/exporter.rs b/opentelemetry-etw-metrics/benches/exporter.rs index bdc4c1c7..5de1454e 100644 --- a/opentelemetry-etw-metrics/benches/exporter.rs +++ b/opentelemetry-etw-metrics/benches/exporter.rs @@ -84,14 +84,17 @@ fn criterion_benchmark(c: &mut Criterion) { .unwrap(); c.bench_function("export", move |b| { - b.to_async(&runtime).iter_custom(|iters| async move { + b.iter_custom(|iters| { let exporter = MetricsExporter::new(); let mut resource_metrics = create_resource_metrics(); let start = std::time::Instant::now(); + for _i in 0..iters { - export(&exporter, &mut resource_metrics).await + runtime.block_on(async { + export(&exporter, &mut resource_metrics).await; + }); } start.elapsed() }) From 72a6bc9e36358cb571e4835ad0de84326813f8c5 Mon Sep 17 00:00:00 2001 From: Matthew Boddewyn <31598686+mattbodd@users.noreply.github.com> Date: Thu, 19 Dec 2024 13:03:00 -0800 Subject: [PATCH 21/24] Move criterion to dev-dependencies in etw-metrics --- opentelemetry-etw-metrics/Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/opentelemetry-etw-metrics/Cargo.toml b/opentelemetry-etw-metrics/Cargo.toml index e67284a8..4fae806c 100644 --- a/opentelemetry-etw-metrics/Cargo.toml +++ b/opentelemetry-etw-metrics/Cargo.toml @@ -18,10 +18,9 @@ async-trait = "0.1" prost = "0.13" tracelogging = "1.2.1" tracing = { version = "0.1", optional = true } -criterion = { workspace = true, features = ["html_reports"] } - [dev-dependencies] tokio = { version = "1.0", features = ["full"] } +criterion = { workspace = true, features = ["html_reports"] } [features] internal-logs = ["tracing"] From 128490a8ce78b8ceb1d2c0e65c7d706268417820 Mon Sep 17 00:00:00 2001 From: Matthew Boddewyn <31598686+mattbodd@users.noreply.github.com> Date: Mon, 13 Jan 2025 13:27:40 -0800 Subject: [PATCH 22/24] Use prost's Message::encoded_len method when allocating the encoding buffer in emit_export_metric_service_request --- opentelemetry-etw-metrics/src/exporter/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opentelemetry-etw-metrics/src/exporter/mod.rs b/opentelemetry-etw-metrics/src/exporter/mod.rs index 13fb4249..de3189c1 100644 --- a/opentelemetry-etw-metrics/src/exporter/mod.rs +++ b/opentelemetry-etw-metrics/src/exporter/mod.rs @@ -44,7 +44,7 @@ impl Debug for MetricsExporter { fn emit_export_metric_service_request( export_metric_service_request: &ExportMetricsServiceRequest, ) -> MetricResult<()> { - let mut encoding_buffer = Vec::new(); + let mut encoding_buffer = Vec::with_capacity(export_metric_service_request.encoded_len()); export_metric_service_request .encode(&mut encoding_buffer) From 1fe84ff7a94f36539f97407e8f2a3841419bdc83 Mon Sep 17 00:00:00 2001 From: Matthew Boddewyn <31598686+mattbodd@users.noreply.github.com> Date: Tue, 14 Jan 2025 15:42:58 -0800 Subject: [PATCH 23/24] Share mutable vector between exit_export_metric_service_request invocations to avoid repeat allocations as much as possible --- opentelemetry-etw-metrics/src/exporter/mod.rs | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/opentelemetry-etw-metrics/src/exporter/mod.rs b/opentelemetry-etw-metrics/src/exporter/mod.rs index de3189c1..8ac94708 100644 --- a/opentelemetry-etw-metrics/src/exporter/mod.rs +++ b/opentelemetry-etw-metrics/src/exporter/mod.rs @@ -43,17 +43,21 @@ impl Debug for MetricsExporter { fn emit_export_metric_service_request( export_metric_service_request: &ExportMetricsServiceRequest, + encoding_buffer: &mut Vec, ) -> MetricResult<()> { - let mut encoding_buffer = Vec::with_capacity(export_metric_service_request.encoded_len()); + if (export_metric_service_request.encoded_len()) > etw::MAX_EVENT_SIZE { + otel_warn!(name: "MetricExportFailedDueToMaxSizeLimit", size = export_metric_service_request.encoded_len(), max_size = etw::MAX_EVENT_SIZE); + } else { + encoding_buffer.resize_with( + export_metric_service_request.encoded_len(), + Default::default, + ); - export_metric_service_request - .encode(&mut encoding_buffer) - .map_err(|err| MetricError::Other(err.to_string()))?; + export_metric_service_request + .encode(encoding_buffer) + .map_err(|err| MetricError::Other(err.to_string()))?; - if (encoding_buffer.len()) > etw::MAX_EVENT_SIZE { - otel_warn!(name: "MetricExportFailedDueToMaxSizeLimit", size = encoding_buffer.len(), max_size = etw::MAX_EVENT_SIZE); - } else { - let result = etw::write(&encoding_buffer); + let result = etw::write(encoding_buffer); // TODO: Better logging/internal metrics needed here for non-failure // case Uncomment the line below to see the exported bytes until a // better logging solution is implemented @@ -75,6 +79,8 @@ impl PushMetricExporter for MetricsExporter { .map(Into::into) .unwrap_or_default(); + let mut encoding_buffer = Vec::new(); + for scope_metric in &metrics.scope_metrics { for metric in &scope_metric.metrics { let proto_data: Option = metric.data.as_any().try_into().ok(); @@ -111,6 +117,7 @@ impl PushMetricExporter for MetricsExporter { })); emit_export_metric_service_request( &export_metrics_service_request, + &mut encoding_buffer, )?; } } @@ -127,6 +134,7 @@ impl PushMetricExporter for MetricsExporter { )); emit_export_metric_service_request( &export_metrics_service_request, + &mut encoding_buffer, )?; } } @@ -140,6 +148,7 @@ impl PushMetricExporter for MetricsExporter { })); emit_export_metric_service_request( &export_metrics_service_request, + &mut encoding_buffer, )?; } } @@ -155,6 +164,7 @@ impl PushMetricExporter for MetricsExporter { })); emit_export_metric_service_request( &export_metrics_service_request, + &mut encoding_buffer, )?; } } @@ -168,6 +178,7 @@ impl PushMetricExporter for MetricsExporter { })); emit_export_metric_service_request( &export_metrics_service_request, + &mut encoding_buffer, )?; } } From 632a02fc1b6f02e24e6417588d7f164d45a1e6d6 Mon Sep 17 00:00:00 2001 From: Matthew Boddewyn <31598686+mattbodd@users.noreply.github.com> Date: Tue, 14 Jan 2025 15:58:37 -0800 Subject: [PATCH 24/24] Update average bench runtime --- opentelemetry-etw-metrics/benches/exporter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opentelemetry-etw-metrics/benches/exporter.rs b/opentelemetry-etw-metrics/benches/exporter.rs index 5de1454e..5ffa1e56 100644 --- a/opentelemetry-etw-metrics/benches/exporter.rs +++ b/opentelemetry-etw-metrics/benches/exporter.rs @@ -10,7 +10,7 @@ Hardware: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz 2.79 GHz, 16vCPUs RAM: 64.0 GB | Test | Average time| |--------------------------------|-------------| -| exporter | 847.38µs | +| exporter | 22.203 ms | */ use opentelemetry::{InstrumentationScope, KeyValue};