Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions exporters/otlp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -999,3 +999,14 @@ if(BUILD_TESTING)
TEST_LIST otlp_file_metric_exporter_factory_test)
endif()
endif() # BUILD_TESTING

if(WITH_BENCHMARK AND WITH_OTLP_HTTP)
add_executable(otlp_http_metric_exporter_benchmark
benchmark/otlp_http_metric_exporter_benchmark.cc)

target_link_libraries(
otlp_http_metric_exporter_benchmark
PRIVATE opentelemetry_exporter_otlp_http_metric opentelemetry_metrics
opentelemetry_resources benchmark::benchmark
${CMAKE_THREAD_LIBS_INIT})
endif()
128 changes: 128 additions & 0 deletions exporters/otlp/benchmark/otlp_http_metric_exporter_benchmark.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

#include <benchmark/benchmark.h>

#include <chrono>
#include <cstddef>
#include <string>
#include <utility>
#include <vector>

#include "opentelemetry/exporters/otlp/otlp_http.h"
#include "opentelemetry/exporters/otlp/otlp_http_metric_exporter.h"
#include "opentelemetry/exporters/otlp/otlp_http_metric_exporter_options.h"

#include "opentelemetry/nostd/string_view.h"

#include "opentelemetry/sdk/common/attribute_utils.h"
#include "opentelemetry/sdk/common/global_log_handler.h"

#include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h"

#include "opentelemetry/sdk/metrics/data/metric_data.h"
#include "opentelemetry/sdk/metrics/data/point_data.h"
#include "opentelemetry/sdk/metrics/export/metric_producer.h"
#include "opentelemetry/sdk/metrics/instruments.h"
#include "opentelemetry/sdk/resource/resource.h"

using InstrumentationScope = opentelemetry::sdk::instrumentationscope::InstrumentationScope;
using namespace opentelemetry::sdk::metrics;
using namespace opentelemetry::exporter::otlp;

// Create ResourceMetrics with a single metric containing 'n' data points
static ResourceMetrics MakeMetrics(std::size_t n_points)
{
ResourceMetrics rm;
static const opentelemetry::sdk::resource::Resource resource =
opentelemetry::sdk::resource::Resource::Create({{"service.name", "metric-benchmark"}});
rm.resource_ = &resource;

static auto scope = InstrumentationScope::Create("benchmark", "1.0.0");
ScopeMetrics scope_metrics;
scope_metrics.scope_ = scope.get();

MetricData metric;
metric.instrument_descriptor.name_ = "cpu.utilization";
metric.instrument_descriptor.unit_ = "1";
metric.instrument_descriptor.type_ = InstrumentType::kCounter;
metric.aggregation_temporality = AggregationTemporality::kCumulative;

for (std::size_t i = 0; i < n_points; ++i)
{
SumPointData sum;
sum.value_ = static_cast<double>(i);
sum.is_monotonic_ = true;

PointDataAttributes p;
p.attributes = {{"core", static_cast<int>(i)}};
p.point_data = sum;

metric.point_data_attr_.push_back(p);
}

scope_metrics.metric_data_.push_back(metric);
rm.scope_metric_data_.push_back(scope_metrics);
return rm;
}

// Benchmark Export() using Binary encoding
static void BM_OtlpHttpMetricExporter_Export_Binary(benchmark::State &state)
{
OtlpHttpMetricExporterOptions opts;
opts.url = "http://localhost:4318/v1/metrics";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems this benchmark attempts real HTTP calls that fail immediately with connection refused. Even a failed connection attempt adds ~10-100μs of overhead, which becomes significant when serialization itself takes only nanoseconds (especially for small payloads).

See how gRPC benchmark avoid this be mocking the network layer entirely. Something we can try here ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, even a failed HTTP attempt adds non-trivial overhead for smaller payload sizes, where serialization itself is very cheap.

Unlike gRPC, the HTTP metric exporter doesn’t currently expose a public way to inject a no-send transport. The only existing injection path goes through test-only peers, and I avoided reusing that here since this is a benchmark target and not built with test permissions.

Longer term, possible options could be (1) making the HTTP client injectable via a public API, (2) reusing the existing test peer, or (3) introducing a dedicated benchmark peer. But all of these require changes to exporter headers or test/benchmark boundaries and felt out of scope for this PR. For now, this benchmark measures Export() through the public API with retries and logging disabled.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for clarifying. Agree with the constraints. For future, reusing the existing test peer infrastructure might be the most practical option, but that's not necessary for this PR to be valuable. I’m okay with landing this as a first baseline and revisiting injection options in a follow-up.

opts.content_type = HttpRequestContentType::kBinary;
opts.timeout = std::chrono::milliseconds(1);
opts.retry_policy_max_attempts = 0;

OtlpHttpMetricExporter exporter(opts);
auto metrics = MakeMetrics(state.range(0));

for (auto _ : state)
{
benchmark::DoNotOptimize(exporter.Export(metrics));
}

state.SetItemsProcessed(state.iterations() * state.range(0));
}

// Benchmark Export() using JSON encoding
static void BM_OtlpHttpMetricExporter_Export_Json(benchmark::State &state)
{
OtlpHttpMetricExporterOptions opts;
opts.url = "http://localhost:4318/v1/metrics";
opts.content_type = HttpRequestContentType::kJson;
opts.timeout = std::chrono::milliseconds(1);
opts.retry_policy_max_attempts = 0;

OtlpHttpMetricExporter exporter(opts);
auto metrics = MakeMetrics(state.range(0));

for (auto _ : state)
{
benchmark::DoNotOptimize(exporter.Export(metrics));
}

state.SetItemsProcessed(state.iterations() * state.range(0));
}

BENCHMARK(BM_OtlpHttpMetricExporter_Export_Binary)
->RangeMultiplier(10)
->Range(1, 10000)
->Unit(benchmark::kMicrosecond);

BENCHMARK(BM_OtlpHttpMetricExporter_Export_Json)
->RangeMultiplier(10)
->Range(1, 10000)
->Unit(benchmark::kMicrosecond);

int main(int argc, char **argv)
{
opentelemetry::sdk::common::internal_log::GlobalLogHandler::SetLogLevel(
opentelemetry::sdk::common::internal_log::LogLevel::None);

benchmark::Initialize(&argc, argv);
benchmark::RunSpecifiedBenchmarks();
benchmark::Shutdown();
return 0;
}
Loading