From 46b16ec3cf6ce622c0e074a0f2bbe73cf269798d Mon Sep 17 00:00:00 2001 From: Lalit Kumar Bhasin Date: Wed, 11 Jan 2023 16:49:35 -0800 Subject: [PATCH] Histogram Aggregation: Fix bucket detection logic, performance improvements, and benchmark tests (#1869) --- examples/metrics_simple/metrics_ostream.cc | 4 +- exporters/ostream/test/ostream_metric_test.cc | 4 +- .../test/otlp_metrics_serialization_test.cc | 4 +- .../exporters/prometheus/exporter_utils.h | 4 +- exporters/prometheus/src/exporter_utils.cc | 4 +- .../metrics/aggregation/aggregation_config.h | 6 +- .../aggregation/histogram_aggregation.h | 7 + .../sdk/metrics/data/point_data.h | 18 +- .../metrics/state/temporal_metric_storage.h | 1 + .../aggregation/histogram_aggregation.cc | 58 ++-- sdk/test/metrics/BUILD | 32 ++ sdk/test/metrics/CMakeLists.txt | 8 + sdk/test/metrics/aggregation_test.cc | 32 +- .../histogram_aggregation_benchmark.cc | 85 ++++++ sdk/test/metrics/histogram_test.cc | 284 ++++++++++++++++++ 15 files changed, 481 insertions(+), 70 deletions(-) create mode 100644 sdk/test/metrics/histogram_aggregation_benchmark.cc create mode 100644 sdk/test/metrics/histogram_test.cc diff --git a/examples/metrics_simple/metrics_ostream.cc b/examples/metrics_simple/metrics_ostream.cc index a802b870f1..30a82a02b5 100644 --- a/examples/metrics_simple/metrics_ostream.cc +++ b/examples/metrics_simple/metrics_ostream.cc @@ -74,8 +74,8 @@ void InitMetrics(const std::string &name) std::shared_ptr aggregation_config{ new opentelemetry::sdk::metrics::HistogramAggregationConfig}; static_cast(aggregation_config.get()) - ->boundaries_ = std::list{0.0, 50.0, 100.0, 250.0, 500.0, 750.0, - 1000.0, 2500.0, 5000.0, 10000.0, 20000.0}; + ->boundaries_ = std::vector{0.0, 50.0, 100.0, 250.0, 500.0, 750.0, + 1000.0, 2500.0, 5000.0, 10000.0, 20000.0}; std::unique_ptr histogram_view{new metric_sdk::View{ name, "description", metric_sdk::AggregationType::kHistogram, aggregation_config}}; p->AddView(std::move(histogram_instrument_selector), std::move(histogram_meter_selector), diff --git a/exporters/ostream/test/ostream_metric_test.cc b/exporters/ostream/test/ostream_metric_test.cc index 9aca741d67..cb4a8a3ab4 100644 --- a/exporters/ostream/test/ostream_metric_test.cc +++ b/exporters/ostream/test/ostream_metric_test.cc @@ -97,14 +97,14 @@ TEST(OStreamMetricsExporter, ExportHistogramPointData) std::unique_ptr(new exportermetrics::OStreamMetricExporter); metric_sdk::HistogramPointData histogram_point_data{}; - histogram_point_data.boundaries_ = std::list{10.1, 20.2, 30.2}; + histogram_point_data.boundaries_ = std::vector{10.1, 20.2, 30.2}; histogram_point_data.count_ = 3; histogram_point_data.counts_ = {200, 300, 400, 500}; histogram_point_data.sum_ = 900.5; histogram_point_data.min_ = 1.8; histogram_point_data.max_ = 12.0; metric_sdk::HistogramPointData histogram_point_data2{}; - histogram_point_data2.boundaries_ = std::list{10.0, 20.0, 30.0}; + histogram_point_data2.boundaries_ = std::vector{10.0, 20.0, 30.0}; histogram_point_data2.count_ = 3; histogram_point_data2.counts_ = {200, 300, 400, 500}; histogram_point_data2.sum_ = (int64_t)900; diff --git a/exporters/otlp/test/otlp_metrics_serialization_test.cc b/exporters/otlp/test/otlp_metrics_serialization_test.cc index adc4f488b0..cc3781c66f 100644 --- a/exporters/otlp/test/otlp_metrics_serialization_test.cc +++ b/exporters/otlp/test/otlp_metrics_serialization_test.cc @@ -54,11 +54,11 @@ static metrics_sdk::MetricData CreateHistogramAggregationData() s_data_1.sum_ = 100.2; s_data_1.count_ = 22; s_data_1.counts_ = {2, 9, 4, 7}; - s_data_1.boundaries_ = std::list({0.0, 10.0, 20.0, 30.0}); + s_data_1.boundaries_ = std::vector({0.0, 10.0, 20.0, 30.0}); s_data_2.sum_ = 200.2; s_data_2.count_ = 20; s_data_2.counts_ = {0, 8, 5, 7}; - s_data_2.boundaries_ = std::list({0.0, 10.0, 20.0, 30.0}); + s_data_2.boundaries_ = std::vector({0.0, 10.0, 20.0, 30.0}); data.aggregation_temporality = metrics_sdk::AggregationTemporality::kCumulative; data.end_ts = opentelemetry::common::SystemTimestamp(std::chrono::system_clock::now()); diff --git a/exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter_utils.h b/exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter_utils.h index d85d6d8a0c..ab5f50058c 100644 --- a/exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter_utils.h +++ b/exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter_utils.h @@ -67,7 +67,7 @@ class PrometheusExporterUtils */ template static void SetData(std::vector values, - const std::list &boundaries, + const std::vector &boundaries, const std::vector &counts, const opentelemetry::sdk::metrics::PointAttributes &labels, std::chrono::nanoseconds time, @@ -104,7 +104,7 @@ class PrometheusExporterUtils */ template static void SetValue(std::vector values, - const std::list &boundaries, + const std::vector &boundaries, const std::vector &counts, ::prometheus::ClientMetric *metric); }; diff --git a/exporters/prometheus/src/exporter_utils.cc b/exporters/prometheus/src/exporter_utils.cc index a8c648132e..3fc71180e5 100644 --- a/exporters/prometheus/src/exporter_utils.cc +++ b/exporters/prometheus/src/exporter_utils.cc @@ -221,7 +221,7 @@ void PrometheusExporterUtils::SetData(std::vector values, */ template void PrometheusExporterUtils::SetData(std::vector values, - const std::list &boundaries, + const std::vector &boundaries, const std::vector &counts, const metric_sdk::PointAttributes &labels, std::chrono::nanoseconds time, @@ -340,7 +340,7 @@ void PrometheusExporterUtils::SetValue(std::vector values, */ template void PrometheusExporterUtils::SetValue(std::vector values, - const std::list &boundaries, + const std::vector &boundaries, const std::vector &counts, prometheus_client::ClientMetric *metric) { diff --git a/sdk/include/opentelemetry/sdk/metrics/aggregation/aggregation_config.h b/sdk/include/opentelemetry/sdk/metrics/aggregation/aggregation_config.h index ee297881d0..69dc4c8864 100644 --- a/sdk/include/opentelemetry/sdk/metrics/aggregation/aggregation_config.h +++ b/sdk/include/opentelemetry/sdk/metrics/aggregation/aggregation_config.h @@ -3,8 +3,10 @@ #pragma once -#include #include "opentelemetry/version.h" + +#include + OPENTELEMETRY_BEGIN_NAMESPACE namespace sdk { @@ -19,7 +21,7 @@ class AggregationConfig class HistogramAggregationConfig : public AggregationConfig { public: - std::list boundaries_; + std::vector boundaries_; bool record_min_max_ = true; }; } // namespace metrics diff --git a/sdk/include/opentelemetry/sdk/metrics/aggregation/histogram_aggregation.h b/sdk/include/opentelemetry/sdk/metrics/aggregation/histogram_aggregation.h index ae9eeb4ded..5d1097d93f 100644 --- a/sdk/include/opentelemetry/sdk/metrics/aggregation/histogram_aggregation.h +++ b/sdk/include/opentelemetry/sdk/metrics/aggregation/histogram_aggregation.h @@ -108,6 +108,13 @@ void HistogramDiff(HistogramPointData ¤t, HistogramPointData &next, Histog diff.record_min_max_ = false; } +template +size_t BucketBinarySearch(T value, const std::vector &boundaries) +{ + auto low = std::lower_bound(boundaries.begin(), boundaries.end(), value); + return low - boundaries.begin(); +} + } // namespace metrics } // namespace sdk OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/metrics/data/point_data.h b/sdk/include/opentelemetry/sdk/metrics/data/point_data.h index 359da0b864..62ba3df1bc 100644 --- a/sdk/include/opentelemetry/sdk/metrics/data/point_data.h +++ b/sdk/include/opentelemetry/sdk/metrics/data/point_data.h @@ -8,7 +8,7 @@ #include "opentelemetry/sdk/metrics/instruments.h" #include "opentelemetry/version.h" -#include +#include OPENTELEMETRY_BEGIN_NAMESPACE namespace sdk @@ -55,14 +55,14 @@ class HistogramPointData HistogramPointData &operator=(HistogramPointData &&) = default; HistogramPointData(const HistogramPointData &) = default; HistogramPointData() = default; - HistogramPointData(std::list &boundaries) : boundaries_(boundaries) {} - std::list boundaries_ = {}; - ValueType sum_ = {}; - ValueType min_ = {}; - ValueType max_ = {}; - std::vector counts_ = {}; - uint64_t count_ = {}; - bool record_min_max_ = true; + HistogramPointData(std::vector &boundaries) : boundaries_(boundaries) {} + std::vector boundaries_ = {}; + ValueType sum_ = {}; + ValueType min_ = {}; + ValueType max_ = {}; + std::vector counts_ = {}; + uint64_t count_ = {}; + bool record_min_max_ = true; }; class DropPointData diff --git a/sdk/include/opentelemetry/sdk/metrics/state/temporal_metric_storage.h b/sdk/include/opentelemetry/sdk/metrics/state/temporal_metric_storage.h index 439ca3cd4d..17fe28d8bf 100644 --- a/sdk/include/opentelemetry/sdk/metrics/state/temporal_metric_storage.h +++ b/sdk/include/opentelemetry/sdk/metrics/state/temporal_metric_storage.h @@ -8,6 +8,7 @@ #include "opentelemetry/sdk/metrics/state/attributes_hashmap.h" #include "opentelemetry/sdk/metrics/state/metric_collector.h" +#include #include OPENTELEMETRY_BEGIN_NAMESPACE diff --git a/sdk/src/metrics/aggregation/histogram_aggregation.cc b/sdk/src/metrics/aggregation/histogram_aggregation.cc index 60dcc36827..fe01030f13 100644 --- a/sdk/src/metrics/aggregation/histogram_aggregation.cc +++ b/sdk/src/metrics/aggregation/histogram_aggregation.cc @@ -2,13 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 #include "opentelemetry/sdk/metrics/aggregation/histogram_aggregation.h" +#include "opentelemetry/version.h" + #include #include #include #include -#include "opentelemetry/version.h" - #include + OPENTELEMETRY_BEGIN_NAMESPACE namespace sdk { @@ -59,16 +60,8 @@ void LongHistogramAggregation::Aggregate(int64_t value, point_data_.min_ = std::min(nostd::get(point_data_.min_), value); point_data_.max_ = std::max(nostd::get(point_data_.max_), value); } - size_t index = 0; - for (auto it = point_data_.boundaries_.begin(); it != point_data_.boundaries_.end(); ++it) - { - if (value < *it) - { - point_data_.counts_[index] += 1; - return; - } - index++; - } + size_t index = BucketBinarySearch(value, point_data_.boundaries_); + point_data_.counts_[index] += 1; } std::unique_ptr LongHistogramAggregation::Merge( @@ -77,7 +70,10 @@ std::unique_ptr LongHistogramAggregation::Merge( auto curr_value = nostd::get(ToPoint()); auto delta_value = nostd::get( (static_cast(delta).ToPoint())); - LongHistogramAggregation *aggr = new LongHistogramAggregation(); + HistogramAggregationConfig agg_config; + agg_config.boundaries_ = curr_value.boundaries_; + agg_config.record_min_max_ = record_min_max_; + LongHistogramAggregation *aggr = new LongHistogramAggregation(&agg_config); HistogramMerge(curr_value, delta_value, aggr->point_data_); return std::unique_ptr(aggr); } @@ -87,7 +83,10 @@ std::unique_ptr LongHistogramAggregation::Diff(const Aggregation &n auto curr_value = nostd::get(ToPoint()); auto next_value = nostd::get( (static_cast(next).ToPoint())); - LongHistogramAggregation *aggr = new LongHistogramAggregation(); + HistogramAggregationConfig agg_config; + agg_config.boundaries_ = curr_value.boundaries_; + agg_config.record_min_max_ = record_min_max_; + LongHistogramAggregation *aggr = new LongHistogramAggregation(&agg_config); HistogramDiff(curr_value, next_value, aggr->point_data_); return std::unique_ptr(aggr); } @@ -107,8 +106,8 @@ DoubleHistogramAggregation::DoubleHistogramAggregation(const AggregationConfig * } else { - point_data_.boundaries_ = - std::list{0.0, 5.0, 10.0, 25.0, 50.0, 75.0, 100.0, 250.0, 500.0, 1000.0}; + point_data_.boundaries_ = {0.0, 5.0, 10.0, 25.0, 50.0, 75.0, 100.0, 250.0, + 500.0, 750.0, 1000.0, 2500.0, 5000.0, 7500.0, 10000.0}; } if (ac) { @@ -141,16 +140,8 @@ void DoubleHistogramAggregation::Aggregate(double value, point_data_.min_ = std::min(nostd::get(point_data_.min_), value); point_data_.max_ = std::max(nostd::get(point_data_.max_), value); } - size_t index = 0; - for (auto it = point_data_.boundaries_.begin(); it != point_data_.boundaries_.end(); ++it) - { - if (value < *it) - { - point_data_.counts_[index] += 1; - return; - } - index++; - } + size_t index = BucketBinarySearch(value, point_data_.boundaries_); + point_data_.counts_[index] += 1; } std::unique_ptr DoubleHistogramAggregation::Merge( @@ -159,12 +150,10 @@ std::unique_ptr DoubleHistogramAggregation::Merge( auto curr_value = nostd::get(ToPoint()); auto delta_value = nostd::get( (static_cast(delta).ToPoint())); - std::shared_ptr aggregation_config(new HistogramAggregationConfig); - static_cast(aggregation_config.get()) - ->boundaries_ = curr_value.boundaries_; - static_cast(aggregation_config.get()) - ->record_min_max_ = record_min_max_; - DoubleHistogramAggregation *aggr = new DoubleHistogramAggregation(aggregation_config.get()); + HistogramAggregationConfig agg_config; + agg_config.boundaries_ = curr_value.boundaries_; + agg_config.record_min_max_ = record_min_max_; + DoubleHistogramAggregation *aggr = new DoubleHistogramAggregation(&agg_config); HistogramMerge(curr_value, delta_value, aggr->point_data_); return std::unique_ptr(aggr); } @@ -175,7 +164,10 @@ std::unique_ptr DoubleHistogramAggregation::Diff( auto curr_value = nostd::get(ToPoint()); auto next_value = nostd::get( (static_cast(next).ToPoint())); - DoubleHistogramAggregation *aggr = new DoubleHistogramAggregation(); + HistogramAggregationConfig agg_config; + agg_config.boundaries_ = curr_value.boundaries_; + agg_config.record_min_max_ = record_min_max_; + DoubleHistogramAggregation *aggr = new DoubleHistogramAggregation(&agg_config); HistogramDiff(curr_value, next_value, aggr->point_data_); return std::unique_ptr(aggr); } diff --git a/sdk/test/metrics/BUILD b/sdk/test/metrics/BUILD index 294510b571..3904b15ea4 100644 --- a/sdk/test/metrics/BUILD +++ b/sdk/test/metrics/BUILD @@ -47,6 +47,22 @@ cc_test( ], ) +cc_test( + name = "histogram_test", + srcs = [ + "histogram_test.cc", + ], + tags = [ + "metrics", + "test", + ], + deps = [ + "//sdk/src/metrics", + "//sdk/src/resource", + "@com_google_googletest//:gtest_main", + ], +) + cc_test( name = "view_registry_test", srcs = [ @@ -265,3 +281,19 @@ otel_cc_benchmark( "//sdk/src/metrics", ], ) + +otel_cc_benchmark( + name = "histogram_aggregation_benchmark", + srcs = [ + "histogram_aggregation_benchmark.cc", + ], + tags = [ + "benchmark", + "metrics", + "test", + ], + deps = [ + "//sdk/src/metrics", + "//sdk/src/resource", + ], +) diff --git a/sdk/test/metrics/CMakeLists.txt b/sdk/test/metrics/CMakeLists.txt index 2acbc6bd18..585023ebbf 100644 --- a/sdk/test/metrics/CMakeLists.txt +++ b/sdk/test/metrics/CMakeLists.txt @@ -6,6 +6,7 @@ foreach( aggregation_test attributes_processor_test attributes_hashmap_test + histogram_test sync_metric_storage_counter_test sync_metric_storage_histogram_test sync_metric_storage_up_down_counter_test @@ -37,6 +38,13 @@ if(WITH_BENCHMARK) add_executable(attributes_hashmap_benchmark attributes_hashmap_benchmark.cc) target_link_libraries(attributes_hashmap_benchmark benchmark::benchmark ${CMAKE_THREAD_LIBS_INIT} opentelemetry_common) + + add_executable(histogram_aggregation_benchmark + histogram_aggregation_benchmark.cc) + target_link_libraries( + histogram_aggregation_benchmark benchmark::benchmark + ${CMAKE_THREAD_LIBS_INIT} opentelemetry_common opentelemetry_metrics + opentelemetry_resources) endif() add_subdirectory(exemplar) diff --git a/sdk/test/metrics/aggregation_test.cc b/sdk/test/metrics/aggregation_test.cc index fffe56b104..9f307cb015 100644 --- a/sdk/test/metrics/aggregation_test.cc +++ b/sdk/test/metrics/aggregation_test.cc @@ -76,17 +76,17 @@ TEST(Aggregation, LongHistogramAggregation) ASSERT_TRUE(nostd::holds_alternative(histogram_data.sum_)); EXPECT_EQ(nostd::get(histogram_data.sum_), 0); EXPECT_EQ(histogram_data.count_, 0); - aggr.Aggregate((int64_t)12, {}); // lies in fourth bucket - aggr.Aggregate((int64_t)100, {}); // lies in eight bucket + aggr.Aggregate((int64_t)12, {}); // lies in third bucket + aggr.Aggregate((int64_t)100, {}); // lies in sixth bucket histogram_data = nostd::get(aggr.ToPoint()); EXPECT_EQ(nostd::get(histogram_data.min_), 12); EXPECT_EQ(nostd::get(histogram_data.max_), 100); EXPECT_EQ(nostd::get(histogram_data.sum_), 112); EXPECT_EQ(histogram_data.count_, 2); EXPECT_EQ(histogram_data.counts_[3], 1); - EXPECT_EQ(histogram_data.counts_[7], 1); - aggr.Aggregate((int64_t)13, {}); // lies in fourth bucket - aggr.Aggregate((int64_t)252, {}); // lies in ninth bucket + EXPECT_EQ(histogram_data.counts_[6], 1); + aggr.Aggregate((int64_t)13, {}); // lies in third bucket + aggr.Aggregate((int64_t)252, {}); // lies in eight bucket histogram_data = nostd::get(aggr.ToPoint()); EXPECT_EQ(histogram_data.count_, 4); EXPECT_EQ(histogram_data.counts_[3], 2); @@ -132,9 +132,9 @@ TEST(Aggregation, LongHistogramAggregationBoundaries) { std::shared_ptr aggregation_config{ new opentelemetry::sdk::metrics::HistogramAggregationConfig}; - std::list user_boundaries = {0.0, 50.0, 100.0, 250.0, 500.0, - 750.0, 1000.0, 2500.0, 5000.0, 10000.0}; - aggregation_config->boundaries_ = user_boundaries; + std::vector user_boundaries = {0.0, 50.0, 100.0, 250.0, 500.0, + 750.0, 1000.0, 2500.0, 5000.0, 10000.0}; + aggregation_config->boundaries_ = user_boundaries; LongHistogramAggregation aggr{aggregation_config.get()}; auto data = aggr.ToPoint(); ASSERT_TRUE(nostd::holds_alternative(data)); @@ -146,9 +146,9 @@ TEST(Aggregation, DoubleHistogramAggregationBoundaries) { std::shared_ptr aggregation_config{ new opentelemetry::sdk::metrics::HistogramAggregationConfig}; - std::list user_boundaries = {0.0, 50.0, 100.0, 250.0, 500.0, - 750.0, 1000.0, 2500.0, 5000.0, 10000.0}; - aggregation_config->boundaries_ = user_boundaries; + std::vector user_boundaries = {0.0, 50.0, 100.0, 250.0, 500.0, + 750.0, 1000.0, 2500.0, 5000.0, 10000.0}; + aggregation_config->boundaries_ = user_boundaries; DoubleHistogramAggregation aggr{aggregation_config.get()}; auto data = aggr.ToPoint(); ASSERT_TRUE(nostd::holds_alternative(data)); @@ -165,17 +165,17 @@ TEST(Aggregation, DoubleHistogramAggregation) ASSERT_TRUE(nostd::holds_alternative(histogram_data.sum_)); EXPECT_EQ(nostd::get(histogram_data.sum_), 0); EXPECT_EQ(histogram_data.count_, 0); - aggr.Aggregate(12.0, {}); // lies in fourth bucket - aggr.Aggregate(100.0, {}); // lies in eight bucket + aggr.Aggregate(12.0, {}); // lies in third bucket + aggr.Aggregate(100.0, {}); // lies in sixth bucket histogram_data = nostd::get(aggr.ToPoint()); EXPECT_EQ(nostd::get(histogram_data.sum_), 112); EXPECT_EQ(histogram_data.count_, 2); EXPECT_EQ(histogram_data.counts_[3], 1); - EXPECT_EQ(histogram_data.counts_[7], 1); + EXPECT_EQ(histogram_data.counts_[6], 1); EXPECT_EQ(nostd::get(histogram_data.min_), 12); EXPECT_EQ(nostd::get(histogram_data.max_), 100); - aggr.Aggregate(13.0, {}); // lies in fourth bucket - aggr.Aggregate(252.0, {}); // lies in ninth bucket + aggr.Aggregate(13.0, {}); // lies in third bucket + aggr.Aggregate(252.0, {}); // lies in eight bucket histogram_data = nostd::get(aggr.ToPoint()); EXPECT_EQ(histogram_data.count_, 4); EXPECT_EQ(histogram_data.counts_[3], 2); diff --git a/sdk/test/metrics/histogram_aggregation_benchmark.cc b/sdk/test/metrics/histogram_aggregation_benchmark.cc new file mode 100644 index 0000000000..41ac2379ca --- /dev/null +++ b/sdk/test/metrics/histogram_aggregation_benchmark.cc @@ -0,0 +1,85 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include "opentelemetry/sdk/metrics/data/point_data.h" +#include "opentelemetry/sdk/metrics/meter.h" +#include "opentelemetry/sdk/metrics/meter_context.h" +#include "opentelemetry/sdk/metrics/meter_provider.h" +#include "opentelemetry/sdk/metrics/metric_reader.h" +#include "opentelemetry/sdk/metrics/push_metric_exporter.h" + +#include + +using namespace opentelemetry; +using namespace opentelemetry::sdk::instrumentationscope; +using namespace opentelemetry::sdk::metrics; + +class MockMetricExporter : public PushMetricExporter +{ +public: + MockMetricExporter() = default; + opentelemetry::sdk::common::ExportResult Export( + const ResourceMetrics & /* records */) noexcept override + { + return opentelemetry::sdk::common::ExportResult::kSuccess; + } + + AggregationTemporality GetAggregationTemporality( + InstrumentType /* instrument_type */) const noexcept override + { + return AggregationTemporality::kCumulative; + } + + bool ForceFlush(std::chrono::microseconds /* timeout */) noexcept override { return true; } + + bool Shutdown(std::chrono::microseconds /* timeout */) noexcept override { return true; } +}; + +class MockMetricReader : public MetricReader +{ +public: + MockMetricReader(std::unique_ptr exporter) : exporter_(std::move(exporter)) {} + AggregationTemporality GetAggregationTemporality( + InstrumentType instrument_type) const noexcept override + { + return exporter_->GetAggregationTemporality(instrument_type); + } + virtual bool OnForceFlush(std::chrono::microseconds /* timeout */) noexcept override + { + return true; + } + virtual bool OnShutDown(std::chrono::microseconds /* timeout */) noexcept override + { + return true; + } + virtual void OnInitialized() noexcept override {} + +private: + std::unique_ptr exporter_; +}; + +namespace +{ +void BM_HistogramAggregation(benchmark::State &state) +{ + MeterProvider mp; + auto m = mp.GetMeter("meter1", "version1", "schema1"); + + std::unique_ptr exporter(new MockMetricExporter()); + std::shared_ptr reader{new MockMetricReader(std::move(exporter))}; + mp.AddMetricReader(reader); + auto h = m->CreateDoubleHistogram("histogram1", "histogram1_description", "histogram1_unit"); + std::default_random_engine generator; + std::uniform_int_distribution distribution(0, 1000000); + while (state.KeepRunning()) + { + auto value = (double)distribution(generator); + h->Record(value, {}); + } +} +BENCHMARK(BM_HistogramAggregation); + +} // namespace +BENCHMARK_MAIN(); diff --git a/sdk/test/metrics/histogram_test.cc b/sdk/test/metrics/histogram_test.cc new file mode 100644 index 0000000000..ec77d99733 --- /dev/null +++ b/sdk/test/metrics/histogram_test.cc @@ -0,0 +1,284 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/common/macros.h" +#include "opentelemetry/sdk/metrics/data/point_data.h" +#include "opentelemetry/sdk/metrics/meter.h" +#include "opentelemetry/sdk/metrics/meter_context.h" +#include "opentelemetry/sdk/metrics/meter_provider.h" +#include "opentelemetry/sdk/metrics/metric_reader.h" +#include "opentelemetry/sdk/metrics/push_metric_exporter.h" + +#include + +using namespace opentelemetry; +using namespace opentelemetry::sdk::instrumentationscope; +using namespace opentelemetry::sdk::metrics; + +class MockMetricExporter : public PushMetricExporter +{ +public: + MockMetricExporter() = default; + opentelemetry::sdk::common::ExportResult Export( + const ResourceMetrics & /* records */) noexcept override + { + return opentelemetry::sdk::common::ExportResult::kSuccess; + } + + AggregationTemporality GetAggregationTemporality( + InstrumentType /* instrument_type */) const noexcept override + { + return AggregationTemporality::kCumulative; + } + + bool ForceFlush(std::chrono::microseconds /* timeout */) noexcept override { return true; } + + bool Shutdown(std::chrono::microseconds /* timeout */) noexcept override { return true; } +}; + +class MockMetricReader : public MetricReader +{ +public: + MockMetricReader(std::unique_ptr exporter) : exporter_(std::move(exporter)) {} + AggregationTemporality GetAggregationTemporality( + InstrumentType instrument_type) const noexcept override + { + return exporter_->GetAggregationTemporality(instrument_type); + } + virtual bool OnForceFlush(std::chrono::microseconds /* timeout */) noexcept override + { + return true; + } + virtual bool OnShutDown(std::chrono::microseconds /* timeout */) noexcept override + { + return true; + } + virtual void OnInitialized() noexcept override {} + +private: + std::unique_ptr exporter_; +}; + +TEST(Histogram, Double) +{ + MeterProvider mp; + auto m = mp.GetMeter("meter1", "version1", "schema1"); + + std::unique_ptr exporter(new MockMetricExporter()); + std::shared_ptr reader{new MockMetricReader(std::move(exporter))}; + mp.AddMetricReader(reader); + + auto h = m->CreateDoubleHistogram("histogram1", "histogram1_description", "histogram1_unit"); + + h->Record(5, {}); + h->Record(10, {}); + h->Record(15, {}); + h->Record(20, {}); + h->Record(25, {}); + h->Record(30, {}); + h->Record(35, {}); + h->Record(40, {}); + h->Record(45, {}); + h->Record(50, {}); + h->Record(1e6, {}); + + std::vector actuals; + reader->Collect([&](ResourceMetrics &rm) { + for (const ScopeMetrics &smd : rm.scope_metric_data_) + { + for (const MetricData &md : smd.metric_data_) + { + for (const PointDataAttributes &dp : md.point_data_attr_) + { + actuals.push_back(opentelemetry::nostd::get(dp.point_data)); + } + } + } + return true; + }); + + ASSERT_EQ(1, actuals.size()); + + const auto &actual = actuals.at(0); + ASSERT_EQ(1000275.0, opentelemetry::nostd::get(actual.sum_)); + ASSERT_EQ(11, actual.count_); + ASSERT_EQ(5.0, opentelemetry::nostd::get(actual.min_)); + ASSERT_EQ(1e6, opentelemetry::nostd::get(actual.max_)); + ASSERT_EQ(std::vector( + {0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}), + actual.boundaries_); + ASSERT_EQ(std::vector({0, 1, 1, 3, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}), + actual.counts_); +} + +#if (HAVE_WORKING_REGEX) +// FIXME - View Preficate search is only supported through regex +TEST(Histogram, DoubleCustomBuckets) +{ + MeterProvider mp; + auto m = mp.GetMeter("meter1", "version1", "schema1"); + + std::unique_ptr exporter(new MockMetricExporter()); + std::shared_ptr reader{new MockMetricReader(std::move(exporter))}; + mp.AddMetricReader(reader); + + std::shared_ptr config(new HistogramAggregationConfig()); + config->boundaries_ = {10, 20, 30, 40}; + std::unique_ptr view{ + new View("view1", "view1_description", AggregationType::kHistogram, config)}; + std::unique_ptr instrument_selector{ + new InstrumentSelector(InstrumentType::kHistogram, "histogram1")}; + std::unique_ptr meter_selector{new MeterSelector("meter1", "version1", "schema1")}; + mp.AddView(std::move(instrument_selector), std::move(meter_selector), std::move(view)); + + auto h = m->CreateDoubleHistogram("histogram1", "histogram1_description", "histogram1_unit"); + + h->Record(5, {}); + h->Record(10, {}); + h->Record(15, {}); + h->Record(20, {}); + h->Record(25, {}); + h->Record(30, {}); + h->Record(35, {}); + h->Record(40, {}); + h->Record(45, {}); + h->Record(50, {}); + + std::vector actuals; + reader->Collect([&](ResourceMetrics &rm) { + for (const ScopeMetrics &smd : rm.scope_metric_data_) + { + for (const MetricData &md : smd.metric_data_) + { + for (const PointDataAttributes &dp : md.point_data_attr_) + { + actuals.push_back(opentelemetry::nostd::get(dp.point_data)); + } + } + } + return true; + }); + + ASSERT_EQ(1, actuals.size()); + + const auto &actual = actuals.at(0); + ASSERT_EQ(275.0, opentelemetry::nostd::get(actual.sum_)); + ASSERT_EQ(10, actual.count_); + ASSERT_EQ(5.0, opentelemetry::nostd::get(actual.min_)); + ASSERT_EQ(50.0, opentelemetry::nostd::get(actual.max_)); + ASSERT_EQ(std::vector({10, 20, 30, 40}), actual.boundaries_); + ASSERT_EQ(std::vector({2, 2, 2, 2, 2}), actual.counts_); +} +#endif + +TEST(Histogram, UInt64) +{ + MeterProvider mp; + auto m = mp.GetMeter("meter1", "version1", "schema1"); + + std::unique_ptr exporter(new MockMetricExporter()); + std::shared_ptr reader{new MockMetricReader(std::move(exporter))}; + mp.AddMetricReader(reader); + + auto h = m->CreateUInt64Histogram("histogram1", "histogram1_description", "histogram1_unit"); + + h->Record(5, {}); + h->Record(10, {}); + h->Record(15, {}); + h->Record(20, {}); + h->Record(25, {}); + h->Record(30, {}); + h->Record(35, {}); + h->Record(40, {}); + h->Record(45, {}); + h->Record(50, {}); + h->Record(1000000, {}); + + std::vector actuals; + reader->Collect([&](ResourceMetrics &rm) { + for (const ScopeMetrics &smd : rm.scope_metric_data_) + { + for (const MetricData &md : smd.metric_data_) + { + for (const PointDataAttributes &dp : md.point_data_attr_) + { + actuals.push_back(opentelemetry::nostd::get(dp.point_data)); + } + } + } + return true; + }); + + ASSERT_EQ(1, actuals.size()); + + const auto &actual = actuals.at(0); + ASSERT_EQ(1000275, opentelemetry::nostd::get(actual.sum_)); + ASSERT_EQ(11, actual.count_); + ASSERT_EQ(5, opentelemetry::nostd::get(actual.min_)); + ASSERT_EQ(1000000, opentelemetry::nostd::get(actual.max_)); + ASSERT_EQ(std::vector( + {0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}), + actual.boundaries_); + ASSERT_EQ(std::vector({0, 1, 1, 3, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}), + actual.counts_); +} + +#if (HAVE_WORKING_REGEX) +// FIXME - View Preficate search is only supported through regex +TEST(Histogram, UInt64CustomBuckets) +{ + MeterProvider mp; + auto m = mp.GetMeter("meter1", "version1", "schema1"); + + std::unique_ptr exporter(new MockMetricExporter()); + std::shared_ptr reader{new MockMetricReader(std::move(exporter))}; + mp.AddMetricReader(reader); + + std::shared_ptr config(new HistogramAggregationConfig()); + config->boundaries_ = {10, 20, 30, 40}; + std::unique_ptr view{ + new View("view1", "view1_description", AggregationType::kHistogram, config)}; + std::unique_ptr instrument_selector{ + new InstrumentSelector(InstrumentType::kHistogram, "histogram1")}; + std::unique_ptr meter_selector{new MeterSelector("meter1", "version1", "schema1")}; + mp.AddView(std::move(instrument_selector), std::move(meter_selector), std::move(view)); + + auto h = m->CreateUInt64Histogram("histogram1", "histogram1_description", "histogram1_unit"); + + h->Record(5, {}); + h->Record(10, {}); + h->Record(15, {}); + h->Record(20, {}); + h->Record(25, {}); + h->Record(30, {}); + h->Record(35, {}); + h->Record(40, {}); + h->Record(45, {}); + h->Record(50, {}); + + std::vector actuals; + reader->Collect([&](ResourceMetrics &rm) { + for (const ScopeMetrics &smd : rm.scope_metric_data_) + { + for (const MetricData &md : smd.metric_data_) + { + for (const PointDataAttributes &dp : md.point_data_attr_) + { + actuals.push_back(opentelemetry::nostd::get(dp.point_data)); + } + } + } + return true; + }); + + ASSERT_EQ(1, actuals.size()); + + const auto &actual = actuals.at(0); + ASSERT_EQ(275, opentelemetry::nostd::get(actual.sum_)); + ASSERT_EQ(10, actual.count_); + ASSERT_EQ(5, opentelemetry::nostd::get(actual.min_)); + ASSERT_EQ(50, opentelemetry::nostd::get(actual.max_)); + ASSERT_EQ(std::vector({10, 20, 30, 40}), actual.boundaries_); + ASSERT_EQ(std::vector({2, 2, 2, 2, 2}), actual.counts_); +} +#endif