From a8e461754437744948ca945d5aef30abb0c4f6c5 Mon Sep 17 00:00:00 2001 From: Ehsan Saei <71217171+esigo@users.noreply.github.com> Date: Thu, 28 Jul 2022 16:34:02 +0200 Subject: [PATCH] Prometheus unit test (#1461) --- exporters/prometheus/BUILD | 61 +++ exporters/prometheus/test/CMakeLists.txt | 11 + exporters/prometheus/test/collector_test.cc | 359 ++++++++++++++++++ exporters/prometheus/test/exporter_test.cc | 165 ++++++++ .../prometheus/test/exporter_utils_test.cc | 130 +++++++ .../prometheus/test/prometheus_test_helper.h | 244 ++++++++++++ 6 files changed, 970 insertions(+) create mode 100644 exporters/prometheus/test/collector_test.cc create mode 100644 exporters/prometheus/test/exporter_test.cc create mode 100644 exporters/prometheus/test/exporter_utils_test.cc create mode 100644 exporters/prometheus/test/prometheus_test_helper.h diff --git a/exporters/prometheus/BUILD b/exporters/prometheus/BUILD index 47c9bbab16..16d524f8a4 100644 --- a/exporters/prometheus/BUILD +++ b/exporters/prometheus/BUILD @@ -142,3 +142,64 @@ cc_library( "@com_github_jupp0r_prometheus_cpp//pull", ], ) + +cc_library( + name = "prometheus_test_helper", + hdrs = [ + "test/prometheus_test_helper.h", + ], + tags = ["prometheus"], + deps = [ + "//api", + "//sdk:headers", + "//sdk/src/trace", + ], +) + +cc_test( + name = "prometheus_exporter_test", + srcs = [ + "test/exporter_test.cc", + ], + tags = [ + "prometheus", + "test", + ], + deps = [ + ":prometheus_exporter", + ":prometheus_test_helper", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "prometheus_collector_test", + srcs = [ + "test/collector_test.cc", + ], + tags = [ + "prometheus", + "test", + ], + deps = [ + ":prometheus_collector", + ":prometheus_test_helper", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "prometheus_exporter_utils_test", + srcs = [ + "test/exporter_utils_test.cc", + ], + tags = [ + "prometheus", + "test", + ], + deps = [ + ":prometheus_exporter_utils", + ":prometheus_test_helper", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/exporters/prometheus/test/CMakeLists.txt b/exporters/prometheus/test/CMakeLists.txt index 1a22469792..0a370a7e1c 100644 --- a/exporters/prometheus/test/CMakeLists.txt +++ b/exporters/prometheus/test/CMakeLists.txt @@ -10,4 +10,15 @@ if(WITH_METRICS_PREVIEW) TEST_PREFIX exporter. TEST_LIST ${testname}) endforeach() +else() + foreach(testname exporter_test collector_test exporter_utils_test) + add_executable(${testname} "${testname}.cc") + target_link_libraries( + ${testname} ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} + prometheus_exporter prometheus-cpp::pull) + gtest_add_tests( + TARGET ${testname} + TEST_PREFIX exporter. + TEST_LIST ${testname}) + endforeach() endif() diff --git a/exporters/prometheus/test/collector_test.cc b/exporters/prometheus/test/collector_test.cc new file mode 100644 index 0000000000..2501310d46 --- /dev/null +++ b/exporters/prometheus/test/collector_test.cc @@ -0,0 +1,359 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifndef ENABLE_METRICS_PREVIEW +# include +# include +# include +# include + +# include "opentelemetry/_metrics/instrument.h" +# include "opentelemetry/exporters/prometheus/collector.h" +# include "opentelemetry/version.h" +# include "prometheus_test_helper.h" + +using opentelemetry::exporter::metrics::PrometheusCollector; +namespace metric_api = opentelemetry::metrics; +namespace metric_sdk = opentelemetry::sdk::metrics; + +OPENTELEMETRY_BEGIN_NAMESPACE + +// ==================== Test for addMetricsData() function ====================== + +/** + * AddMetricData() should be able to successfully add a collection + * of SumPointData. It checks that the cumulative + * sum of updates to the aggregator of a record before and after AddMetricData() + * is called are equal. + */ +TEST(PrometheusCollector, AddMetricDataWithCounterRecordsSuccessfully) +{ + PrometheusCollector collector; + + // construct a collection of records with CounterAggregators and double + auto data = CreateSumPointData(); + + // add records to collection + collector.AddMetricData(data); + + // Collection size should be the same as the size + // of the records collection passed to addMetricData() + ASSERT_EQ(collector.GetCollection().size(), 1); + + // check values of records created vs records from metricsToCollect, + // accessed by getCollection() + + auto &&collector_data = collector.GetCollection(); + ASSERT_EQ(collector_data.at(0)->resource_, data.resource_); + for (auto &collector_d : collector_data) + { + for (uint32_t instrument_idx = 0; + instrument_idx < collector_d->instrumentation_info_metric_data_.size(); ++instrument_idx) + { + ASSERT_EQ(collector_d->instrumentation_info_metric_data_.at(instrument_idx) + .instrumentation_library_, + data.instrumentation_info_metric_data_.at(instrument_idx).instrumentation_library_); + ASSERT_EQ(collector_d->instrumentation_info_metric_data_.at(instrument_idx).metric_data_, + data.instrumentation_info_metric_data_.at(instrument_idx).metric_data_); + } + } +} + +/** + * AddMetricData() should be able to successfully add a collection + * of HistogramPointData. It checks that the cumulative + * sum of updates to the aggregator of a record before and after AddMetricData() + * is called are equal. + */ +TEST(PrometheusCollector, AddMetricDataWithHistogramSuccessfully) +{ + PrometheusCollector collector; + + // construct a collection of records with CounterAggregators and double + auto data = CreateHistogramPointData(); + + // add records to collection + collector.AddMetricData(data); + + // Collection size should be the same as the size + // of the records collection passed to addMetricData() + ASSERT_EQ(collector.GetCollection().size(), 1); + + // check values of records created vs records from metricsToCollect, + // accessed by getCollection() + + auto &&collector_data = collector.GetCollection(); + ASSERT_EQ(collector_data.at(0)->resource_, data.resource_); + for (auto &collector_d : collector_data) + { + for (uint32_t instrument_idx = 0; + instrument_idx < collector_d->instrumentation_info_metric_data_.size(); ++instrument_idx) + { + ASSERT_EQ(collector_d->instrumentation_info_metric_data_.at(instrument_idx) + .instrumentation_library_, + data.instrumentation_info_metric_data_.at(instrument_idx).instrumentation_library_); + ASSERT_EQ(collector_d->instrumentation_info_metric_data_.at(instrument_idx).metric_data_, + data.instrumentation_info_metric_data_.at(instrument_idx).metric_data_); + } + } +} + +/** + * AddMetricData() should be able to successfully add a collection + * of LastValuePointData. It checks that the cumulative + * sum of updates to the aggregator of a record before and after AddMetricData() + * is called are equal. + */ +TEST(PrometheusCollector, AddMetricDataWithLastValueSuccessfully) +{ + PrometheusCollector collector; + + // construct a collection of records with CounterAggregators and double + auto data = CreateLastValuePointData(); + + // add records to collection + collector.AddMetricData(data); + + // Collection size should be the same as the size + // of the records collection passed to addMetricData() + ASSERT_EQ(collector.GetCollection().size(), 1); + + // check values of records created vs records from metricsToCollect, + // accessed by getCollection() + + auto &&collector_data = collector.GetCollection(); + ASSERT_EQ(collector_data.at(0)->resource_, data.resource_); + for (auto &collector_d : collector_data) + { + for (uint32_t instrument_idx = 0; + instrument_idx < collector_d->instrumentation_info_metric_data_.size(); ++instrument_idx) + { + ASSERT_EQ(collector_d->instrumentation_info_metric_data_.at(instrument_idx) + .instrumentation_library_, + data.instrumentation_info_metric_data_.at(instrument_idx).instrumentation_library_); + ASSERT_EQ(collector_d->instrumentation_info_metric_data_.at(instrument_idx).metric_data_, + data.instrumentation_info_metric_data_.at(instrument_idx).metric_data_); + } + } +} + +/** + * AddMetricData() should be able to successfully add a collection + * of DropPointData. It checks that the cumulative + * sum of updates to the aggregator of a record before and after AddMetricData() + * is called are equal. + */ +TEST(PrometheusCollector, AddMetricDataWithDropSuccessfully) +{ + PrometheusCollector collector; + + // construct a collection of records with CounterAggregators and double + auto data = CreateDropPointData(); + + // add records to collection + collector.AddMetricData(data); + + // Collection size should be the same as the size + // of the records collection passed to addMetricData() + ASSERT_EQ(collector.GetCollection().size(), 1); + + // check values of records created vs records from metricsToCollect, + // accessed by getCollection() + + auto &&collector_data = collector.GetCollection(); + ASSERT_EQ(collector_data.at(0)->resource_, data.resource_); + for (auto &collector_d : collector_data) + { + for (uint32_t instrument_idx = 0; + instrument_idx < collector_d->instrumentation_info_metric_data_.size(); ++instrument_idx) + { + ASSERT_EQ(collector_d->instrumentation_info_metric_data_.at(instrument_idx) + .instrumentation_library_, + data.instrumentation_info_metric_data_.at(instrument_idx).instrumentation_library_); + ASSERT_EQ(collector_d->instrumentation_info_metric_data_.at(instrument_idx).metric_data_, + data.instrumentation_info_metric_data_.at(instrument_idx).metric_data_); + } + } +} + +TEST(PrometheusCollector, AddMetricDataDoesNotAddWithInsufficentSpace) +{ + PrometheusCollector collector; + + int max_collection_size = collector.GetMaxCollectionSize(); + + for (int count = 1; count <= max_collection_size; ++count) + { + collector.AddMetricData(CreateSumPointData()); + ASSERT_EQ(count, collector.GetCollection().size()); + } + + // Try adding one more collection of records again to + // metricsToCollect. + collector.AddMetricData(CreateSumPointData()); + + // Check that the number of records in metricsToCollect + // has not changed. + ASSERT_EQ(collector.GetCollection().size(), max_collection_size); +} + +// ==================== Test for Constructor ====================== +TEST(PrometheusCollector, ConstructorInitializesCollector) +{ + PrometheusCollector collector; + + // current size should be 0, capacity should be set to default + ASSERT_EQ(collector.GetCollection().size(), 0); +} + +// ==================== Tests for collect() function ====================== + +/** + * When collector is initialized, the collection inside is should also be initialized + */ +TEST(PrometheusCollector, CollectInitializesMetricFamilyCollection) +{ + PrometheusCollector collector; + auto c1 = collector.Collect(); + ASSERT_EQ(c1.size(), 0); +} + +/** + * Collect function should collect all data and clear the intermediate collection + */ +TEST(PrometheusCollector, CollectClearsTheCollection) +{ + PrometheusCollector collector; + + // construct a collection to add metric records + collector.AddMetricData(CreateSumPointData()); + collector.AddMetricData(CreateSumPointData()); + + // the collection should not be empty now + ASSERT_EQ(collector.GetCollection().size(), 2); + + // don't care the collected result in this test + collector.Collect(); + + // after the collect() call, the collection should be empty + ASSERT_EQ(collector.GetCollection().size(), 0); +} + +/** + * Collected data should be already be parsed to Prometheus Metric format + */ +TEST(PrometheusCollector, CollectParsesDataToMetricFamily) +{ + PrometheusCollector collector; + + collector.AddMetricData(CreateSumPointData()); + + // the collection should not be empty now + ASSERT_EQ(collector.GetCollection().size(), 1); + auto collected = collector.Collect(); + + ASSERT_EQ(collected.size(), 1); + + auto metric_family = collected[0]; + + // Collect function really collects a vector of MetricFamily + ASSERT_EQ(metric_family.name, "library_name"); + ASSERT_EQ(metric_family.help, "description"); + ASSERT_EQ(metric_family.type, prometheus_client::MetricType::Counter); + ASSERT_EQ(metric_family.metric.size(), 2); + ASSERT_DOUBLE_EQ(metric_family.metric[0].counter.value, 10); +} + +/** + * Concurrency Test 1: After adding data concurrently, the intermediate collection should + * contain all data from all threads. + */ +TEST(PrometheusCollector, ConcurrencyAddingRecords) +{ + PrometheusCollector collector; + + std::thread first(&PrometheusCollector::AddMetricData, std::ref(collector), CreateSumPointData()); + std::thread second(&PrometheusCollector::AddMetricData, std::ref(collector), + CreateSumPointData()); + + first.join(); + second.join(); + + ASSERT_EQ(collector.GetCollection().size(), 2); +} + +/** + * Concurrency Test 2: After adding data concurrently and collecting, the intermediate collection + * should be empty, and all data are collected in the result vector. + */ +TEST(PrometheusCollector, ConcurrentlyAddingAndThenCollecting) +{ + PrometheusCollector collector; + + std::thread first(&PrometheusCollector::AddMetricData, std::ref(collector), CreateSumPointData()); + std::thread second(&PrometheusCollector::AddMetricData, std::ref(collector), + CreateSumPointData()); + first.join(); + second.join(); + + auto collect_future = std::async(&PrometheusCollector::Collect, std::ref(collector)); + auto res = collect_future.get(); + + ASSERT_EQ(collector.GetCollection().size(), 0); + ASSERT_EQ(res.size(), 2); +} + +/** + * Concurrency Test 3: Concurrently adding and collecting. We don't know when the collect function + * is called, but all data entries are either collected or left in the collection. + */ +TEST(PrometheusCollector, ConcurrentlyAddingAndCollecting) +{ + PrometheusCollector collector; + + // construct a collection to add metric records + + std::thread first(&PrometheusCollector::AddMetricData, std::ref(collector), CreateSumPointData()); + std::thread second(&PrometheusCollector::AddMetricData, std::ref(collector), + CreateSumPointData()); + auto collect_future = std::async(&PrometheusCollector::Collect, std::ref(collector)); + + first.join(); + second.join(); + + auto res = collect_future.get(); + + // the size of collection can be 0, 2, 4, because we don't know when the collect() + // is really called. However, we claim that if the data in the collection is collected, + // they must be in the res. So res.size() + collection.size() must be the total number + // of data records we generated. + ASSERT_EQ(res.size() + collector.GetCollection().size(), 2); +} + +/** + * Concurrency Test 4: Concurrently adding then concurrently collecting. We don't know which + * collecting thread fetches all data, but either one should succeed. + */ +TEST(PrometheusCollector, ConcurrentlyAddingAndConcurrentlyCollecting) +{ + PrometheusCollector collector; + + // concurrently adding + std::thread first(&PrometheusCollector::AddMetricData, std::ref(collector), CreateSumPointData()); + std::thread second(&PrometheusCollector::AddMetricData, std::ref(collector), + CreateSumPointData()); + first.join(); + second.join(); + + // after adding, then concurrently consuming + auto collect_future1 = std::async(&PrometheusCollector::Collect, std::ref(collector)); + auto collect_future2 = std::async(&PrometheusCollector::Collect, std::ref(collector)); + auto res1 = collect_future1.get(); + auto res2 = collect_future2.get(); + + // all added data must be collected in either res1 or res2 + ASSERT_EQ(res1.size() + res2.size(), 2); +} + +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/exporters/prometheus/test/exporter_test.cc b/exporters/prometheus/test/exporter_test.cc new file mode 100644 index 0000000000..f9ee6b937b --- /dev/null +++ b/exporters/prometheus/test/exporter_test.cc @@ -0,0 +1,165 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifndef ENABLE_METRICS_PREVIEW + +# include + +# include "opentelemetry/exporters/prometheus/collector.h" +# include "opentelemetry/exporters/prometheus/exporter.h" +# include "opentelemetry/sdk/_metrics/aggregator/counter_aggregator.h" +# include "opentelemetry/version.h" +# include "prometheus_test_helper.h" + +/** + * PrometheusExporterTest is a friend class of PrometheusExporter. + * It has access to a private constructor that does not take in + * an exposer as an argument, and instead takes no arguments; this + * private constructor is only to be used here for testing + */ +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace metrics +{ +class PrometheusExporterTest // : public ::testing::Test +{ +public: + PrometheusExporter GetExporter() { return PrometheusExporter(); } +}; +} // namespace metrics +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE + +using opentelemetry::exporter::metrics::PrometheusCollector; +using opentelemetry::exporter::metrics::PrometheusExporter; +using opentelemetry::exporter::metrics::PrometheusExporterTest; +using opentelemetry::sdk::common::ExportResult; + +/** + * When a PrometheusExporter is initialized, + * isShutdown should be false. + */ +TEST(PrometheusExporter, InitializeConstructorIsNotShutdown) +{ + PrometheusExporterTest p; + PrometheusExporter exporter = p.GetExporter(); + + // // Asserts that the exporter is not shutdown. + ASSERT_TRUE(!exporter.IsShutdown()); +} + +/** + * The shutdown() function should set the isShutdown field to true. + */ +TEST(PrometheusExporter, ShutdownSetsIsShutdownToTrue) +{ + PrometheusExporterTest p; + PrometheusExporter exporter = p.GetExporter(); + + // exporter shuold not be shutdown by default + ASSERT_TRUE(!exporter.IsShutdown()); + + exporter.Shutdown(); + + // the exporter shuold be shutdown + ASSERT_TRUE(exporter.IsShutdown()); + + // shutdown function should be idempotent + exporter.Shutdown(); + ASSERT_TRUE(exporter.IsShutdown()); +} + +/** + * The Export() function should return kSuccess = 0 + * when data is exported successfully. + */ +TEST(PrometheusExporter, ExportSuccessfully) +{ + PrometheusExporterTest p; + PrometheusExporter exporter = p.GetExporter(); + + auto res = exporter.Export(CreateSumPointData()); + + // result should be kSuccess = 0 + ExportResult code = ExportResult::kSuccess; + ASSERT_EQ(res, code); +} + +/** + * If the exporter is shutdown, it cannot process + * any more export requests and returns kFailure = 1. + */ +TEST(PrometheusExporter, ExporterIsShutdown) +{ + PrometheusExporterTest p; + PrometheusExporter exporter = p.GetExporter(); + + exporter.Shutdown(); + + // send export request after shutdown + auto res = exporter.Export(CreateSumPointData()); + + // result code should be kFailure = 1 + ExportResult code = ExportResult::kFailure; + ASSERT_EQ(res, code); +} + +/** + * The Export() function should return + * kFailureFull = 2 when the collection is full, + * or when the collection is not full but does not have enough + * space to hold the batch data. + */ +TEST(PrometheusExporter, CollectionNotEnoughSpace) +{ + PrometheusExporterTest p; + PrometheusExporter exporter = p.GetExporter(); + + // prepare two collections of records to export, + // one close to max size and another one that, when added + // to the first, will exceed the size of the collection + + int max_collection_size = exporter.GetCollector()->GetMaxCollectionSize(); + + // send export request to fill the + // collection in the collector + ExportResult code = ExportResult::kSuccess; + for (int count = 1; count <= max_collection_size; ++count) + { + auto res = exporter.Export(CreateSumPointData()); + ASSERT_EQ(res, code); + } + + // send export request that does not complete + // due to not enough space in the collection + auto res = exporter.Export(CreateSumPointData()); + + // the result code should be kFailureFull = 2 + code = ExportResult::kFailureFull; + ASSERT_EQ(res, code); +} + +/** + * The Export() function should return + * kFailureInvalidArgument = 3 when an empty collection + * of records is passed to the Export() function. + */ +TEST(PrometheusExporter, InvalidArgumentWhenPassedEmptyRecordCollection) +{ + PrometheusExporterTest p; + PrometheusExporter exporter = p.GetExporter(); + + // Initializes an empty colelction of records + metric_sdk::ResourceMetrics data; + + // send export request to fill the + // collection in the collector + auto res = exporter.Export(data); + + // the result code should be kFailureInvalidArgument = 3 + ExportResult code = ExportResult::kFailureInvalidArgument; + ASSERT_EQ(res, code); +} + +#endif // ENABLE_METRICS_PREVIEW diff --git a/exporters/prometheus/test/exporter_utils_test.cc b/exporters/prometheus/test/exporter_utils_test.cc new file mode 100644 index 0000000000..cb09b27964 --- /dev/null +++ b/exporters/prometheus/test/exporter_utils_test.cc @@ -0,0 +1,130 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifndef ENABLE_METRICS_PREVIEW +# include +# include "prometheus/metric_family.h" +# include "prometheus/metric_type.h" + +# include "opentelemetry/exporters/prometheus/exporter_utils.h" +# include "prometheus_test_helper.h" + +using opentelemetry::exporter::metrics::PrometheusExporterUtils; +namespace metric_sdk = opentelemetry::sdk::metrics; +namespace metric_api = opentelemetry::metrics; +namespace prometheus_client = ::prometheus; + +OPENTELEMETRY_BEGIN_NAMESPACE +template +void assert_basic(prometheus_client::MetricFamily &metric, + const std::string &sanitized_name, + const std::string &description, + prometheus_client::MetricType type, + int label_num, + std::vector vals) +{ + ASSERT_EQ(metric.name, sanitized_name); // name sanitized + ASSERT_EQ(metric.help, description); // description not changed + ASSERT_EQ(metric.type, type); // type translated + + auto metric_data = metric.metric[0]; + ASSERT_EQ(metric_data.label.size(), label_num); + + switch (type) + { + case prometheus_client::MetricType::Counter: { + ASSERT_DOUBLE_EQ(metric_data.counter.value, vals[0]); + break; + } + case prometheus_client::MetricType::Histogram: { + ASSERT_DOUBLE_EQ(metric_data.histogram.sample_count, vals[0]); + ASSERT_DOUBLE_EQ(metric_data.histogram.sample_sum, vals[1]); + auto buckets = metric_data.histogram.bucket; + ASSERT_EQ(buckets.size(), vals[2]); + break; + } + case prometheus_client::MetricType::Gauge: + // Gauge type not supported + ASSERT_TRUE(false); + break; + case prometheus_client::MetricType::Summary: + // Summary type not supported + ASSERT_TRUE(false); + break; + case prometheus::MetricType::Untyped: + break; + } +} + +void assert_histogram(prometheus_client::MetricFamily &metric, + std::list boundaries, + std::vector correct) +{ + int cumulative_count = 0; + auto buckets = metric.metric[0].histogram.bucket; + auto boundary_it = boundaries.cbegin(); + for (size_t i = 0; i < buckets.size(); i++, ++boundary_it) + { + auto bucket = buckets[i]; + if (i != buckets.size() - 1) + { + ASSERT_DOUBLE_EQ(*boundary_it, bucket.upper_bound); + } + else + { + ASSERT_DOUBLE_EQ(std::numeric_limits::infinity(), bucket.upper_bound); + } + cumulative_count += correct[i]; + ASSERT_EQ(cumulative_count, bucket.cumulative_count); + } +} + +TEST(PrometheusExporterUtils, TranslateToPrometheusEmptyInputReturnsEmptyCollection) +{ + auto translated = PrometheusExporterUtils::TranslateToPrometheus( + std::vector>{}); + ASSERT_EQ(translated.size(), 0); +} + +TEST(PrometheusExporterUtils, TranslateToPrometheusIntegerCounter) +{ + std::vector> collection; + + collection.emplace_back(new metric_sdk::ResourceMetrics{CreateSumPointData()}); + + auto translated = PrometheusExporterUtils::TranslateToPrometheus(collection); + ASSERT_EQ(translated.size(), collection.size()); + + auto metric1 = translated[0]; + std::vector vals = {10}; + assert_basic(metric1, "library_name", "description", prometheus_client::MetricType::Counter, 1, + vals); + + collection.emplace_back(new metric_sdk::ResourceMetrics{CreateSumPointData()}); + + translated = PrometheusExporterUtils::TranslateToPrometheus(collection); + ASSERT_EQ(translated.size(), collection.size()); + + auto metric2 = translated[1]; + assert_basic(metric2, "library_name", "description", prometheus_client::MetricType::Counter, 1, + vals); +} + +TEST(PrometheusExporterUtils, TranslateToPrometheusHistogramNormal) +{ + std::vector> collection; + + collection.emplace_back(new metric_sdk::ResourceMetrics{CreateHistogramPointData()}); + + auto translated = PrometheusExporterUtils::TranslateToPrometheus(collection); + ASSERT_EQ(translated.size(), collection.size()); + + auto metric = translated[0]; + std::vector vals = {3, 900.5, 4}; + assert_basic(metric, "library_name", "description", prometheus_client::MetricType::Histogram, 1, + vals); + assert_histogram(metric, std::list{10.1, 20.2, 30.2}, {200, 300, 400, 500}); +} + +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/exporters/prometheus/test/prometheus_test_helper.h b/exporters/prometheus/test/prometheus_test_helper.h new file mode 100644 index 0000000000..1c7b12cd23 --- /dev/null +++ b/exporters/prometheus/test/prometheus_test_helper.h @@ -0,0 +1,244 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#ifndef ENABLE_METRICS_PREVIEW + +# include "opentelemetry/version.h" + +namespace metric_sdk = opentelemetry::sdk::metrics; +namespace nostd = opentelemetry::nostd; +namespace exportermetrics = opentelemetry::exporter::metrics; + +namespace +{ +/** + * Helper function to create ResourceMetrics + */ +inline metric_sdk::ResourceMetrics CreateSumPointData() +{ + metric_sdk::SumPointData sum_point_data{}; + sum_point_data.value_ = 10.0; + metric_sdk::SumPointData sum_point_data2{}; + sum_point_data2.value_ = 20.0; + metric_sdk::ResourceMetrics data; + auto resource = opentelemetry::sdk::resource::Resource::Create( + opentelemetry::sdk::resource::ResourceAttributes{}); + data.resource_ = &resource; + auto instrumentation_library = + opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary::Create("library_name", + "1.2.0"); + metric_sdk::MetricData metric_data{ + metric_sdk::InstrumentDescriptor{"library_name", "description", "unit", + metric_sdk::InstrumentType::kCounter, + metric_sdk::InstrumentValueType::kDouble}, + metric_sdk::AggregationTemporality::kDelta, opentelemetry::common::SystemTimestamp{}, + opentelemetry::common::SystemTimestamp{}, + std::vector{ + {metric_sdk::PointAttributes{{"a1", "b1"}}, sum_point_data}, + {metric_sdk::PointAttributes{{"a2", "b2"}}, sum_point_data2}}}; + data.instrumentation_info_metric_data_ = std::vector{ + {instrumentation_library.get(), std::vector{metric_data}}}; + return data; +} + +inline metric_sdk::ResourceMetrics CreateHistogramPointData() +{ + metric_sdk::HistogramPointData histogram_point_data{}; + histogram_point_data.boundaries_ = std::list{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; + metric_sdk::HistogramPointData histogram_point_data2{}; + histogram_point_data2.boundaries_ = std::list{10, 20, 30}; + histogram_point_data2.count_ = 3; + histogram_point_data2.counts_ = {200, 300, 400, 500}; + histogram_point_data2.sum_ = 900l; + metric_sdk::ResourceMetrics data; + auto resource = opentelemetry::sdk::resource::Resource::Create( + opentelemetry::sdk::resource::ResourceAttributes{}); + data.resource_ = &resource; + auto instrumentation_library = + opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary::Create("library_name", + "1.2.0"); + metric_sdk::MetricData metric_data{ + metric_sdk::InstrumentDescriptor{"library_name", "description", "unit", + metric_sdk::InstrumentType::kHistogram, + metric_sdk::InstrumentValueType::kDouble}, + metric_sdk::AggregationTemporality::kDelta, opentelemetry::common::SystemTimestamp{}, + opentelemetry::common::SystemTimestamp{}, + std::vector{ + {metric_sdk::PointAttributes{{"a1", "b1"}}, histogram_point_data}, + {metric_sdk::PointAttributes{{"a2", "b2"}}, histogram_point_data2}}}; + data.instrumentation_info_metric_data_ = std::vector{ + {instrumentation_library.get(), std::vector{metric_data}}}; + return data; +} + +inline metric_sdk::ResourceMetrics CreateLastValuePointData() +{ + metric_sdk::ResourceMetrics data; + auto resource = opentelemetry::sdk::resource::Resource::Create( + opentelemetry::sdk::resource::ResourceAttributes{}); + data.resource_ = &resource; + auto instrumentation_library = + opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary::Create("library_name", + "1.2.0"); + metric_sdk::LastValuePointData last_value_point_data{}; + last_value_point_data.value_ = 10.0; + last_value_point_data.is_lastvalue_valid_ = true; + last_value_point_data.sample_ts_ = opentelemetry::common::SystemTimestamp{}; + metric_sdk::LastValuePointData last_value_point_data2{}; + last_value_point_data2.value_ = 20l; + last_value_point_data2.is_lastvalue_valid_ = true; + last_value_point_data2.sample_ts_ = opentelemetry::common::SystemTimestamp{}; + metric_sdk::MetricData metric_data{ + metric_sdk::InstrumentDescriptor{"library_name", "description", "unit", + metric_sdk::InstrumentType::kCounter, + metric_sdk::InstrumentValueType::kDouble}, + metric_sdk::AggregationTemporality::kDelta, opentelemetry::common::SystemTimestamp{}, + opentelemetry::common::SystemTimestamp{}, + std::vector{ + {metric_sdk::PointAttributes{{"a1", "b1"}}, last_value_point_data}, + {metric_sdk::PointAttributes{{"a2", "b2"}}, last_value_point_data2}}}; + data.instrumentation_info_metric_data_ = std::vector{ + {instrumentation_library.get(), std::vector{metric_data}}}; + return data; +} + +inline metric_sdk::ResourceMetrics CreateDropPointData() +{ + metric_sdk::ResourceMetrics data; + auto resource = opentelemetry::sdk::resource::Resource::Create( + opentelemetry::sdk::resource::ResourceAttributes{}); + data.resource_ = &resource; + auto instrumentation_library = + opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary::Create("library_name", + "1.2.0"); + metric_sdk::DropPointData drop_point_data{}; + metric_sdk::DropPointData drop_point_data2{}; + metric_sdk::MetricData metric_data{ + metric_sdk::InstrumentDescriptor{"library_name", "description", "unit", + metric_sdk::InstrumentType::kCounter, + metric_sdk::InstrumentValueType::kDouble}, + metric_sdk::AggregationTemporality::kDelta, opentelemetry::common::SystemTimestamp{}, + opentelemetry::common::SystemTimestamp{}, + std::vector{ + {metric_sdk::PointAttributes{{"a1", "b1"}}, drop_point_data}, + {metric_sdk::PointAttributes{{"a2", "b2"}}, drop_point_data2}}}; + data.instrumentation_info_metric_data_ = std::vector{ + {instrumentation_library.get(), std::vector{metric_data}}}; + return data; +} +} // namespace + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace metrics +{ +inline bool operator==(const metric_sdk::MetricData &lhs, const metric_sdk::MetricData &rhs) +{ + if (lhs.start_ts != rhs.start_ts) + { + return false; + } + if (lhs.end_ts != rhs.end_ts) + { + return false; + } + + if (lhs.instrument_descriptor.description_ != rhs.instrument_descriptor.description_) + { + return false; + } + if (lhs.instrument_descriptor.name_ != rhs.instrument_descriptor.name_) + { + return false; + } + if (lhs.instrument_descriptor.type_ != rhs.instrument_descriptor.type_) + { + return false; + } + if (lhs.instrument_descriptor.unit_ != rhs.instrument_descriptor.unit_) + { + return false; + } + if (lhs.instrument_descriptor.value_type_ != rhs.instrument_descriptor.value_type_) + { + return false; + } + + if (lhs.point_data_attr_.size() != rhs.point_data_attr_.size()) + { + return false; + } + for (uint32_t idx = 0; idx < lhs.point_data_attr_.size(); ++idx) + { + if (lhs.point_data_attr_.at(idx).attributes != rhs.point_data_attr_.at(idx).attributes) + { + return false; + } + auto &lhs_point_data = lhs.point_data_attr_.at(idx).point_data; + auto &rhs_point_data = rhs.point_data_attr_.at(idx).point_data; + if (nostd::holds_alternative(lhs_point_data)) + { + if (nostd::get(lhs_point_data).value_ != + nostd::get(rhs_point_data).value_) + { + return false; + } + } + else if (nostd::holds_alternative(lhs_point_data)) + { + if (!nostd::holds_alternative(rhs_point_data)) + { + return false; + } + } + else if (nostd::holds_alternative(lhs_point_data)) + { + auto &lhs_histogram_d = nostd::get(lhs_point_data); + auto &rhs_histogram_d = nostd::get(rhs_point_data); + if (lhs_histogram_d.count_ != rhs_histogram_d.count_) + { + return false; + } + if (lhs_histogram_d.counts_ != rhs_histogram_d.counts_) + { + return false; + } + if (lhs_histogram_d.boundaries_ != rhs_histogram_d.boundaries_) + { + return false; + } + if (lhs_histogram_d.sum_ != rhs_histogram_d.sum_) + { + return false; + } + } + else if (nostd::holds_alternative(lhs_point_data)) + { + auto &lhs_last_value_d = nostd::get(lhs_point_data); + auto &rhs_last_value_d = nostd::get(rhs_point_data); + if (lhs_last_value_d.is_lastvalue_valid_ != rhs_last_value_d.is_lastvalue_valid_) + { + return false; + } + if (lhs_last_value_d.sample_ts_ != rhs_last_value_d.sample_ts_) + { + return false; + } + if (lhs_last_value_d.value_ != rhs_last_value_d.value_) + { + return false; + } + } + } + return true; +} +} // namespace metrics +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE +#endif // ENABLE_METRICS_PREVIEW