summaryrefslogtreecommitdiffstats
path: root/src/jaegertracing/opentelemetry-cpp/exporters/prometheus
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:54:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:54:28 +0000
commite6918187568dbd01842d8d1d2c808ce16a894239 (patch)
tree64f88b554b444a49f656b6c656111a145cbbaa28 /src/jaegertracing/opentelemetry-cpp/exporters/prometheus
parentInitial commit. (diff)
downloadceph-e6918187568dbd01842d8d1d2c808ce16a894239.tar.xz
ceph-e6918187568dbd01842d8d1d2c808ce16a894239.zip
Adding upstream version 18.2.2.upstream/18.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/jaegertracing/opentelemetry-cpp/exporters/prometheus')
-rw-r--r--src/jaegertracing/opentelemetry-cpp/exporters/prometheus/BUILD144
-rwxr-xr-xsrc/jaegertracing/opentelemetry-cpp/exporters/prometheus/CMakeLists.txt90
-rw-r--r--src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/collector.h87
-rw-r--r--src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter.h126
-rw-r--r--src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter_utils.h115
-rw-r--r--src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/prometheus_collector.h88
-rw-r--r--src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/prometheus_exporter.h98
-rw-r--r--src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/prometheus_exporter_utils.h171
-rw-r--r--src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/collector.cc89
-rw-r--r--src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/exporter.cc100
-rw-r--r--src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/exporter_utils.cc314
-rw-r--r--src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/prometheus_collector.cc166
-rw-r--r--src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/prometheus_exporter.cc110
-rw-r--r--src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/prometheus_exporter_utils.cc446
-rw-r--r--src/jaegertracing/opentelemetry-cpp/exporters/prometheus/test/CMakeLists.txt13
-rw-r--r--src/jaegertracing/opentelemetry-cpp/exporters/prometheus/test/prometheus_collector_test.cc756
-rw-r--r--src/jaegertracing/opentelemetry-cpp/exporters/prometheus/test/prometheus_exporter_test.cc218
-rw-r--r--src/jaegertracing/opentelemetry-cpp/exporters/prometheus/test/prometheus_exporter_utils_test.cc460
18 files changed, 3591 insertions, 0 deletions
diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/BUILD b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/BUILD
new file mode 100644
index 000000000..47c9bbab1
--- /dev/null
+++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/BUILD
@@ -0,0 +1,144 @@
+# Copyright 2020, OpenTelemetry Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+package(default_visibility = ["//visibility:public"])
+
+cc_library(
+ name = "prometheus_exporter_deprecated",
+ srcs = [
+ "src/prometheus_exporter.cc",
+ ],
+ hdrs = [
+ "include/opentelemetry/exporters/prometheus/prometheus_exporter.h",
+ ],
+ strip_include_prefix = "include",
+ tags = ["prometheus"],
+ deps = [
+ ":prometheus_collector_deprecated",
+ ":prometheus_exporter_utils_deprecated",
+ "//api",
+ "//sdk:headers",
+ "@com_github_jupp0r_prometheus_cpp//core",
+ "@com_github_jupp0r_prometheus_cpp//pull",
+ ],
+)
+
+cc_library(
+ name = "prometheus_exporter_utils_deprecated",
+ srcs = [
+ "src/prometheus_exporter_utils.cc",
+ ],
+ hdrs = [
+ "include/opentelemetry/exporters/prometheus/prometheus_exporter_utils.h",
+ ],
+ strip_include_prefix = "include",
+ tags = ["prometheus"],
+ deps = [
+ "//api",
+ "//sdk:headers",
+ "@com_github_jupp0r_prometheus_cpp//core",
+ "@com_github_jupp0r_prometheus_cpp//pull",
+ ],
+)
+
+cc_library(
+ name = "prometheus_collector_deprecated",
+ srcs = [
+ "src/prometheus_collector.cc",
+ ],
+ hdrs = [
+ "include/opentelemetry/exporters/prometheus/prometheus_collector.h",
+ ],
+ strip_include_prefix = "include",
+ tags = ["prometheus"],
+ deps = [
+ ":prometheus_exporter_utils_deprecated",
+ "//api",
+ "//sdk:headers",
+ "@com_github_jupp0r_prometheus_cpp//core",
+ "@com_github_jupp0r_prometheus_cpp//pull",
+ ],
+)
+
+cc_test(
+ name = "prometheus_exporter_test_deprecated",
+ srcs = [
+ "test/prometheus_exporter_test.cc",
+ ],
+ tags = [
+ "prometheus",
+ "test",
+ ],
+ deps = [
+ ":prometheus_exporter_deprecated",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_library(
+ name = "prometheus_exporter",
+ srcs = [
+ "src/exporter.cc",
+ ],
+ hdrs = [
+ "include/opentelemetry/exporters/prometheus/exporter.h",
+ ],
+ strip_include_prefix = "include",
+ tags = ["prometheus"],
+ deps = [
+ ":prometheus_collector",
+ ":prometheus_exporter_utils",
+ "//api",
+ "//sdk:headers",
+ "@com_github_jupp0r_prometheus_cpp//core",
+ "@com_github_jupp0r_prometheus_cpp//pull",
+ ],
+)
+
+cc_library(
+ name = "prometheus_exporter_utils",
+ srcs = [
+ "src/exporter_utils.cc",
+ ],
+ hdrs = [
+ "include/opentelemetry/exporters/prometheus/exporter_utils.h",
+ ],
+ strip_include_prefix = "include",
+ tags = ["prometheus"],
+ deps = [
+ "//api",
+ "//sdk:headers",
+ "@com_github_jupp0r_prometheus_cpp//core",
+ "@com_github_jupp0r_prometheus_cpp//pull",
+ ],
+)
+
+cc_library(
+ name = "prometheus_collector",
+ srcs = [
+ "src/collector.cc",
+ ],
+ hdrs = [
+ "include/opentelemetry/exporters/prometheus/collector.h",
+ ],
+ strip_include_prefix = "include",
+ tags = ["prometheus"],
+ deps = [
+ ":prometheus_exporter_utils",
+ "//api",
+ "//sdk:headers",
+ "@com_github_jupp0r_prometheus_cpp//core",
+ "@com_github_jupp0r_prometheus_cpp//pull",
+ ],
+)
diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/CMakeLists.txt b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/CMakeLists.txt
new file mode 100755
index 000000000..56523ec84
--- /dev/null
+++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/CMakeLists.txt
@@ -0,0 +1,90 @@
+# Copyright 2020, OpenTelemetry Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+include_directories(include)
+if(NOT TARGET prometheus-cpp::core)
+ find_package(prometheus-cpp CONFIG REQUIRED)
+endif()
+if(WITH_METRICS_PREVIEW)
+ add_library(
+ prometheus_exporter_deprecated
+ src/prometheus_exporter.cc src/prometheus_collector.cc
+ src/prometheus_exporter_utils.cc)
+ target_include_directories(
+ prometheus_exporter_deprecated
+ PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>"
+ "$<INSTALL_INTERFACE:include>")
+
+ set(PROMETHEUS_EXPORTER_TARGETS_DEPRECATED prometheus_exporter_deprecated)
+ if(TARGET pull)
+ list(APPEND PROMETHEUS_EXPORTER_TARGETS_DEPRECATED pull)
+ endif()
+ if(TARGET core)
+ list(APPEND PROMETHEUS_EXPORTER_TARGETS_DEPRECATED core)
+ endif()
+ target_link_libraries(
+ prometheus_exporter_deprecated
+ PUBLIC opentelemetry_metrics_deprecated prometheus-cpp::pull
+ prometheus-cpp::core)
+ install(
+ TARGETS ${PROMETHEUS_EXPORTER_TARGETS_DEPRECATED}
+ EXPORT "${PROJECT_NAME}-target"
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
+
+ install(
+ DIRECTORY include/opentelemetry/exporters/prometheus
+ DESTINATION include/opentelemetry/exporters/
+ FILES_MATCHING
+ PATTERN "*.h")
+ if(BUILD_TESTING)
+ add_subdirectory(test)
+ endif()
+else()
+
+ add_library(prometheus_exporter src/exporter.cc src/collector.cc
+ src/exporter_utils.cc)
+ target_include_directories(
+ prometheus_exporter
+ PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>"
+ "$<INSTALL_INTERFACE:include>")
+
+ set(PROMETHEUS_EXPORTER_TARGETS prometheus_exporter)
+ if(TARGET pull)
+ list(APPEND PROMETHEUS_EXPORTER_TARGETS pull)
+ endif()
+ if(TARGET core)
+ list(APPEND PROMETHEUS_EXPORTER_TARGETS core)
+ endif()
+ target_link_libraries(
+ prometheus_exporter PUBLIC opentelemetry_metrics prometheus-cpp::pull
+ prometheus-cpp::core)
+ install(
+ TARGETS ${PROMETHEUS_EXPORTER_TARGETS}
+ EXPORT "${PROJECT_NAME}-target"
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
+
+ install(
+ DIRECTORY include/opentelemetry/exporters/prometheus
+ DESTINATION include/opentelemetry/exporters/
+ FILES_MATCHING
+ PATTERN "*.h")
+
+ if(BUILD_TESTING)
+ add_subdirectory(test)
+ endif()
+endif()
diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/collector.h b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/collector.h
new file mode 100644
index 000000000..68f50d29f
--- /dev/null
+++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/collector.h
@@ -0,0 +1,87 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+#ifndef ENABLE_METRICS_PREVIEW
+
+# include <memory>
+# include <mutex>
+# include <vector>
+
+# include <prometheus/collectable.h>
+# include <prometheus/metric_family.h>
+# include "opentelemetry/exporters/prometheus/exporter_utils.h"
+
+namespace prometheus_client = ::prometheus;
+
+OPENTELEMETRY_BEGIN_NAMESPACE
+namespace exporter
+{
+namespace metrics
+{
+/**
+ * The Prometheus Collector maintains the intermediate collection in Prometheus Exporter
+ */
+class PrometheusCollector : public prometheus_client::Collectable
+{
+public:
+ /**
+ * Default Constructor.
+ *
+ * This constructor initializes the collection for metrics to export
+ * in this class with default capacity
+ */
+ explicit PrometheusCollector(size_t max_collection_size = 2048);
+
+ /**
+ * Collects all metrics data from metricsToCollect collection.
+ *
+ * @return all metrics in the metricsToCollect snapshot
+ */
+ std::vector<prometheus_client::MetricFamily> Collect() const override;
+
+ /**
+ * This function is called by export() function and add the collection of
+ * records to the metricsToCollect collection
+ *
+ * @param records a collection of records to add to the metricsToCollect collection
+ */
+ void AddMetricData(const sdk::metrics::ResourceMetrics &data);
+
+ /**
+ * Get the current collection in the collector.
+ *
+ * @return the current metricsToCollect collection
+ */
+ std::vector<std::unique_ptr<sdk::metrics::ResourceMetrics>> &GetCollection();
+
+ /**
+ * Gets the maximum size of the collection.
+ *
+ * @return max collection size
+ */
+ int GetMaxCollectionSize() const;
+
+private:
+ /**
+ * Collection of metrics data from the export() function, and to be export
+ * to user when they send a pull request. This collection is a pointer
+ * to a collection so Collect() is able to clear the collection, even
+ * though it is a const function.
+ */
+ mutable std::vector<std::unique_ptr<sdk::metrics::ResourceMetrics>> metrics_to_collect_;
+
+ /**
+ * Maximum size of the metricsToCollect collection.
+ */
+ size_t max_collection_size_;
+
+ /*
+ * Lock when operating the metricsToCollect collection
+ */
+ mutable std::mutex collection_lock_;
+};
+} // namespace metrics
+} // namespace exporter
+OPENTELEMETRY_END_NAMESPACE
+#endif
diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter.h b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter.h
new file mode 100644
index 000000000..59ef1a11a
--- /dev/null
+++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter.h
@@ -0,0 +1,126 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+#ifndef ENABLE_METRICS_PREVIEW
+# include <memory>
+# include <string>
+# include <vector>
+
+# include <prometheus/exposer.h>
+# include "opentelemetry/common/spin_lock_mutex.h"
+# include "opentelemetry/exporters/prometheus/collector.h"
+# include "opentelemetry/nostd/span.h"
+# include "opentelemetry/sdk/common/env_variables.h"
+# include "opentelemetry/sdk/metrics/metric_exporter.h"
+# include "opentelemetry/version.h"
+
+/**
+ * This class is an implementation of the MetricsExporter interface and
+ * exports Prometheus metrics data. Functions in this class should be
+ * called by the Controller in our data pipeline.
+ */
+
+OPENTELEMETRY_BEGIN_NAMESPACE
+
+namespace exporter
+{
+namespace metrics
+{
+
+inline const std::string GetPrometheusDefaultHttpEndpoint()
+{
+ constexpr char kPrometheusEndpointEnv[] = "PROMETHEUS_EXPORTER_ENDPOINT";
+ constexpr char kPrometheusEndpointDefault[] = "localhost:9464";
+
+ auto endpoint = opentelemetry::sdk::common::GetEnvironmentVariable(kPrometheusEndpointEnv);
+ return endpoint.size() ? endpoint : kPrometheusEndpointDefault;
+}
+
+/**
+ * Struct to hold Prometheus exporter options.
+ */
+struct PrometheusExporterOptions
+{
+ // The endpoint the Prometheus backend can collect metrics from
+ std::string url = GetPrometheusDefaultHttpEndpoint();
+};
+
+class PrometheusExporter : public sdk::metrics::MetricExporter
+{
+public:
+ /**
+ * Constructor - binds an exposer and collector to the exporter
+ * @param options: options for an exposer that exposes
+ * an HTTP endpoint for the exporter to connect to
+ */
+ PrometheusExporter(const PrometheusExporterOptions &options);
+
+ /**
+ * Exports a batch of Metric Records.
+ * @param records: a collection of records to export
+ * @return: returns a ReturnCode detailing a success, or type of failure
+ */
+ sdk::common::ExportResult Export(const sdk::metrics::ResourceMetrics &data) noexcept override;
+
+ /**
+ * Force flush the exporter.
+ */
+ bool ForceFlush(
+ std::chrono::microseconds timeout = (std::chrono::microseconds::max)()) noexcept override;
+
+ /**
+ * Shuts down the exporter and does cleanup.
+ * Since Prometheus is a pull based interface,
+ * we cannot serve data remaining in the intermediate
+ * collection to to client an HTTP request being sent,
+ * so we flush the data.
+ */
+ bool Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept override;
+
+ /**
+ * @return: returns a shared_ptr to
+ * the PrometheusCollector instance
+ */
+ std::shared_ptr<PrometheusCollector> &GetCollector();
+
+ /**
+ * @return: Gets the shutdown status of the exporter
+ */
+ bool IsShutdown() const;
+
+private:
+ // The configuration options associated with this exporter.
+ const PrometheusExporterOptions options_;
+ /**
+ * exporter shutdown status
+ */
+ bool is_shutdown_;
+
+ /**
+ * Pointer to a
+ * PrometheusCollector instance
+ */
+ std::shared_ptr<PrometheusCollector> collector_;
+
+ /**
+ * Pointer to an
+ * Exposer instance
+ */
+ std::unique_ptr<::prometheus::Exposer> exposer_;
+
+ /**
+ * friend class for testing
+ */
+ friend class PrometheusExporterTest;
+
+ /**
+ * PrometheusExporter constructor with no parameters
+ * Used for testing only
+ */
+ PrometheusExporter();
+};
+} // namespace metrics
+} // namespace exporter
+OPENTELEMETRY_END_NAMESPACE
+#endif // ENABLE_METRICS_PREVIEW
diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter_utils.h b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter_utils.h
new file mode 100644
index 000000000..c8df4f6cf
--- /dev/null
+++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter_utils.h
@@ -0,0 +1,115 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+#ifndef ENABLE_METRICS_PREVIEW
+
+# include <prometheus/metric_family.h>
+# include <string>
+# include <vector>
+# include "opentelemetry/metrics/provider.h"
+# include "opentelemetry/sdk/metrics/meter.h"
+# include "opentelemetry/version.h"
+
+OPENTELEMETRY_BEGIN_NAMESPACE
+namespace exporter
+{
+namespace metrics
+{
+/**
+ * The Prometheus Utils contains utility functions for Prometheus Exporter
+ */
+class PrometheusExporterUtils
+{
+public:
+ /**
+ * Helper function to convert OpenTelemetry metrics data collection
+ * to Prometheus metrics data collection
+ *
+ * @param records a collection of metrics in OpenTelemetry
+ * @return a collection of translated metrics that is acceptable by Prometheus
+ */
+ static std::vector<::prometheus::MetricFamily> TranslateToPrometheus(
+ const std::vector<std::unique_ptr<sdk::metrics::ResourceMetrics>> &data);
+
+private:
+ /**
+ * Sanitize the given metric name or label according to Prometheus rule.
+ *
+ * This function is needed because names in OpenTelemetry can contain
+ * alphanumeric characters, '_', '.', and '-', whereas in Prometheus the
+ * name should only contain alphanumeric characters and '_'.
+ */
+ static std::string SanitizeNames(std::string name);
+
+ static opentelemetry::sdk::metrics::AggregationType getAggregationType(
+ const opentelemetry::sdk::metrics::PointType &point_type);
+
+ /**
+ * Translate the OTel metric type to Prometheus metric type
+ */
+ static ::prometheus::MetricType TranslateType(opentelemetry::sdk::metrics::AggregationType kind);
+
+ /**
+ * Set metric data for:
+ * Counter => Prometheus Counter
+ */
+ template <typename T>
+ static void SetData(std::vector<T> values,
+ const opentelemetry::sdk::metrics::PointAttributes &labels,
+ ::prometheus::MetricType type,
+ std::chrono::nanoseconds time,
+ ::prometheus::MetricFamily *metric_family);
+
+ /**
+ * Set metric data for:
+ * Histogram => Prometheus Histogram
+ */
+ template <typename T>
+ static void SetData(std::vector<T> values,
+ const opentelemetry::sdk::metrics::ListType &boundaries,
+ const std::vector<uint64_t> &counts,
+ const opentelemetry::sdk::metrics::PointAttributes &labels,
+ std::chrono::nanoseconds time,
+ ::prometheus::MetricFamily *metric_family);
+
+ /**
+ * Set time and labels to metric data
+ */
+ static void SetMetricBasic(::prometheus::ClientMetric &metric,
+ std::chrono::nanoseconds time,
+ const opentelemetry::sdk::metrics::PointAttributes &labels);
+
+ /**
+ * Convert attribute value to string
+ */
+ static std::string AttributeValueToString(
+ const opentelemetry::sdk::common::OwnedAttributeValue &value);
+
+ /**
+ * Handle Counter and Gauge.
+ */
+ template <typename T>
+ static void SetValue(std::vector<T> values,
+ ::prometheus::MetricType type,
+ ::prometheus::ClientMetric *metric);
+
+ /**
+ * Handle Gauge from MinMaxSumCount
+ */
+ static void SetValue(double value, ::prometheus::ClientMetric *metric);
+
+ /**
+ * Handle Histogram
+ */
+ template <typename T, typename U>
+ static void SetValue(std::vector<T> values,
+ const std::list<U> &boundaries,
+ const std::vector<uint64_t> &counts,
+ ::prometheus::ClientMetric *metric);
+};
+} // namespace metrics
+} // namespace exporter
+OPENTELEMETRY_END_NAMESPACE
+#endif
diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/prometheus_collector.h b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/prometheus_collector.h
new file mode 100644
index 000000000..4be748385
--- /dev/null
+++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/prometheus_collector.h
@@ -0,0 +1,88 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+#ifdef ENABLE_METRICS_PREVIEW
+
+# include <memory>
+# include <mutex>
+# include <vector>
+
+# include "opentelemetry/exporters/prometheus/prometheus_exporter_utils.h"
+# include "opentelemetry/sdk/_metrics/record.h"
+# include "prometheus/collectable.h"
+# include "prometheus/metric_family.h"
+
+namespace prometheus_client = ::prometheus;
+
+OPENTELEMETRY_BEGIN_NAMESPACE
+namespace exporter
+{
+namespace prometheus
+{
+/**
+ * The Prometheus Collector maintains the intermediate collection in Prometheus Exporter
+ */
+class PrometheusCollector : public prometheus_client::Collectable
+{
+public:
+ /**
+ * Default Constructor.
+ *
+ * This constructor initializes the collection for metrics to export
+ * in this class with default capacity
+ */
+ explicit PrometheusCollector(size_t max_collection_size = 2048);
+
+ /**
+ * Collects all metrics data from metricsToCollect collection.
+ *
+ * @return all metrics in the metricsToCollect snapshot
+ */
+ std::vector<prometheus_client::MetricFamily> Collect() const override;
+
+ /**
+ * This function is called by export() function and add the collection of
+ * records to the metricsToCollect collection
+ *
+ * @param records a collection of records to add to the metricsToCollect collection
+ */
+ void AddMetricData(const std::vector<opentelemetry::sdk::metrics::Record> &records);
+
+ /**
+ * Get the current collection in the collector.
+ *
+ * @return the current metricsToCollect collection
+ */
+ std::vector<opentelemetry::sdk::metrics::Record> GetCollection();
+
+ /**
+ * Gets the maximum size of the collection.
+ *
+ * @return max collection size
+ */
+ int GetMaxCollectionSize() const;
+
+private:
+ /**
+ * Collection of metrics data from the export() function, and to be export
+ * to user when they send a pull request. This collection is a pointer
+ * to a collection so Collect() is able to clear the collection, even
+ * though it is a const function.
+ */
+ std::unique_ptr<std::vector<opentelemetry::sdk::metrics::Record>> metrics_to_collect_;
+
+ /**
+ * Maximum size of the metricsToCollect collection.
+ */
+ size_t max_collection_size_;
+
+ /*
+ * Lock when operating the metricsToCollect collection
+ */
+ mutable std::mutex collection_lock_;
+};
+} // namespace prometheus
+} // namespace exporter
+OPENTELEMETRY_END_NAMESPACE
+#endif
diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/prometheus_exporter.h b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/prometheus_exporter.h
new file mode 100644
index 000000000..7c1f99a75
--- /dev/null
+++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/prometheus_exporter.h
@@ -0,0 +1,98 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+#ifdef ENABLE_METRICS_PREVIEW
+# include <memory>
+# include <string>
+# include <vector>
+
+# include "opentelemetry/exporters/prometheus/prometheus_collector.h"
+# include "opentelemetry/sdk/_metrics/exporter.h"
+# include "opentelemetry/sdk/_metrics/record.h"
+# include "opentelemetry/version.h"
+# include "prometheus/exposer.h"
+
+/**
+ * This class is an implementation of the MetricsExporter interface and
+ * exports Prometheus metrics data. Functions in this class should be
+ * called by the Controller in our data pipeline.
+ */
+
+OPENTELEMETRY_BEGIN_NAMESPACE
+
+namespace exporter
+{
+namespace prometheus
+{
+class PrometheusExporter : public sdk::metrics::MetricsExporter
+{
+public:
+ /**
+ * Constructor - binds an exposer and collector to the exporter
+ * @param address: an address for an exposer that exposes
+ * an HTTP endpoint for the exporter to connect to
+ */
+ PrometheusExporter(std::string &address);
+
+ /**
+ * Exports a batch of Metric Records.
+ * @param records: a collection of records to export
+ * @return: returns a ReturnCode detailing a success, or type of failure
+ */
+ sdk::common::ExportResult Export(
+ const std::vector<sdk::metrics::Record> &records) noexcept override;
+
+ /**
+ * Shuts down the exporter and does cleanup.
+ * Since Prometheus is a pull based interface,
+ * we cannot serve data remaining in the intermediate
+ * collection to to client an HTTP request being sent,
+ * so we flush the data.
+ */
+ void Shutdown() noexcept;
+
+ /**
+ * @return: returns a shared_ptr to
+ * the PrometheusCollector instance
+ */
+ std::shared_ptr<PrometheusCollector> &GetCollector();
+
+ /**
+ * @return: Gets the shutdown status of the exporter
+ */
+ bool IsShutdown() const;
+
+private:
+ /**
+ * exporter shutdown status
+ */
+ bool is_shutdown_;
+
+ /**
+ * Pointer to a
+ * PrometheusCollector instance
+ */
+ std::shared_ptr<PrometheusCollector> collector_;
+
+ /**
+ * Pointer to an
+ * Exposer instance
+ */
+ std::unique_ptr<::prometheus::Exposer> exposer_;
+
+ /**
+ * friend class for testing
+ */
+ friend class PrometheusExporterTest;
+
+ /**
+ * PrometheusExporter constructor with no parameters
+ * Used for testing only
+ */
+ PrometheusExporter();
+};
+} // namespace prometheus
+} // namespace exporter
+OPENTELEMETRY_END_NAMESPACE
+#endif // ENABLE_METRICS_PREVIEW
diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/prometheus_exporter_utils.h b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/prometheus_exporter_utils.h
new file mode 100644
index 000000000..7efea9d39
--- /dev/null
+++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/prometheus_exporter_utils.h
@@ -0,0 +1,171 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+#ifdef ENABLE_METRICS_PREVIEW
+
+# include <string>
+# include <vector>
+
+# include "opentelemetry/sdk/_metrics/record.h"
+# include "prometheus/metric_family.h"
+
+OPENTELEMETRY_BEGIN_NAMESPACE
+namespace exporter
+{
+namespace prometheus
+{
+/**
+ * The Prometheus Utils contains utility functions for Prometheus Exporter
+ */
+class PrometheusExporterUtils
+{
+public:
+ /**
+ * Helper function to convert OpenTelemetry metrics data collection
+ * to Prometheus metrics data collection
+ *
+ * @param records a collection of metrics in OpenTelemetry
+ * @return a collection of translated metrics that is acceptable by Prometheus
+ */
+ static std::vector<::prometheus::MetricFamily> TranslateToPrometheus(
+ const std::vector<opentelemetry::sdk::metrics::Record> &records);
+
+private:
+ /**
+ * Set value to metric family according to record
+ */
+ static void SetMetricFamily(opentelemetry::sdk::metrics::Record &record,
+ ::prometheus::MetricFamily *metric_family);
+
+ /**
+ * Sanitize the given metric name or label according to Prometheus rule.
+ *
+ * This function is needed because names in OpenTelemetry can contain
+ * alphanumeric characters, '_', '.', and '-', whereas in Prometheus the
+ * name should only contain alphanumeric characters and '_'.
+ */
+ static std::string SanitizeNames(std::string name);
+
+ /**
+ * Set value to metric family for different aggregator
+ */
+ template <typename T>
+ static void SetMetricFamilyByAggregator(
+ std::shared_ptr<opentelemetry::sdk::metrics::Aggregator<T>> aggregator,
+ std::string labels_str,
+ ::prometheus::MetricFamily *metric_family);
+
+ /**
+ * Translate the OTel metric type to Prometheus metric type
+ */
+ static ::prometheus::MetricType TranslateType(opentelemetry::sdk::metrics::AggregatorKind kind);
+
+ /**
+ * Set metric data for:
+ * Counter => Prometheus Counter
+ * Gauge => Prometheus Gauge
+ */
+ template <typename T>
+ static void SetData(std::vector<T> values,
+ const std::string &labels,
+ ::prometheus::MetricType type,
+ std::chrono::nanoseconds time,
+ ::prometheus::MetricFamily *metric_family);
+
+ /**
+ * Set metric data for:
+ * Histogram => Prometheus Histogram
+ */
+ template <typename T>
+ static void SetData(std::vector<T> values,
+ const std::vector<double> &boundaries,
+ const std::vector<int> &counts,
+ const std::string &labels,
+ std::chrono::nanoseconds time,
+ ::prometheus::MetricFamily *metric_family);
+
+ /**
+ * Set metric data for:
+ * MinMaxSumCount => Prometheus Gauge
+ * Use Average (sum / count) as the gauge metric
+ */
+ static void SetData(double value,
+ const std::string &labels,
+ std::chrono::nanoseconds time,
+ ::prometheus::MetricFamily *metric_family);
+
+ /**
+ * Set metric data for:
+ * Exact => Prometheus Summary
+ * Sketch => Prometheus Summary
+ */
+ template <typename T>
+ static void SetData(std::vector<T> values,
+ opentelemetry::sdk::metrics::AggregatorKind kind,
+ const std::vector<T> &quantiles,
+ const std::string &labels,
+ std::chrono::nanoseconds time,
+ ::prometheus::MetricFamily *metric_family,
+ bool do_quantile,
+ std::vector<double> quantile_points);
+
+ /**
+ * Set time and labels to metric data
+ */
+ static void SetMetricBasic(::prometheus::ClientMetric &metric,
+ std::chrono::nanoseconds time,
+ const std::string &labels);
+
+ /**
+ * Parse a string of labels (key:value) into a vector of pairs
+ * {,}
+ * {l1:v1,l2:v2,...,}
+ */
+ static std::vector<std::pair<std::string, std::string>> ParseLabel(std::string labels);
+
+ /**
+ * Build a quantiles vector from aggregator
+ */
+ template <typename T>
+ static std::vector<T> GetQuantilesVector(
+ std::shared_ptr<opentelemetry::sdk::metrics::Aggregator<T>> aggregator,
+ const std::vector<double> &quantile_points);
+
+ /**
+ * Handle Counter and Gauge.
+ */
+ template <typename T>
+ static void SetValue(std::vector<T> values,
+ ::prometheus::MetricType type,
+ ::prometheus::ClientMetric *metric);
+
+ /**
+ * Handle Gauge from MinMaxSumCount
+ */
+ static void SetValue(double value, ::prometheus::ClientMetric *metric);
+
+ /**
+ * Handle Histogram
+ */
+ template <typename T>
+ static void SetValue(std::vector<T> values,
+ std::vector<double> boundaries,
+ std::vector<int> counts,
+ ::prometheus::ClientMetric *metric);
+
+ /**
+ * Handle Exact and Sketch
+ */
+ template <typename T>
+ static void SetValue(std::vector<T> values,
+ opentelemetry::sdk::metrics::AggregatorKind kind,
+ std::vector<T> quantiles,
+ ::prometheus::ClientMetric *metric,
+ bool do_quantile,
+ const std::vector<double> &quantile_points);
+};
+} // namespace prometheus
+} // namespace exporter
+OPENTELEMETRY_END_NAMESPACE
+#endif
diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/collector.cc b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/collector.cc
new file mode 100644
index 000000000..03793a8ee
--- /dev/null
+++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/collector.cc
@@ -0,0 +1,89 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef ENABLE_METRICS_PREVIEW
+
+# include "opentelemetry/exporters/prometheus/collector.h"
+
+namespace metric_sdk = opentelemetry::sdk::metrics;
+
+OPENTELEMETRY_BEGIN_NAMESPACE
+namespace exporter
+{
+namespace metrics
+{
+/**
+ * Default Constructor.
+ *
+ * This constructor initializes the collection for metrics to export
+ * in this class with default capacity
+ */
+PrometheusCollector::PrometheusCollector(size_t max_collection_size)
+ : max_collection_size_(max_collection_size)
+{}
+
+/**
+ * Collects all metrics data from metricsToCollect collection.
+ *
+ * @return all metrics in the metricsToCollect snapshot
+ */
+std::vector<prometheus_client::MetricFamily> PrometheusCollector::Collect() const
+{
+ this->collection_lock_.lock();
+ if (metrics_to_collect_.empty())
+ {
+ this->collection_lock_.unlock();
+ return {};
+ }
+
+ std::vector<prometheus_client::MetricFamily> result;
+
+ // copy the intermediate collection, and then clear it
+ std::vector<std::unique_ptr<sdk::metrics::ResourceMetrics>> copied_data;
+ copied_data.swap(metrics_to_collect_);
+ this->collection_lock_.unlock();
+
+ result = PrometheusExporterUtils::TranslateToPrometheus(copied_data);
+ return result;
+}
+
+/**
+ * This function is called by export() function and add the collection of
+ * records to the metricsToCollect collection
+ *
+ * @param records a collection of records to add to the metricsToCollect collection
+ */
+void PrometheusCollector::AddMetricData(const sdk::metrics::ResourceMetrics &data)
+{
+ collection_lock_.lock();
+ if (metrics_to_collect_.size() + 1 <= max_collection_size_)
+ {
+ metrics_to_collect_.emplace_back(new sdk::metrics::ResourceMetrics{data});
+ }
+ collection_lock_.unlock();
+}
+
+/**
+ * Get the current collection in the collector.
+ *
+ * @return the current metrics_to_collect collection
+ */
+std::vector<std::unique_ptr<sdk::metrics::ResourceMetrics>> &PrometheusCollector::GetCollection()
+{
+ return metrics_to_collect_;
+}
+
+/**
+ * Gets the maximum size of the collection.
+ *
+ * @return max collection size
+ */
+int PrometheusCollector::GetMaxCollectionSize() const
+{
+ return max_collection_size_;
+}
+
+} // namespace metrics
+} // namespace exporter
+OPENTELEMETRY_END_NAMESPACE
+#endif
diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/exporter.cc b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/exporter.cc
new file mode 100644
index 000000000..a0bd9e27a
--- /dev/null
+++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/exporter.cc
@@ -0,0 +1,100 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef ENABLE_METRICS_PREVIEW
+# include "opentelemetry/exporters/prometheus/exporter.h"
+
+OPENTELEMETRY_BEGIN_NAMESPACE
+
+namespace exporter
+{
+namespace metrics
+{
+/**
+ * Constructor - binds an exposer and collector to the exporter
+ * @param address: an address for an exposer that exposes
+ * an HTTP endpoint for the exporter to connect to
+ */
+PrometheusExporter::PrometheusExporter(const PrometheusExporterOptions &options)
+ : options_(options), is_shutdown_(false)
+{
+ exposer_ = std::unique_ptr<::prometheus::Exposer>(new ::prometheus::Exposer{options_.url});
+ collector_ = std::shared_ptr<PrometheusCollector>(new PrometheusCollector);
+
+ exposer_->RegisterCollectable(collector_);
+}
+
+/**
+ * PrometheusExporter constructor with no parameters
+ * Used for testing only
+ */
+PrometheusExporter::PrometheusExporter() : is_shutdown_(false)
+{
+ collector_ = std::unique_ptr<PrometheusCollector>(new PrometheusCollector);
+}
+
+/**
+ * Exports a batch of Metric Records.
+ * @param records: a collection of records to export
+ * @return: returns a ReturnCode detailing a success, or type of failure
+ */
+sdk::common::ExportResult PrometheusExporter::Export(
+ const sdk::metrics::ResourceMetrics &data) noexcept
+{
+ if (is_shutdown_)
+ {
+ return sdk::common::ExportResult::kFailure;
+ }
+ else if (collector_->GetCollection().size() + 1 > (size_t)collector_->GetMaxCollectionSize())
+ {
+ return sdk::common::ExportResult::kFailureFull;
+ }
+ else
+ {
+ collector_->AddMetricData(data);
+ return sdk::common::ExportResult::kSuccess;
+ }
+ return sdk::common::ExportResult::kSuccess;
+}
+
+bool PrometheusExporter::ForceFlush(std::chrono::microseconds timeout) noexcept
+{
+ return true;
+}
+
+/**
+ * Shuts down the exporter and does cleanup.
+ * Since Prometheus is a pull based interface,
+ * we cannot serve data remaining in the intermediate
+ * collection to to client an HTTP request being sent,
+ * so we flush the data.
+ */
+bool PrometheusExporter::Shutdown(std::chrono::microseconds timeout) noexcept
+{
+ is_shutdown_ = true;
+ return true;
+
+ collector_->GetCollection().clear();
+}
+
+/**
+ * @return: returns a shared_ptr to
+ * the PrometheusCollector instance
+ */
+std::shared_ptr<PrometheusCollector> &PrometheusExporter::GetCollector()
+{
+ return collector_;
+}
+
+/**
+ * @return: Gets the shutdown status of the exporter
+ */
+bool PrometheusExporter::IsShutdown() const
+{
+ return is_shutdown_;
+}
+
+} // namespace metrics
+} // namespace exporter
+OPENTELEMETRY_END_NAMESPACE
+#endif // ENABLE_METRICS_PREVIEW
diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/exporter_utils.cc b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/exporter_utils.cc
new file mode 100644
index 000000000..383925f98
--- /dev/null
+++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/exporter_utils.cc
@@ -0,0 +1,314 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef ENABLE_METRICS_PREVIEW
+# include <sstream>
+# include <utility>
+# include <vector>
+
+# include <prometheus/metric_type.h>
+# include "opentelemetry/exporters/prometheus/exporter_utils.h"
+# include "opentelemetry/sdk/metrics/export/metric_producer.h"
+
+# include "opentelemetry/sdk/common/global_log_handler.h"
+
+namespace prometheus_client = ::prometheus;
+namespace metric_sdk = opentelemetry::sdk::metrics;
+
+OPENTELEMETRY_BEGIN_NAMESPACE
+namespace exporter
+{
+namespace metrics
+{
+/**
+ * Helper function to convert OpenTelemetry metrics data collection
+ * to Prometheus metrics data collection
+ *
+ * @param records a collection of metrics in OpenTelemetry
+ * @return a collection of translated metrics that is acceptable by Prometheus
+ */
+std::vector<prometheus_client::MetricFamily> PrometheusExporterUtils::TranslateToPrometheus(
+ const std::vector<std::unique_ptr<sdk::metrics::ResourceMetrics>> &data)
+{
+ if (data.empty())
+ {
+ return {};
+ }
+
+ // initialize output vector
+ std::vector<prometheus_client::MetricFamily> output;
+
+ // iterate through the vector and set result data into it
+ for (const auto &r : data)
+ {
+ for (const auto &instrumentation_info : r->instrumentation_info_metric_data_)
+ {
+ for (const auto &metric_data : instrumentation_info.metric_data_)
+ {
+ auto origin_name = metric_data.instrument_descriptor.name_;
+ auto sanitized = SanitizeNames(origin_name);
+ prometheus_client::MetricFamily metric_family;
+ metric_family.name = sanitized;
+ metric_family.help = metric_data.instrument_descriptor.description_;
+ auto time = metric_data.start_ts.time_since_epoch();
+ for (const auto &point_data_attr : metric_data.point_data_attr_)
+ {
+ auto kind = getAggregationType(point_data_attr.point_data);
+ const prometheus_client::MetricType type = TranslateType(kind);
+ metric_family.type = type;
+ if (type == prometheus_client::MetricType::Histogram) // Histogram
+ {
+ auto histogram_point_data =
+ nostd::get<sdk::metrics::HistogramPointData>(point_data_attr.point_data);
+ auto boundaries = histogram_point_data.boundaries_;
+ auto counts = histogram_point_data.counts_;
+ SetData(std::vector<double>{nostd::get<double>(histogram_point_data.sum_),
+ (double)histogram_point_data.count_},
+ boundaries, counts, point_data_attr.attributes, time, &metric_family);
+ }
+ else // Counter, Untyped
+ {
+ auto sum_point_data =
+ nostd::get<sdk::metrics::SumPointData>(point_data_attr.point_data);
+ std::vector<metric_sdk::ValueType> values{sum_point_data.value_};
+ SetData(values, point_data_attr.attributes, type, time, &metric_family);
+ }
+ }
+ output.emplace_back(metric_family);
+ }
+ }
+ }
+ return output;
+}
+
+/**
+ * Sanitize the given metric name or label according to Prometheus rule.
+ *
+ * This function is needed because names in OpenTelemetry can contain
+ * alphanumeric characters, '_', '.', and '-', whereas in Prometheus the
+ * name should only contain alphanumeric characters and '_'.
+ */
+std::string PrometheusExporterUtils::SanitizeNames(std::string name)
+{
+ // replace all '.' and '-' with '_'
+ std::replace(name.begin(), name.end(), '.', '_');
+ std::replace(name.begin(), name.end(), '-', '_');
+
+ return name;
+}
+
+metric_sdk::AggregationType PrometheusExporterUtils::getAggregationType(
+ const metric_sdk::PointType &point_type)
+{
+
+ if (nostd::holds_alternative<sdk::metrics::SumPointData>(point_type))
+ {
+ return metric_sdk::AggregationType::kSum;
+ }
+ else if (nostd::holds_alternative<sdk::metrics::DropPointData>(point_type))
+ {
+ return metric_sdk::AggregationType::kDrop;
+ }
+ else if (nostd::holds_alternative<sdk::metrics::HistogramPointData>(point_type))
+ {
+ return metric_sdk::AggregationType::kHistogram;
+ }
+ else if (nostd::holds_alternative<sdk::metrics::LastValuePointData>(point_type))
+ {
+ return metric_sdk::AggregationType::kLastValue;
+ }
+ return metric_sdk::AggregationType::kDefault;
+}
+
+/**
+ * Translate the OTel metric type to Prometheus metric type
+ */
+prometheus_client::MetricType PrometheusExporterUtils::TranslateType(
+ metric_sdk::AggregationType kind)
+{
+ switch (kind)
+ {
+ case metric_sdk::AggregationType::kSum:
+ return prometheus_client::MetricType::Counter;
+ case metric_sdk::AggregationType::kHistogram:
+ return prometheus_client::MetricType::Histogram;
+ default:
+ return prometheus_client::MetricType::Untyped;
+ }
+}
+
+/**
+ * Set metric data for:
+ * sum => Prometheus Counter
+ */
+template <typename T>
+void PrometheusExporterUtils::SetData(std::vector<T> values,
+ const metric_sdk::PointAttributes &labels,
+ prometheus_client::MetricType type,
+ std::chrono::nanoseconds time,
+ prometheus_client::MetricFamily *metric_family)
+{
+ metric_family->metric.emplace_back();
+ prometheus_client::ClientMetric &metric = metric_family->metric.back();
+ SetMetricBasic(metric, time, labels);
+ SetValue(values, type, &metric);
+}
+
+/**
+ * Set metric data for:
+ * Histogram => Prometheus Histogram
+ */
+template <typename T>
+void PrometheusExporterUtils::SetData(std::vector<T> values,
+ const opentelemetry::sdk::metrics::ListType &boundaries,
+ const std::vector<uint64_t> &counts,
+ const metric_sdk::PointAttributes &labels,
+ std::chrono::nanoseconds time,
+ prometheus_client::MetricFamily *metric_family)
+{
+ metric_family->metric.emplace_back();
+ prometheus_client::ClientMetric &metric = metric_family->metric.back();
+ SetMetricBasic(metric, time, labels);
+ if (nostd::holds_alternative<std::list<long>>(boundaries))
+ {
+ SetValue(values, nostd::get<std::list<long>>(boundaries), counts, &metric);
+ }
+ else
+ {
+ SetValue(values, nostd::get<std::list<double>>(boundaries), counts, &metric);
+ }
+}
+
+/**
+ * Set time and labels to metric data
+ */
+void PrometheusExporterUtils::SetMetricBasic(prometheus_client::ClientMetric &metric,
+ std::chrono::nanoseconds time,
+ const metric_sdk::PointAttributes &labels)
+{
+ metric.timestamp_ms = time.count() / 1000000;
+
+ // auto label_pairs = ParseLabel(labels);
+ if (!labels.empty())
+ {
+ metric.label.resize(labels.size());
+ size_t i = 0;
+ for (auto const &label : labels)
+ {
+ auto sanitized = SanitizeNames(label.first);
+ metric.label[i].name = sanitized;
+ metric.label[i++].value = AttributeValueToString(label.second);
+ }
+ }
+};
+
+std::string PrometheusExporterUtils::AttributeValueToString(
+ const opentelemetry::sdk::common::OwnedAttributeValue &value)
+{
+ std::string result;
+ if (nostd::holds_alternative<bool>(value))
+ {
+ result = nostd::get<bool>(value) ? "true" : "false";
+ }
+ else if (nostd::holds_alternative<int>(value))
+ {
+ result = std::to_string(nostd::get<int>(value));
+ }
+ else if (nostd::holds_alternative<int64_t>(value))
+ {
+ result = std::to_string(nostd::get<int64_t>(value));
+ }
+ else if (nostd::holds_alternative<unsigned int>(value))
+ {
+ result = std::to_string(nostd::get<unsigned int>(value));
+ }
+ else if (nostd::holds_alternative<uint64_t>(value))
+ {
+ result = std::to_string(nostd::get<uint64_t>(value));
+ }
+ else if (nostd::holds_alternative<double>(value))
+ {
+ result = std::to_string(nostd::get<double>(value));
+ }
+ else if (nostd::holds_alternative<std::string>(value))
+ {
+ result = nostd::get<std::string>(value);
+ }
+ else
+ {
+ OTEL_INTERNAL_LOG_WARN(
+ "[Prometheus Exporter] AttributeValueToString - "
+ " Nested attributes not supported - ignored");
+ }
+ return result;
+}
+
+/**
+ * Handle Counter.
+ */
+template <typename T>
+void PrometheusExporterUtils::SetValue(std::vector<T> values,
+ prometheus_client::MetricType type,
+ prometheus_client::ClientMetric *metric)
+{
+ double value = 0.0;
+ const auto &value_var = values[0];
+ if (nostd::holds_alternative<long>(value_var))
+ {
+ value = nostd::get<long>(value_var);
+ }
+ else
+ {
+ value = nostd::get<double>(value_var);
+ }
+
+ switch (type)
+ {
+ case prometheus_client::MetricType::Counter: {
+ metric->counter.value = value;
+ break;
+ }
+ case prometheus_client::MetricType::Untyped: {
+ metric->untyped.value = value;
+ break;
+ }
+ default:
+ return;
+ }
+}
+
+/**
+ * Handle Histogram
+ */
+template <typename T, typename U>
+void PrometheusExporterUtils::SetValue(std::vector<T> values,
+ const std::list<U> &boundaries,
+ const std::vector<uint64_t> &counts,
+ prometheus_client::ClientMetric *metric)
+{
+ metric->histogram.sample_sum = values[0];
+ metric->histogram.sample_count = values[1];
+ int cumulative = 0;
+ std::vector<prometheus_client::ClientMetric::Bucket> buckets;
+ uint32_t idx = 0;
+ for (const auto &boundary : boundaries)
+ {
+ prometheus_client::ClientMetric::Bucket bucket;
+ cumulative += counts[idx];
+ bucket.cumulative_count = cumulative;
+ bucket.upper_bound = boundary;
+ buckets.emplace_back(bucket);
+ ++idx;
+ }
+ prometheus_client::ClientMetric::Bucket bucket;
+ cumulative += counts[idx];
+ bucket.cumulative_count = cumulative;
+ bucket.upper_bound = std::numeric_limits<double>::infinity();
+ buckets.emplace_back(bucket);
+ metric->histogram.bucket = buckets;
+}
+
+} // namespace metrics
+} // namespace exporter
+OPENTELEMETRY_END_NAMESPACE
+#endif
diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/prometheus_collector.cc b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/prometheus_collector.cc
new file mode 100644
index 000000000..53b7913de
--- /dev/null
+++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/prometheus_collector.cc
@@ -0,0 +1,166 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+#ifdef ENABLE_METRICS_PREVIEW
+# include <iostream>
+
+# include "opentelemetry/exporters/prometheus/prometheus_collector.h"
+
+namespace metric_sdk = opentelemetry::sdk::metrics;
+
+OPENTELEMETRY_BEGIN_NAMESPACE
+namespace exporter
+{
+namespace prometheus
+{
+/**
+ * Default Constructor.
+ *
+ * This constructor initializes the collection for metrics to export
+ * in this class with default capacity
+ */
+PrometheusCollector::PrometheusCollector(size_t max_collection_size)
+ : max_collection_size_(max_collection_size)
+{
+ metrics_to_collect_ =
+ std::unique_ptr<std::vector<metric_sdk::Record>>(new std::vector<metric_sdk::Record>);
+}
+
+/**
+ * Collects all metrics data from metricsToCollect collection.
+ *
+ * @return all metrics in the metricsToCollect snapshot
+ */
+std::vector<prometheus_client::MetricFamily> PrometheusCollector::Collect() const
+{
+ this->collection_lock_.lock();
+ if (metrics_to_collect_->empty())
+ {
+ this->collection_lock_.unlock();
+ return {};
+ }
+ this->collection_lock_.unlock();
+
+ std::vector<prometheus_client::MetricFamily> result;
+
+ // copy the intermediate collection, and then clear it
+ std::vector<metric_sdk::Record> copied_data;
+
+ this->collection_lock_.lock();
+ copied_data = std::vector<metric_sdk::Record>(*metrics_to_collect_);
+ metrics_to_collect_->clear();
+ this->collection_lock_.unlock();
+
+ result = PrometheusExporterUtils::TranslateToPrometheus(copied_data);
+ return result;
+}
+
+/**
+ * This function is called by export() function and add the collection of
+ * records to the metricsToCollect collection
+ *
+ * @param records a collection of records to add to the metricsToCollect collection
+ */
+void PrometheusCollector::AddMetricData(const std::vector<sdk::metrics::Record> &records)
+{
+ if (records.empty())
+ {
+ return;
+ }
+
+ collection_lock_.lock();
+ if (metrics_to_collect_->size() + records.size() <= max_collection_size_)
+ {
+ /**
+ * ValidAggregator is a lambda that checks a Record to see if its
+ * Aggregator is a valid nostd::shared_ptr and not a nullptr.
+ */
+ auto ValidAggregator = [](sdk::metrics::Record record) {
+ auto aggregator_variant = record.GetAggregator();
+ if (nostd::holds_alternative<std::shared_ptr<metric_sdk::Aggregator<int>>>(
+ aggregator_variant))
+ {
+ auto aggregator =
+ nostd::get<std::shared_ptr<metric_sdk::Aggregator<int>>>(aggregator_variant);
+ if (!aggregator)
+ {
+ return false;
+ }
+ }
+ else if (nostd::holds_alternative<std::shared_ptr<metric_sdk::Aggregator<short>>>(
+ aggregator_variant))
+ {
+ auto aggregator =
+ nostd::get<std::shared_ptr<metric_sdk::Aggregator<short>>>(aggregator_variant);
+ if (!aggregator)
+ {
+ return false;
+ }
+ }
+ else if (nostd::holds_alternative<std::shared_ptr<metric_sdk::Aggregator<float>>>(
+ aggregator_variant))
+ {
+ auto aggregator =
+ nostd::get<std::shared_ptr<metric_sdk::Aggregator<float>>>(aggregator_variant);
+ if (!aggregator)
+ {
+ return false;
+ }
+ }
+ else if (nostd::holds_alternative<std::shared_ptr<metric_sdk::Aggregator<double>>>(
+ aggregator_variant))
+ {
+ auto aggregator =
+ nostd::get<std::shared_ptr<metric_sdk::Aggregator<double>>>(aggregator_variant);
+ if (!aggregator)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ };
+
+ for (auto &r : records)
+ {
+ if (ValidAggregator(r))
+ {
+ metrics_to_collect_->emplace_back(r);
+ }
+ // Drop the record and write to std::cout
+ else
+ {
+ // Cannot call non const functions on const Record r
+ sdk::metrics::Record c = r;
+ std::cout << "Dropped Record containing invalid aggregator with name: " + c.GetName()
+ << std::endl;
+ }
+ }
+ }
+ collection_lock_.unlock();
+}
+
+/**
+ * Get the current collection in the collector.
+ *
+ * @return the current metrics_to_collect collection
+ */
+std::vector<sdk::metrics::Record> PrometheusCollector::GetCollection()
+{
+ return *metrics_to_collect_;
+}
+
+/**
+ * Gets the maximum size of the collection.
+ *
+ * @return max collection size
+ */
+int PrometheusCollector::GetMaxCollectionSize() const
+{
+ return max_collection_size_;
+}
+
+} // namespace prometheus
+} // namespace exporter
+OPENTELEMETRY_END_NAMESPACE
+#endif
diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/prometheus_exporter.cc b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/prometheus_exporter.cc
new file mode 100644
index 000000000..b64af1e90
--- /dev/null
+++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/prometheus_exporter.cc
@@ -0,0 +1,110 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifdef ENABLE_METRICS_PREVIEW
+# include "opentelemetry/exporters/prometheus/prometheus_exporter.h"
+
+OPENTELEMETRY_BEGIN_NAMESPACE
+
+namespace exporter
+{
+namespace prometheus
+{
+/**
+ * Constructor - binds an exposer and collector to the exporter
+ * @param address: an address for an exposer that exposes
+ * an HTTP endpoint for the exporter to connect to
+ */
+PrometheusExporter::PrometheusExporter(std::string &address) : is_shutdown_(false)
+{
+ exposer_ = std::unique_ptr<::prometheus::Exposer>(new ::prometheus::Exposer{address});
+ collector_ = std::shared_ptr<PrometheusCollector>(new PrometheusCollector);
+
+ exposer_->RegisterCollectable(collector_);
+}
+
+/**
+ * PrometheusExporter constructor with no parameters
+ * Used for testing only
+ */
+PrometheusExporter::PrometheusExporter() : is_shutdown_(false)
+{
+ collector_ = std::unique_ptr<PrometheusCollector>(new PrometheusCollector);
+}
+
+/**
+ * Exports a batch of Metric Records.
+ * @param records: a collection of records to export
+ * @return: returns a ReturnCode detailing a success, or type of failure
+ */
+sdk::common::ExportResult PrometheusExporter::Export(
+ const std::vector<sdk::metrics::Record> &records) noexcept
+{
+ if (is_shutdown_)
+ {
+ return sdk::common::ExportResult::kFailure;
+ }
+ else if (records.empty())
+ {
+ return sdk::common::ExportResult::kFailureInvalidArgument;
+ }
+ else if (collector_->GetCollection().size() + records.size() >
+ (size_t)collector_->GetMaxCollectionSize())
+ {
+ return sdk::common::ExportResult::kFailureFull;
+ }
+ else
+ {
+ collector_->AddMetricData(records);
+ return sdk::common::ExportResult::kSuccess;
+ }
+}
+
+/**
+ * Shuts down the exporter and does cleanup.
+ * Since Prometheus is a pull based interface,
+ * we cannot serve data remaining in the intermediate
+ * collection to to client an HTTP request being sent,
+ * so we flush the data.
+ */
+void PrometheusExporter::Shutdown() noexcept
+{
+ is_shutdown_ = true;
+
+ collector_->GetCollection().clear();
+}
+
+/**
+ * @return: returns a shared_ptr to
+ * the PrometheusCollector instance
+ */
+std::shared_ptr<PrometheusCollector> &PrometheusExporter::GetCollector()
+{
+ return collector_;
+}
+
+/**
+ * @return: Gets the shutdown status of the exporter
+ */
+bool PrometheusExporter::IsShutdown() const
+{
+ return is_shutdown_;
+}
+
+} // namespace prometheus
+} // namespace exporter
+OPENTELEMETRY_END_NAMESPACE
+#endif // ENABLE_METRICS_PREVIEW
diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/prometheus_exporter_utils.cc b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/prometheus_exporter_utils.cc
new file mode 100644
index 000000000..5eec9e3e8
--- /dev/null
+++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/prometheus_exporter_utils.cc
@@ -0,0 +1,446 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+#ifdef ENABLE_METRICS_PREVIEW
+# include <iostream>
+# include <sstream>
+# include <utility>
+# include <vector>
+
+# include "opentelemetry/exporters/prometheus/prometheus_exporter_utils.h"
+# include "opentelemetry/sdk/_metrics/aggregator/aggregator.h"
+# include "prometheus/metric_type.h"
+
+namespace prometheus_client = ::prometheus;
+namespace metric_sdk = opentelemetry::sdk::metrics;
+
+OPENTELEMETRY_BEGIN_NAMESPACE
+namespace exporter
+{
+namespace prometheus
+{
+/**
+ * Helper function to convert OpenTelemetry metrics data collection
+ * to Prometheus metrics data collection
+ *
+ * @param records a collection of metrics in OpenTelemetry
+ * @return a collection of translated metrics that is acceptable by Prometheus
+ */
+std::vector<prometheus_client::MetricFamily> PrometheusExporterUtils::TranslateToPrometheus(
+ const std::vector<metric_sdk::Record> &records)
+{
+ if (records.empty())
+ {
+ return {};
+ }
+
+ // initialize output vector
+ std::vector<prometheus_client::MetricFamily> output(records.size());
+
+ // iterate through the vector and set result data into it
+ int i = 0;
+ for (auto r : records)
+ {
+ SetMetricFamily(r, &output[i]);
+ i++;
+ }
+
+ return output;
+}
+
+// ======================= private helper functions =========================
+/**
+ * Set value to metric family according to record
+ */
+void PrometheusExporterUtils::SetMetricFamily(metric_sdk::Record &record,
+ prometheus_client::MetricFamily *metric_family)
+{
+
+ auto origin_name = record.GetName();
+ auto sanitized = SanitizeNames(origin_name);
+ metric_family->name = sanitized;
+ metric_family->help = record.GetDescription();
+
+ // unpack the variant and set the metric data to metric family struct
+ auto agg_var = record.GetAggregator();
+ auto labels_str = record.GetLabels();
+ if (nostd::holds_alternative<std::shared_ptr<metric_sdk::Aggregator<int>>>(agg_var))
+ {
+ auto aggregator = nostd::get<std::shared_ptr<metric_sdk::Aggregator<int>>>(agg_var);
+ SetMetricFamilyByAggregator(aggregator, labels_str, metric_family);
+ }
+ else if (nostd::holds_alternative<std::shared_ptr<metric_sdk::Aggregator<short>>>(agg_var))
+ {
+ auto aggregator = nostd::get<std::shared_ptr<metric_sdk::Aggregator<short>>>(agg_var);
+ SetMetricFamilyByAggregator(aggregator, labels_str, metric_family);
+ }
+ else if (nostd::holds_alternative<std::shared_ptr<metric_sdk::Aggregator<float>>>(
+ record.GetAggregator()))
+ {
+ auto aggregator = nostd::get<std::shared_ptr<metric_sdk::Aggregator<float>>>(agg_var);
+ SetMetricFamilyByAggregator(aggregator, labels_str, metric_family);
+ }
+ else if (nostd::holds_alternative<std::shared_ptr<metric_sdk::Aggregator<double>>>(
+ record.GetAggregator()))
+ {
+ auto aggregator = nostd::get<std::shared_ptr<metric_sdk::Aggregator<double>>>(agg_var);
+ SetMetricFamilyByAggregator(aggregator, labels_str, metric_family);
+ }
+}
+
+/**
+ * Sanitize the given metric name or label according to Prometheus rule.
+ *
+ * This function is needed because names in OpenTelemetry can contain
+ * alphanumeric characters, '_', '.', and '-', whereas in Prometheus the
+ * name should only contain alphanumeric characters and '_'.
+ */
+std::string PrometheusExporterUtils::SanitizeNames(std::string name)
+{
+ // replace all '.' and '-' with '_'
+ std::replace(name.begin(), name.end(), '.', '_');
+ std::replace(name.begin(), name.end(), '-', '_');
+
+ return name;
+}
+
+/**
+ * Set value to metric family for different aggregator
+ */
+template <typename T>
+void PrometheusExporterUtils::SetMetricFamilyByAggregator(
+ std::shared_ptr<metric_sdk::Aggregator<T>> aggregator,
+ std::string labels_str,
+ prometheus_client::MetricFamily *metric_family)
+{
+ // get aggregator kind and translate to Prometheus metric type
+ auto kind = aggregator->get_aggregator_kind();
+ const prometheus_client::MetricType type = TranslateType(kind);
+ metric_family->type = type;
+ // get check-pointed values, label string and check-pointed time
+ auto checkpointed_values = aggregator->get_checkpoint();
+ auto time = aggregator->get_checkpoint_timestamp().time_since_epoch();
+
+ if (type == prometheus_client::MetricType::Histogram) // Histogram
+ {
+ auto boundaries = aggregator->get_boundaries();
+ auto counts = aggregator->get_counts();
+ SetData(checkpointed_values, boundaries, counts, labels_str, time, metric_family);
+ }
+ else if (type == prometheus_client::MetricType::Summary) // Sketch, Exact
+ {
+ std::vector<double> quantile_points = {0, 0.5, 0.9, 0.95, 0.99, 1};
+ if (kind == metric_sdk::AggregatorKind::Exact)
+ {
+ std::vector<T> quantiles;
+ bool do_quantile = aggregator->get_quant_estimation();
+ if (do_quantile)
+ {
+ quantiles = GetQuantilesVector(aggregator, quantile_points);
+ }
+ SetData(checkpointed_values, kind, quantiles, labels_str, time, metric_family, do_quantile,
+ quantile_points);
+ }
+ else if (kind == metric_sdk::AggregatorKind::Sketch)
+ {
+ auto quantiles = GetQuantilesVector(aggregator, quantile_points);
+ SetData(checkpointed_values, kind, quantiles, labels_str, time, metric_family, true,
+ quantile_points);
+ }
+ }
+ else // Counter, Gauge, MinMaxSumCount, Untyped
+ {
+ // Handle MinMaxSumCount: https://github.com/open-telemetry/opentelemetry-cpp/issues/228
+ // Use sum/count is ok.
+ if (kind == metric_sdk::AggregatorKind::MinMaxSumCount)
+ {
+ double avg = (double)checkpointed_values[2] / checkpointed_values[3];
+ SetData(avg, labels_str, time, metric_family);
+ }
+ else
+ {
+ SetData(checkpointed_values, labels_str, type, time, metric_family);
+ }
+ }
+}
+
+/**
+ * Translate the OTel metric type to Prometheus metric type
+ */
+prometheus_client::MetricType PrometheusExporterUtils::TranslateType(
+ metric_sdk::AggregatorKind kind)
+{
+ switch (kind)
+ {
+ case metric_sdk::AggregatorKind::Counter:
+ return prometheus_client::MetricType::Counter;
+ case metric_sdk::AggregatorKind::Gauge:
+ case metric_sdk::AggregatorKind::MinMaxSumCount:
+ return prometheus_client::MetricType::Gauge;
+ case metric_sdk::AggregatorKind::Histogram:
+ return prometheus_client::MetricType::Histogram;
+ case metric_sdk::AggregatorKind::Sketch:
+ case metric_sdk::AggregatorKind::Exact:
+ return prometheus_client::MetricType::Summary;
+ default:
+ return prometheus_client::MetricType::Untyped;
+ }
+}
+
+/**
+ * Set metric data for:
+ * Counter => Prometheus Counter
+ * Gauge => Prometheus Gauge
+ */
+template <typename T>
+void PrometheusExporterUtils::SetData(std::vector<T> values,
+ const std::string &labels,
+ prometheus_client::MetricType type,
+ std::chrono::nanoseconds time,
+ prometheus_client::MetricFamily *metric_family)
+{
+ metric_family->metric.emplace_back();
+ prometheus_client::ClientMetric &metric = metric_family->metric.back();
+ SetMetricBasic(metric, time, labels);
+ SetValue(values, type, &metric);
+}
+
+/**
+ * Set metric data for:
+ * Histogram => Prometheus Histogram
+ */
+template <typename T>
+void PrometheusExporterUtils::SetData(std::vector<T> values,
+ const std::vector<double> &boundaries,
+ const std::vector<int> &counts,
+ const std::string &labels,
+ std::chrono::nanoseconds time,
+ prometheus_client::MetricFamily *metric_family)
+{
+ metric_family->metric.emplace_back();
+ prometheus_client::ClientMetric &metric = metric_family->metric.back();
+ SetMetricBasic(metric, time, labels);
+ SetValue(values, boundaries, counts, &metric);
+}
+
+/**
+ * Set metric data for:
+ * MinMaxSumCount => Prometheus Gauge
+ * Use Average (sum / count) as the gauge metric
+ */
+void PrometheusExporterUtils::SetData(double value,
+ const std::string &labels,
+ std::chrono::nanoseconds time,
+ prometheus_client::MetricFamily *metric_family)
+{
+ metric_family->metric.emplace_back();
+ prometheus_client::ClientMetric &metric = metric_family->metric.back();
+ SetMetricBasic(metric, time, labels);
+ SetValue(value, &metric);
+}
+
+/**
+ * Set metric data for:
+ * Exact => Prometheus Summary
+ * Sketch => Prometheus Summary
+ */
+template <typename T>
+void PrometheusExporterUtils::SetData(std::vector<T> values,
+ metric_sdk::AggregatorKind kind,
+ const std::vector<T> &quantiles,
+ const std::string &labels,
+ std::chrono::nanoseconds time,
+ prometheus_client::MetricFamily *metric_family,
+ bool do_quantile,
+ std::vector<double> quantile_points)
+{
+ metric_family->metric.emplace_back();
+ prometheus_client::ClientMetric &metric = metric_family->metric.back();
+ SetMetricBasic(metric, time, labels);
+ SetValue(values, kind, quantiles, &metric, do_quantile, quantile_points);
+}
+
+/**
+ * Set time and labels to metric data
+ */
+void PrometheusExporterUtils::SetMetricBasic(prometheus_client::ClientMetric &metric,
+ std::chrono::nanoseconds time,
+ const std::string &labels)
+{
+ metric.timestamp_ms = time.count() / 1000000;
+
+ auto label_pairs = ParseLabel(labels);
+ if (!label_pairs.empty())
+ {
+ metric.label.resize(label_pairs.size());
+ for (size_t i = 0; i < label_pairs.size(); ++i)
+ {
+ auto origin_name = label_pairs[i].first;
+ auto sanitized = SanitizeNames(origin_name);
+ metric.label[i].name = sanitized;
+ metric.label[i].value = label_pairs[i].second;
+ }
+ }
+};
+
+/**
+ * Parse a string of labels (key:value) into a vector of pairs
+ * {,}
+ * {l1:v1,l2:v2,...,}
+ */
+std::vector<std::pair<std::string, std::string>> PrometheusExporterUtils::ParseLabel(
+ std::string labels)
+{
+ labels = labels.substr(1, labels.size() - 2);
+
+ std::vector<std::string> paired_labels;
+ std::stringstream s_stream(labels);
+ while (s_stream.good())
+ {
+ std::string substr;
+ getline(s_stream, substr, ','); // get first string delimited by comma
+ if (!substr.empty())
+ {
+ paired_labels.push_back(substr);
+ }
+ }
+
+ std::vector<std::pair<std::string, std::string>> result;
+ for (auto &paired : paired_labels)
+ {
+ std::size_t split_index = paired.find(':');
+ std::string label = paired.substr(0, split_index);
+ std::string value = paired.substr(split_index + 1);
+ result.emplace_back(std::pair<std::string, std::string>(label, value));
+ }
+
+ return result;
+}
+
+/**
+ * Build a quantiles vector from aggregator
+ */
+template <typename T>
+std::vector<T> PrometheusExporterUtils::GetQuantilesVector(
+ std::shared_ptr<metric_sdk::Aggregator<T>> aggregator,
+ const std::vector<double> &quantile_points)
+{
+ std::vector<T> quantiles;
+ for (double q : quantile_points)
+ {
+ T quantile = aggregator->get_quantiles(q);
+ quantiles.emplace_back(quantile);
+ }
+ return quantiles;
+}
+
+/**
+ * Handle Counter and Gauge.
+ */
+template <typename T>
+void PrometheusExporterUtils::SetValue(std::vector<T> values,
+ prometheus_client::MetricType type,
+ prometheus_client::ClientMetric *metric)
+{
+ switch (type)
+ {
+ case prometheus_client::MetricType::Counter: {
+ metric->counter.value = values[0];
+ break;
+ }
+ case prometheus_client::MetricType::Gauge: {
+ metric->gauge.value = values[0];
+ break;
+ }
+ case prometheus_client::MetricType::Untyped: {
+ metric->untyped.value = values[0];
+ break;
+ }
+ default:
+ return;
+ }
+}
+
+/**
+ * Handle Gauge from MinMaxSumCount
+ */
+void PrometheusExporterUtils::SetValue(double value, prometheus_client::ClientMetric *metric)
+{
+ metric->gauge.value = value;
+}
+
+/**
+ * Handle Histogram
+ */
+template <typename T>
+void PrometheusExporterUtils::SetValue(std::vector<T> values,
+ std::vector<double> boundaries,
+ std::vector<int> counts,
+ prometheus_client::ClientMetric *metric)
+{
+ metric->histogram.sample_sum = values[0];
+ metric->histogram.sample_count = values[1];
+ int cumulative = 0;
+ std::vector<prometheus_client::ClientMetric::Bucket> buckets;
+ for (size_t i = 0; i < boundaries.size() + 1; i++)
+ {
+ prometheus_client::ClientMetric::Bucket bucket;
+ cumulative += counts[i];
+ bucket.cumulative_count = cumulative;
+ if (i != boundaries.size())
+ {
+ bucket.upper_bound = boundaries[i];
+ }
+ else
+ {
+ bucket.upper_bound = std::numeric_limits<double>::infinity();
+ }
+ buckets.emplace_back(bucket);
+ }
+ metric->histogram.bucket = buckets;
+}
+
+/**
+ * Handle Exact and Sketch
+ */
+template <typename T>
+void PrometheusExporterUtils::SetValue(std::vector<T> values,
+ metric_sdk::AggregatorKind kind,
+ std::vector<T> quantiles,
+ prometheus_client::ClientMetric *metric,
+ bool do_quantile,
+ const std::vector<double> &quantile_points)
+{
+ if (kind == metric_sdk::AggregatorKind::Exact)
+ {
+ metric->summary.sample_count = values.size();
+ auto sum = 0;
+ for (auto val : values)
+ {
+ sum += val;
+ }
+ metric->summary.sample_sum = sum;
+ }
+ else if (kind == metric_sdk::AggregatorKind::Sketch)
+ {
+ metric->summary.sample_sum = values[0];
+ metric->summary.sample_count = values[1];
+ }
+
+ if (do_quantile)
+ {
+ std::vector<prometheus_client::ClientMetric::Quantile> prometheus_quantiles;
+ for (size_t i = 0; i < quantiles.size(); i++)
+ {
+ prometheus_client::ClientMetric::Quantile quantile;
+ quantile.quantile = quantile_points[i];
+ quantile.value = quantiles[i];
+ prometheus_quantiles.emplace_back(quantile);
+ }
+ metric->summary.quantile = prometheus_quantiles;
+ }
+}
+} // namespace prometheus
+} // namespace exporter
+OPENTELEMETRY_END_NAMESPACE
+#endif
diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/test/CMakeLists.txt b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/test/CMakeLists.txt
new file mode 100644
index 000000000..1a2246979
--- /dev/null
+++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/test/CMakeLists.txt
@@ -0,0 +1,13 @@
+if(WITH_METRICS_PREVIEW)
+ foreach(testname prometheus_exporter_test prometheus_collector_test
+ prometheus_exporter_utils_test)
+ add_executable(${testname} "${testname}.cc")
+ target_link_libraries(
+ ${testname} ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}
+ prometheus_exporter_deprecated prometheus-cpp::pull)
+ gtest_add_tests(
+ TARGET ${testname}
+ TEST_PREFIX exporter.
+ TEST_LIST ${testname})
+ endforeach()
+endif()
diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/test/prometheus_collector_test.cc b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/test/prometheus_collector_test.cc
new file mode 100644
index 000000000..d1aff1b0f
--- /dev/null
+++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/test/prometheus_collector_test.cc
@@ -0,0 +1,756 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+#ifdef ENABLE_METRICS_PREVIEW
+# include <gtest/gtest.h>
+# include <future>
+# include <map>
+# include <thread>
+
+# include "opentelemetry/_metrics/instrument.h"
+# include "opentelemetry/exporters/prometheus/prometheus_collector.h"
+# include "opentelemetry/sdk/_metrics/aggregator/aggregator.h"
+# include "opentelemetry/sdk/_metrics/aggregator/counter_aggregator.h"
+# include "opentelemetry/sdk/_metrics/aggregator/exact_aggregator.h"
+# include "opentelemetry/sdk/_metrics/aggregator/gauge_aggregator.h"
+# include "opentelemetry/sdk/_metrics/aggregator/histogram_aggregator.h"
+# include "opentelemetry/sdk/_metrics/aggregator/min_max_sum_count_aggregator.h"
+# include "opentelemetry/sdk/_metrics/aggregator/sketch_aggregator.h"
+# include "opentelemetry/sdk/_metrics/record.h"
+# include "opentelemetry/version.h"
+
+using opentelemetry::exporter::prometheus::PrometheusCollector;
+namespace metric_api = opentelemetry::metrics;
+namespace metric_sdk = opentelemetry::sdk::metrics;
+
+OPENTELEMETRY_BEGIN_NAMESPACE
+
+/**
+ * CreateAgg() is a helper function that returns a
+ * nostd::shared_ptr given an AggregatorKind
+ */
+template <typename T>
+std::shared_ptr<metric_sdk::Aggregator<T>> CreateAgg(metric_sdk::AggregatorKind kind,
+ bool exactMode = true)
+{
+ std::shared_ptr<metric_sdk::Aggregator<T>> aggregator;
+ switch (kind)
+ {
+ case metric_sdk::AggregatorKind::Counter: {
+ aggregator = std::shared_ptr<metric_sdk::Aggregator<T>>(
+ new metric_sdk::CounterAggregator<T>(metric_api::InstrumentKind::Counter));
+ break;
+ }
+ case metric_sdk::AggregatorKind::MinMaxSumCount: {
+ aggregator = std::shared_ptr<metric_sdk::Aggregator<T>>(
+ new metric_sdk::MinMaxSumCountAggregator<T>(metric_api::InstrumentKind::Counter));
+ break;
+ }
+ case metric_sdk::AggregatorKind::Gauge: {
+ aggregator = std::shared_ptr<metric_sdk::Aggregator<T>>(
+ new metric_sdk::GaugeAggregator<T>(metric_api::InstrumentKind::Counter));
+ break;
+ }
+ case metric_sdk::AggregatorKind::Sketch: {
+ aggregator = std::shared_ptr<metric_sdk::Aggregator<T>>(
+ new metric_sdk::SketchAggregator<T>(metric_api::InstrumentKind::Counter, 0.000005));
+ break;
+ }
+ case metric_sdk::AggregatorKind::Histogram: {
+ std::vector<double> boundaries{10, 20};
+ aggregator = std::shared_ptr<metric_sdk::Aggregator<T>>(
+ new metric_sdk::HistogramAggregator<T>(metric_api::InstrumentKind::Counter, boundaries));
+ break;
+ }
+ case metric_sdk::AggregatorKind::Exact: {
+ aggregator = std::shared_ptr<metric_sdk::Aggregator<T>>(
+ new metric_sdk::ExactAggregator<T>(metric_api::InstrumentKind::Counter, exactMode));
+ break;
+ }
+ default:
+ aggregator = nullptr;
+ }
+ return aggregator;
+}
+
+/**
+ * Populate() updates the aggregator with values and checkpoints it based
+ * on what its AggregatorKind is
+ */
+template <typename T>
+void Populate(std::shared_ptr<metric_sdk::Aggregator<T>> &aggregator)
+{
+ if (aggregator->get_aggregator_kind() == metric_sdk::AggregatorKind::Counter)
+ {
+ aggregator->update(10.0);
+ aggregator->update(5.0);
+ aggregator->checkpoint();
+ }
+ else if (aggregator->get_aggregator_kind() == metric_sdk::AggregatorKind::MinMaxSumCount)
+ {
+ aggregator->update(10);
+ aggregator->update(2);
+ aggregator->update(5);
+ aggregator->checkpoint();
+ }
+ else if (aggregator->get_aggregator_kind() == metric_sdk::AggregatorKind::Gauge)
+ {
+ aggregator->update(10);
+ aggregator->update(5);
+ aggregator->checkpoint();
+ }
+ else if (aggregator->get_aggregator_kind() == metric_sdk::AggregatorKind::Sketch)
+ {
+ for (double i = 0; i < 10.0; i++)
+ {
+ aggregator->update(i);
+ }
+ aggregator->checkpoint();
+ }
+ else if (aggregator->get_aggregator_kind() == metric_sdk::AggregatorKind::Histogram)
+ {
+ for (float i = 0; i < 30.0; i++)
+ {
+ aggregator->update(i);
+ }
+ aggregator->checkpoint();
+ }
+ else if (aggregator->get_aggregator_kind() == metric_sdk::AggregatorKind::Exact)
+ {
+ for (double i = 0; i < 10.0; i++)
+ {
+ aggregator->update(i);
+ }
+ aggregator->checkpoint();
+ }
+}
+
+/**
+ * Helper function to create a collection of records taken from
+ * a aggregator of specified AggregatorKind
+ */
+template <typename T>
+std::vector<metric_sdk::Record> CreateRecords(int num,
+ metric_sdk::AggregatorKind kind,
+ bool exactMode = true)
+{
+ std::vector<metric_sdk::Record> records;
+
+ for (int i = 0; i < num; i++)
+ {
+ std::string name = "record-" + std::to_string(i);
+ std::string description = "record " + std::to_string(i) + " for test purpose";
+ std::string labels = "{label1:v1,label2:v2,}";
+ std::shared_ptr<metric_sdk::Aggregator<T>> aggregator = CreateAgg<T>(kind, exactMode);
+ Populate(aggregator);
+
+ metric_sdk::Record r{name, description, labels, aggregator};
+ records.push_back(r);
+ }
+ return records;
+}
+
+// ==================== Test for addMetricsData() function ======================
+
+/**
+ * AddMetricData() should be able to successfully add a collection
+ * of Records with Counter Aggregators. 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;
+
+ // number of records to create
+ int num_records = 2;
+
+ // construct a collection of records with CounterAggregators and double
+ std::vector<metric_sdk::Record> records =
+ CreateRecords<double>(num_records, metric_sdk::AggregatorKind::Counter);
+
+ // add records to collection
+ collector.AddMetricData(records);
+
+ // Collection size should be the same as the size
+ // of the records collection passed to addMetricData()
+ ASSERT_EQ(collector.GetCollection().size(), records.size());
+
+ // check values of records created vs records from metricsToCollect,
+ // accessed by getCollection()
+
+ for (int i = 0; i < num_records; i++)
+ {
+ metric_sdk::Record before = records[i];
+ metric_sdk::Record after = collector.GetCollection()[i];
+
+ ASSERT_EQ(before.GetName(), after.GetName());
+
+ ASSERT_EQ(before.GetDescription(), after.GetDescription());
+
+ ASSERT_EQ(before.GetLabels(), after.GetLabels());
+
+ auto before_agg_var = before.GetAggregator();
+ auto before_agg = nostd::get<std::shared_ptr<metric_sdk::Aggregator<double>>>(before_agg_var);
+
+ auto after_agg_var = after.GetAggregator();
+ auto after_agg = nostd::get<std::shared_ptr<metric_sdk::Aggregator<double>>>(after_agg_var);
+
+ ASSERT_EQ(before_agg->get_checkpoint().size(), after_agg->get_checkpoint().size());
+ for (size_t i = 0; i < before_agg->get_checkpoint().size(); i++)
+ {
+ ASSERT_EQ(before_agg->get_checkpoint()[i], after_agg->get_checkpoint()[i]);
+ }
+ }
+}
+
+/**
+ * AddMetricData() should be able to successfully add a collection
+ * of Records with MinMaxSumCount Aggregators. It checks that the min, max,
+ * sum, and count of updates to the aggregator of a record before and after AddMetricData()
+ * is called are equal.
+ */
+TEST(PrometheusCollector, AddMetricDataWithMinMaxSumCountRecordsSuccessfully)
+{
+ PrometheusCollector collector;
+
+ // number of records to create
+ int num_records = 2;
+
+ // construct a collection of records with MinMaxSumCountAggregators and short
+ std::vector<metric_sdk::Record> records =
+ CreateRecords<short>(num_records, metric_sdk::AggregatorKind::MinMaxSumCount);
+
+ // add records to collection
+ collector.AddMetricData(records);
+
+ // Collection size should be the same as the size
+ // of the records collection passed to addMetricData()
+ ASSERT_EQ(collector.GetCollection().size(), records.size());
+
+ // check values of records created vs records from metricsToCollect,
+ // accessed by getCollection()
+
+ for (int i = 0; i < num_records; i++)
+ {
+ metric_sdk::Record before = records[i];
+ metric_sdk::Record after = collector.GetCollection()[i];
+
+ ASSERT_EQ(before.GetName(), after.GetName());
+
+ ASSERT_EQ(before.GetDescription(), after.GetDescription());
+
+ ASSERT_EQ(before.GetLabels(), after.GetLabels());
+
+ auto before_agg_var = before.GetAggregator();
+ auto before_agg = nostd::get<std::shared_ptr<metric_sdk::Aggregator<short>>>(before_agg_var);
+
+ auto after_agg_var = after.GetAggregator();
+ auto after_agg = nostd::get<std::shared_ptr<metric_sdk::Aggregator<short>>>(after_agg_var);
+
+ ASSERT_EQ(before_agg->get_checkpoint().size(), after_agg->get_checkpoint().size());
+ for (size_t i = 0; i < before_agg->get_checkpoint().size(); i++)
+ {
+ ASSERT_EQ(before_agg->get_checkpoint()[i], after_agg->get_checkpoint()[i]);
+ }
+ }
+}
+
+/**
+ * AddMetricData() should be able to successfully add a collection
+ * of Records with Gauge Aggregators. It checks that the last update
+ * to the aggregator of a record before and after AddMetricData()
+ * is called are equal.
+ */
+TEST(PrometheusCollector, AddMetricDataWithGaugeRecordsSuccessfully)
+{
+ PrometheusCollector collector;
+
+ // number of records to create
+ int num_records = 2;
+
+ // construct a collection of records with GaugeAggregators and int
+ std::vector<metric_sdk::Record> records =
+ CreateRecords<int>(num_records, metric_sdk::AggregatorKind::Gauge);
+
+ // add records to collection
+ collector.AddMetricData(records);
+
+ // Collection size should be the same as the size
+ // of the records collection passed to addMetricData()
+ ASSERT_EQ(collector.GetCollection().size(), records.size());
+
+ // check values of records created vs records from metricsToCollect,
+ // accessed by getCollection()
+
+ for (int i = 0; i < num_records; i++)
+ {
+ metric_sdk::Record before = records[i];
+ metric_sdk::Record after = collector.GetCollection()[i];
+
+ ASSERT_EQ(before.GetName(), after.GetName());
+
+ ASSERT_EQ(before.GetDescription(), after.GetDescription());
+
+ ASSERT_EQ(before.GetLabels(), after.GetLabels());
+
+ auto before_agg_var = before.GetAggregator();
+ auto before_agg = nostd::get<std::shared_ptr<metric_sdk::Aggregator<int>>>(before_agg_var);
+
+ auto after_agg_var = after.GetAggregator();
+ auto after_agg = nostd::get<std::shared_ptr<metric_sdk::Aggregator<int>>>(after_agg_var);
+
+ ASSERT_EQ(before_agg->get_checkpoint().size(), after_agg->get_checkpoint().size());
+ for (size_t i = 0; i < before_agg->get_checkpoint().size(); i++)
+ {
+ ASSERT_EQ(before_agg->get_checkpoint()[i], after_agg->get_checkpoint()[i]);
+ }
+ }
+}
+
+/**
+ * AddMetricData() should be able to successfully add a collection
+ * of Records with Sketch Aggregators. It checks that the sum of updates
+ * and count of values added for a record before and after being added are
+ * equal using get_checkpoint(). It also checks the same for buckets, in
+ * get_boundaries(), and counts for buckets, in get_counts().
+ */
+TEST(PrometheusCollector, AddMetricDataWithSketchRecordsSuccessfully)
+{
+ PrometheusCollector collector;
+
+ // number of records to create
+ int num_records = 2;
+
+ // construct a collection of records with SketchAggregators and double
+ std::vector<metric_sdk::Record> records =
+ CreateRecords<double>(num_records, metric_sdk::AggregatorKind::Sketch);
+
+ // add records to collection
+ collector.AddMetricData(records);
+
+ // Collection size should be the same as the size
+ // of the records collection passed to addMetricData()
+ ASSERT_EQ(collector.GetCollection().size(), records.size());
+
+ // check values of records created vs records from metricsToCollect,
+ // accessed by getCollection()
+
+ for (int i = 0; i < num_records; i++)
+ {
+ metric_sdk::Record before = records[i];
+ metric_sdk::Record after = collector.GetCollection()[i];
+
+ ASSERT_EQ(before.GetName(), after.GetName());
+
+ ASSERT_EQ(before.GetDescription(), after.GetDescription());
+
+ ASSERT_EQ(before.GetLabels(), after.GetLabels());
+
+ auto before_agg_var = before.GetAggregator();
+ auto before_agg = nostd::get<std::shared_ptr<metric_sdk::Aggregator<double>>>(before_agg_var);
+
+ auto after_agg_var = after.GetAggregator();
+ auto after_agg = nostd::get<std::shared_ptr<metric_sdk::Aggregator<double>>>(after_agg_var);
+
+ ASSERT_EQ(before_agg->get_checkpoint().size(), after_agg->get_checkpoint().size());
+ for (size_t i = 0; i < before_agg->get_checkpoint().size(); i++)
+ {
+ ASSERT_EQ(before_agg->get_checkpoint()[i], after_agg->get_checkpoint()[i]);
+ }
+ for (size_t i = 0; i < before_agg->get_boundaries().size(); i++)
+ {
+ ASSERT_EQ(before_agg->get_boundaries()[i], after_agg->get_boundaries()[i]);
+ }
+ for (size_t i = 0; i < before_agg->get_counts().size(); i++)
+ {
+ ASSERT_EQ(before_agg->get_counts()[i], after_agg->get_counts()[i]);
+ }
+ }
+}
+
+/**
+ * AddMetricData() should be able to successfully add a collection
+ * of Records with Histogram Aggregators. It checks that the sum of
+ * updates, number of updates, boundaries, and counts for each bucket
+ * for the aggregator of a record before and after AddMetricData()
+ * is called are equal.
+ */
+TEST(PrometheusCollector, AddMetricDataWithHistogramRecordsSuccessfully)
+{
+ PrometheusCollector collector;
+
+ // number of records to create
+ int num_records = 2;
+
+ // construct a collection of records with HistogramAggregators and float
+ std::vector<metric_sdk::Record> records =
+ CreateRecords<float>(num_records, metric_sdk::AggregatorKind::Histogram);
+
+ // add records to collection
+ collector.AddMetricData(records);
+
+ // Collection size should be the same as the size
+ // of the records collection passed to addMetricData()
+ ASSERT_EQ(collector.GetCollection().size(), records.size());
+
+ // check values of records created vs records from metricsToCollect,
+ // accessed by getCollection()
+
+ for (int i = 0; i < num_records; i++)
+ {
+ metric_sdk::Record before = records[i];
+ metric_sdk::Record after = collector.GetCollection()[i];
+
+ ASSERT_EQ(before.GetName(), after.GetName());
+
+ ASSERT_EQ(before.GetDescription(), after.GetDescription());
+
+ ASSERT_EQ(before.GetLabels(), after.GetLabels());
+
+ auto before_agg_var = before.GetAggregator();
+ auto before_agg = nostd::get<std::shared_ptr<metric_sdk::Aggregator<float>>>(before_agg_var);
+
+ auto after_agg_var = after.GetAggregator();
+ auto after_agg = nostd::get<std::shared_ptr<metric_sdk::Aggregator<float>>>(after_agg_var);
+
+ ASSERT_EQ(before_agg->get_checkpoint().size(), after_agg->get_checkpoint().size());
+ for (size_t i = 0; i < before_agg->get_checkpoint().size(); i++)
+ {
+ ASSERT_EQ(before_agg->get_checkpoint()[i], after_agg->get_checkpoint()[i]);
+ }
+ for (size_t i = 0; i < before_agg->get_boundaries().size(); i++)
+ {
+ ASSERT_EQ(before_agg->get_boundaries()[i], after_agg->get_boundaries()[i]);
+ }
+ for (size_t i = 0; i < before_agg->get_counts().size(); i++)
+ {
+ ASSERT_EQ(before_agg->get_counts()[i], after_agg->get_counts()[i]);
+ }
+ }
+}
+
+/**
+ * AddMetricData() should be able to successfully add a collection
+ * of Records with Exact Aggregators. If the Exact Aggregator is in
+ * quantile mode, it will check quantiles at selected values of 0, 0.25,
+ * 0.5, 0.75, and 1. If not, it will check the vector of checkpointed
+ * values in get_checkpoint().
+ */
+TEST(PrometheusCollector, AddMetricDataWithExactRecordsSuccessfully)
+{
+ PrometheusCollector collector;
+
+ // number of records to create
+ int num_records = 1;
+
+ // construct a collection of a single record with a quantile
+ // estimation ExactAggregator and double
+ std::vector<metric_sdk::Record> records =
+ CreateRecords<double>(num_records, metric_sdk::AggregatorKind::Exact, true);
+
+ // add records to collection
+ collector.AddMetricData(records);
+
+ // construct a collection of a single record with an in-order
+ // ExactAggregator and double
+ records = CreateRecords<double>(num_records, metric_sdk::AggregatorKind::Exact, false);
+
+ // add records to collection
+ collector.AddMetricData(records);
+
+ // Collection size should be the same as the size
+ // of the records collection passed to addMetricData()
+ ASSERT_EQ(collector.GetCollection().size(), records.size() * 2);
+
+ // check values of records created vs records from metricsToCollect,
+ // accessed by getCollection()
+
+ for (int i = 0; i < num_records; i++)
+ {
+ metric_sdk::Record before = records[i];
+ metric_sdk::Record after = collector.GetCollection()[i];
+
+ ASSERT_EQ(before.GetName(), after.GetName());
+
+ ASSERT_EQ(before.GetDescription(), after.GetDescription());
+
+ ASSERT_EQ(before.GetLabels(), after.GetLabels());
+
+ auto before_agg_var = before.GetAggregator();
+ auto before_agg = nostd::get<std::shared_ptr<metric_sdk::Aggregator<double>>>(before_agg_var);
+
+ auto after_agg_var = after.GetAggregator();
+ auto after_agg = nostd::get<std::shared_ptr<metric_sdk::Aggregator<double>>>(after_agg_var);
+
+ if (before_agg->get_quant_estimation() && after_agg->get_quant_estimation())
+ {
+ for (double i = 0; i <= 1;)
+ {
+ ASSERT_EQ(before_agg->get_quantiles(i), after_agg->get_quantiles(i));
+ i += 0.25;
+ }
+ }
+ else
+ {
+ ASSERT_EQ(before_agg->get_checkpoint().size(), after_agg->get_checkpoint().size());
+ for (size_t i = 0; i < before_agg->get_checkpoint().size(); i++)
+ {
+ ASSERT_EQ(before_agg->get_checkpoint()[i], after_agg->get_checkpoint()[i]);
+ }
+ }
+ }
+}
+
+TEST(PrometheusCollector, AddMetricDataDoesNotAddWithInsufficentSpace)
+{
+ PrometheusCollector collector;
+
+ // number of records to create
+ int num_records = collector.GetMaxCollectionSize() - 5;
+
+ // construct a collection close to max capacity
+ std::vector<metric_sdk::Record> records =
+ CreateRecords<double>(num_records, metric_sdk::AggregatorKind::Counter);
+
+ collector.AddMetricData(records);
+
+ // Check if all the records have been added
+ ASSERT_EQ(collector.GetCollection().size(), num_records);
+
+ // Try adding the same collection of records again to
+ // metricsToCollect.
+ collector.AddMetricData(records);
+
+ // Check that the number of records in metricsToCollect
+ // has not changed.
+ ASSERT_EQ(collector.GetCollection().size(), num_records);
+}
+
+TEST(PrometheusCollector, AddMetricDataDoesNotAddBadIndividualRecords)
+{
+ PrometheusCollector collector;
+
+ // number of records to create
+ int num_records = 5;
+
+ // construct a collection with the specified number of records
+ std::vector<metric_sdk::Record> records =
+ CreateRecords<double>(num_records, metric_sdk::AggregatorKind::Counter);
+
+ // add records to collection
+ collector.AddMetricData(records);
+
+ // Check if all the records have been added
+ ASSERT_EQ(collector.GetCollection().size(), num_records);
+
+ // Creates a bad record, with a nullptr aggregator and adds
+ // it to the colelction of records
+ std::string name = "bad_record";
+ std::string description = "nullptr_agg";
+ std::string labels = "{label1:v1}";
+ std::shared_ptr<metric_sdk::Aggregator<int>> aggregator;
+ metric_sdk::Record bad_record{name, description, labels, aggregator};
+
+ records.push_back(bad_record);
+
+ // add records to collection
+ collector.AddMetricData(records);
+
+ // Check if all the records except the bad
+ // record have been added; the number of records added
+ // should be twice the original number of records
+ // epecified to be created
+ ASSERT_EQ(collector.GetCollection().size(), num_records * 2);
+}
+
+// ==================== 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
+ int num_records = 2;
+ auto records = CreateRecords<int>(num_records, metric_sdk::AggregatorKind::Counter);
+ collector.AddMetricData(records);
+
+ // the collection should not be empty now
+ ASSERT_EQ(collector.GetCollection().size(), num_records);
+
+ // 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;
+
+ // construct a collection to add metric records
+ int num_records = 1;
+ auto records = CreateRecords<int>(num_records, metric_sdk::AggregatorKind::Counter);
+ collector.AddMetricData(records);
+
+ // the collection should not be empty now
+ ASSERT_EQ(collector.GetCollection().size(), num_records);
+ auto collected = collector.Collect();
+
+ ASSERT_EQ(collected.size(), num_records);
+
+ auto metric_family = collected[0];
+
+ // Collect function really collects a vector of MetricFamily
+ ASSERT_EQ(metric_family.name, "record_0");
+ ASSERT_EQ(metric_family.help, "record 0 for test purpose");
+ ASSERT_EQ(metric_family.type, prometheus_client::MetricType::Counter);
+ ASSERT_EQ(metric_family.metric.size(), 1);
+ ASSERT_DOUBLE_EQ(metric_family.metric[0].counter.value, 15);
+}
+
+/**
+ * Concurrency Test 1: After adding data concurrently, the intermediate collection should
+ * contain all data from all threads.
+ */
+TEST(PrometheusCollector, ConcurrencyAddingRecords)
+{
+ PrometheusCollector collector;
+
+ // construct a collection to add metric records
+ int num_records = 2;
+ std::vector<metric_sdk::Record> records1 =
+ CreateRecords<int>(num_records, metric_sdk::AggregatorKind::Counter);
+
+ std::vector<metric_sdk::Record> records2 =
+ CreateRecords<int>(num_records, metric_sdk::AggregatorKind::Gauge);
+
+ std::thread first(&PrometheusCollector::AddMetricData, std::ref(collector), std::ref(records1));
+ std::thread second(&PrometheusCollector::AddMetricData, std::ref(collector), std::ref(records2));
+
+ first.join();
+ second.join();
+
+ ASSERT_EQ(collector.GetCollection().size(), 4);
+}
+
+/**
+ * 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;
+
+ // construct a collection to add metric records
+ int num_records = 2;
+ std::vector<metric_sdk::Record> records1 =
+ CreateRecords<int>(num_records, metric_sdk::AggregatorKind::Counter);
+
+ std::vector<metric_sdk::Record> records2 =
+ CreateRecords<int>(num_records, metric_sdk::AggregatorKind::Gauge);
+
+ std::thread first(&PrometheusCollector::AddMetricData, std::ref(collector), std::ref(records1));
+ std::thread second(&PrometheusCollector::AddMetricData, std::ref(collector), std::ref(records2));
+ 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(), 4);
+}
+
+/**
+ * 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
+ int num_records = 2;
+ std::vector<metric_sdk::Record> records1 =
+ CreateRecords<int>(num_records, metric_sdk::AggregatorKind::Counter);
+
+ std::vector<metric_sdk::Record> records2 =
+ CreateRecords<int>(num_records, metric_sdk::AggregatorKind::Gauge);
+
+ std::thread first(&PrometheusCollector::AddMetricData, std::ref(collector), std::ref(records1));
+ std::thread second(&PrometheusCollector::AddMetricData, std::ref(collector), std::ref(records2));
+ 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(), 4);
+}
+
+/**
+ * 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;
+
+ // construct a collection to add metric records
+ int num_records = 2;
+ std::vector<metric_sdk::Record> records1 =
+ CreateRecords<int>(num_records, metric_sdk::AggregatorKind::Counter);
+
+ std::vector<metric_sdk::Record> records2 =
+ CreateRecords<int>(num_records, metric_sdk::AggregatorKind::Gauge);
+
+ // concurrently adding
+ std::thread first(&PrometheusCollector::AddMetricData, std::ref(collector), std::ref(records1));
+ std::thread second(&PrometheusCollector::AddMetricData, std::ref(collector), std::ref(records2));
+ 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(), 4);
+}
+
+OPENTELEMETRY_END_NAMESPACE
+#endif
diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/test/prometheus_exporter_test.cc b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/test/prometheus_exporter_test.cc
new file mode 100644
index 000000000..564880c26
--- /dev/null
+++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/test/prometheus_exporter_test.cc
@@ -0,0 +1,218 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifdef ENABLE_METRICS_PREVIEW
+
+# include <gtest/gtest.h>
+# include <typeinfo>
+
+# include "opentelemetry/exporters/prometheus/prometheus_collector.h"
+# include "opentelemetry/exporters/prometheus/prometheus_exporter.h"
+# include "opentelemetry/sdk/_metrics/aggregator/counter_aggregator.h"
+# include "opentelemetry/version.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 prometheus
+{
+class PrometheusExporterTest // : public ::testing::Test
+{
+public:
+ PrometheusExporter GetExporter() { return PrometheusExporter(); }
+};
+} // namespace prometheus
+} // namespace exporter
+OPENTELEMETRY_END_NAMESPACE
+
+using opentelemetry::exporter::prometheus::PrometheusCollector;
+using opentelemetry::exporter::prometheus::PrometheusExporter;
+using opentelemetry::exporter::prometheus::PrometheusExporterTest;
+using opentelemetry::sdk::common::ExportResult;
+using opentelemetry::sdk::metrics::CounterAggregator;
+using opentelemetry::sdk::metrics::Record;
+
+/**
+ * Helper function to create a collection of records taken from
+ * a counter aggregator
+ */
+std::vector<Record> CreateRecords(int num)
+{
+
+ std::vector<Record> records;
+
+ for (int i = 0; i < num; i++)
+ {
+ std::string name = "record-" + std::to_string(i);
+ std::string description = "record-" + std::to_string(i);
+ std::string labels = "record-" + std::to_string(i) + "-label-1.0";
+ auto aggregator = std::shared_ptr<opentelemetry::sdk::metrics::Aggregator<int>>(
+ new opentelemetry::sdk::metrics::CounterAggregator<int>(
+ opentelemetry::metrics::InstrumentKind::Counter));
+ aggregator->update(10);
+ aggregator->checkpoint();
+
+ Record r{name, description, labels, aggregator};
+ records.push_back(r);
+ }
+ return records;
+}
+
+/**
+ * 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();
+
+ int num_records = 2;
+
+ std::vector<Record> records = CreateRecords(num_records);
+
+ auto res = exporter.Export(records);
+
+ // 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();
+
+ int num_records = 1;
+
+ std::vector<Record> records = CreateRecords(num_records);
+
+ exporter.Shutdown();
+
+ // send export request after shutdown
+ auto res = exporter.Export(records);
+
+ // 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();
+
+ int num_records = 2;
+
+ // 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();
+
+ std::vector<Record> full_records = CreateRecords(max_collection_size - 1);
+ std::vector<Record> records = CreateRecords(num_records);
+
+ // send export request to fill the
+ // collection in the collector
+ auto res = exporter.Export(full_records);
+
+ // the result code should be kSuccess = 0
+ ExportResult code = ExportResult::kSuccess;
+ ASSERT_EQ(res, code);
+
+ // send export request that does not complete
+ // due to not enough space in the collection
+ res = exporter.Export(records);
+
+ // 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
+ std::vector<Record> records;
+
+ // send export request to fill the
+ // collection in the collector
+ auto res = exporter.Export(records);
+
+ // the result code should be kFailureInvalidArgument = 3
+ ExportResult code = ExportResult::kFailureInvalidArgument;
+ ASSERT_EQ(res, code);
+}
+
+#endif // ENABLE_METRICS_PREVIEW
diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/test/prometheus_exporter_utils_test.cc b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/test/prometheus_exporter_utils_test.cc
new file mode 100644
index 000000000..22ce3f5fd
--- /dev/null
+++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/test/prometheus_exporter_utils_test.cc
@@ -0,0 +1,460 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+#ifdef ENABLE_METRICS_PREVIEW
+# include <gtest/gtest.h>
+# include <map>
+# include <numeric>
+# include <string>
+# include <typeinfo>
+
+# include <opentelemetry/version.h>
+# include "opentelemetry/exporters/prometheus/prometheus_exporter_utils.h"
+# include "opentelemetry/sdk/_metrics/aggregator/counter_aggregator.h"
+# include "opentelemetry/sdk/_metrics/aggregator/exact_aggregator.h"
+# include "opentelemetry/sdk/_metrics/aggregator/gauge_aggregator.h"
+# include "opentelemetry/sdk/_metrics/aggregator/histogram_aggregator.h"
+# include "opentelemetry/sdk/_metrics/aggregator/min_max_sum_count_aggregator.h"
+# include "opentelemetry/sdk/_metrics/aggregator/sketch_aggregator.h"
+
+using opentelemetry::exporter::prometheus::PrometheusExporterUtils;
+namespace metric_sdk = opentelemetry::sdk::metrics;
+namespace metric_api = opentelemetry::metrics;
+namespace prometheus_client = ::prometheus;
+
+OPENTELEMETRY_BEGIN_NAMESPACE
+template <typename T>
+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<T> 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::Gauge: {
+ ASSERT_EQ(metric_data.gauge.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::Summary: {
+ ASSERT_DOUBLE_EQ(metric_data.summary.sample_count, vals[0]);
+ ASSERT_DOUBLE_EQ(metric_data.summary.sample_sum, vals[1]);
+ break;
+ }
+ case prometheus::MetricType::Untyped:
+ break;
+ }
+}
+
+void assert_histogram(prometheus_client::MetricFamily &metric,
+ std::vector<double> boundaries,
+ std::vector<int> correct)
+{
+ int cumulative_count = 0;
+ auto buckets = metric.metric[0].histogram.bucket;
+ for (size_t i = 0; i < buckets.size(); i++)
+ {
+ auto bucket = buckets[i];
+ if (i != buckets.size() - 1)
+ {
+ ASSERT_DOUBLE_EQ(boundaries[i], bucket.upper_bound);
+ }
+ else
+ {
+ ASSERT_DOUBLE_EQ(std::numeric_limits<double>::infinity(), bucket.upper_bound);
+ }
+ cumulative_count += correct[i];
+ ASSERT_EQ(cumulative_count, bucket.cumulative_count);
+ }
+}
+
+template <typename T>
+metric_sdk::Record get_record(const std::string &type,
+ int version,
+ const std::string &label,
+ std::shared_ptr<metric_sdk::Aggregator<T>> aggregator)
+{
+ std::string name = "test-" + type + "-metric-record-v_" + std::to_string(version) + ".0";
+ std::string desc = "this is a test " + type + " metric record";
+ metric_sdk::Record record(name, desc, label, aggregator);
+ return record;
+}
+
+TEST(PrometheusExporterUtils, TranslateToPrometheusEmptyInputReturnsEmptyCollection)
+{
+ std::vector<metric_sdk::Record> collection;
+ auto translated2 = PrometheusExporterUtils::TranslateToPrometheus(collection);
+ ASSERT_EQ(translated2.size(), 0);
+}
+
+TEST(PrometheusExporterUtils, TranslateToPrometheusIntegerCounter)
+{
+ auto aggregator = std::shared_ptr<metric_sdk::Aggregator<int>>(
+ new metric_sdk::CounterAggregator<int>(metric_api::InstrumentKind::Counter));
+
+ std::vector<metric_sdk::Record> collection;
+
+ auto record1 = get_record("int-counter", 1, "{label1:v1,label2:v2,label3:v3,}", aggregator);
+ aggregator->update(10);
+ aggregator->checkpoint();
+ collection.emplace_back(record1);
+
+ auto translated = PrometheusExporterUtils::TranslateToPrometheus(collection);
+ ASSERT_EQ(translated.size(), collection.size());
+
+ auto metric1 = translated[0];
+ std::vector<int> vals = {10};
+ assert_basic(metric1, "test_int_counter_metric_record_v_1_0", record1.GetDescription(),
+ prometheus_client::MetricType::Counter, 3, vals);
+
+ auto record2 = get_record("int-counter", 2, "{,}", aggregator);
+ aggregator->update(20);
+ aggregator->update(30);
+ aggregator->checkpoint();
+ collection.emplace_back(record2);
+
+ translated = PrometheusExporterUtils::TranslateToPrometheus(collection);
+ ASSERT_EQ(translated.size(), collection.size());
+
+ auto metric2 = translated[1];
+ vals = {50};
+ assert_basic(metric2, "test_int_counter_metric_record_v_2_0", record2.GetDescription(),
+ prometheus_client::MetricType::Counter, 0, vals);
+}
+
+TEST(PrometheusExporterUtils, TranslateToPrometheusDoubleCounter)
+{
+ auto aggregator = std::shared_ptr<metric_sdk::Aggregator<double>>(
+ new metric_sdk::CounterAggregator<double>(metric_api::InstrumentKind::Counter));
+
+ std::vector<metric_sdk::Record> collection;
+ aggregator->update(10.5);
+ aggregator->checkpoint();
+ auto record1 = get_record("double-counter", 1, "{label1:v1,label2:v2,label3:v3,}", aggregator);
+ aggregator->update(22.4);
+ aggregator->update(31.2);
+ aggregator->checkpoint();
+ auto record2 = get_record("double-counter", 2, "{,}", aggregator);
+ collection.emplace_back(record1);
+ collection.emplace_back(record2);
+
+ auto translated = PrometheusExporterUtils::TranslateToPrometheus(collection);
+ ASSERT_EQ(translated.size(), collection.size());
+
+ auto metric1 = translated[0];
+ std::vector<double> vals = {53.6};
+ assert_basic(metric1, "test_double_counter_metric_record_v_1_0", record1.GetDescription(),
+ prometheus_client::MetricType::Counter, 3, vals);
+ auto metric2 = translated[1];
+ assert_basic(metric2, "test_double_counter_metric_record_v_2_0", record2.GetDescription(),
+ prometheus_client::MetricType::Counter, 0, vals);
+}
+
+TEST(PrometheusExporterUtils, TranslateToPrometheusShortCounter)
+{
+ auto aggregator = std::shared_ptr<metric_sdk::Aggregator<short>>(
+ new metric_sdk::CounterAggregator<short>(metric_api::InstrumentKind::Counter));
+
+ std::vector<metric_sdk::Record> collection;
+ aggregator->update(10);
+ aggregator->checkpoint();
+ auto record1 = get_record("short-counter", 1, "{label1:v1,label2:v2,label3:v3,}", aggregator);
+ aggregator->update(20);
+ aggregator->update(30);
+ aggregator->checkpoint();
+ auto record2 = get_record("short-counter", 2, "{,}", aggregator);
+ collection.emplace_back(record1);
+ collection.emplace_back(record2);
+
+ auto translated = PrometheusExporterUtils::TranslateToPrometheus(collection);
+ ASSERT_EQ(translated.size(), collection.size());
+
+ auto metric1 = translated[0];
+ std::vector<short> vals = {50};
+ assert_basic(metric1, "test_short_counter_metric_record_v_1_0", record1.GetDescription(),
+ prometheus_client::MetricType::Counter, 3, vals);
+ auto metric2 = translated[1];
+ assert_basic(metric2, "test_short_counter_metric_record_v_2_0", record2.GetDescription(),
+ prometheus_client::MetricType::Counter, 0, vals);
+}
+
+TEST(PrometheusExporterUtils, TranslateToPrometheusFloatCounter)
+{
+ auto aggregator = std::shared_ptr<metric_sdk::Aggregator<float>>(
+ new metric_sdk::CounterAggregator<float>(metric_api::InstrumentKind::Counter));
+
+ std::vector<metric_sdk::Record> collection;
+ aggregator->update(10.5f);
+ aggregator->checkpoint();
+ auto record1 = get_record("float-counter", 1, "{label1:v1,label2:v2,label3:v3,}", aggregator);
+ aggregator->update(22.4f);
+ aggregator->update(31.2f);
+ aggregator->checkpoint();
+ auto record2 = get_record("float-counter", 2, "{,}", aggregator);
+ collection.emplace_back(record1);
+ collection.emplace_back(record2);
+
+ auto translated = PrometheusExporterUtils::TranslateToPrometheus(collection);
+ ASSERT_EQ(translated.size(), collection.size());
+
+ auto metric1 = translated[0];
+ std::vector<float> vals = {53.6f};
+ assert_basic(metric1, "test_float_counter_metric_record_v_1_0", record1.GetDescription(),
+ prometheus_client::MetricType::Counter, 3, vals);
+ auto metric2 = translated[1];
+ assert_basic(metric2, "test_float_counter_metric_record_v_2_0", record2.GetDescription(),
+ prometheus_client::MetricType::Counter, 0, vals);
+}
+
+TEST(PrometheusExporterUtils, TranslateToPrometheusGauge)
+{
+ auto aggregator = std::shared_ptr<metric_sdk::Aggregator<int>>(
+ new metric_sdk::GaugeAggregator<int>(metric_api::InstrumentKind::Counter));
+
+ std::vector<metric_sdk::Record> collection;
+ aggregator->update(10);
+ aggregator->checkpoint();
+ auto record1 = get_record("gauge", 1, "{label1:v1,label2:v2,label3:v3,}", aggregator);
+ aggregator->update(20);
+ aggregator->update(30);
+ aggregator->checkpoint();
+ auto record2 = get_record("gauge", 2, "{,}", aggregator);
+ collection.emplace_back(record1);
+ collection.emplace_back(record2);
+
+ auto translated = PrometheusExporterUtils::TranslateToPrometheus(collection);
+ ASSERT_EQ(translated.size(), collection.size());
+
+ auto metric1 = translated[0];
+ std::vector<int> vals = {30};
+ assert_basic(metric1, "test_gauge_metric_record_v_1_0", record1.GetDescription(),
+ prometheus_client::MetricType::Gauge, 3, vals);
+ auto metric2 = translated[1];
+ assert_basic(metric2, "test_gauge_metric_record_v_2_0", record2.GetDescription(),
+ prometheus_client::MetricType::Gauge, 0, vals);
+}
+
+TEST(PrometheusExporterUtils, TranslateToPrometheusHistogramUniform)
+{
+ std::vector<double> boundaries{10, 20, 30, 40, 50};
+ auto aggregator = std::shared_ptr<metric_sdk::Aggregator<int>>(
+ new metric_sdk::HistogramAggregator<int>(metric_api::InstrumentKind::Counter, boundaries));
+
+ std::vector<metric_sdk::Record> collection;
+ auto record = get_record("histogram-uniform", 1, "{label1:v1,label2:v2,label3:v3,}", aggregator);
+ int count_num = 60;
+ for (int i = 0; i < count_num; i++)
+ {
+ aggregator->update(i);
+ }
+ aggregator->checkpoint();
+ collection.emplace_back(record);
+
+ auto translated = PrometheusExporterUtils::TranslateToPrometheus(collection);
+ ASSERT_EQ(translated.size(), collection.size());
+
+ auto metric = translated[0];
+ std::vector<int> vals = {aggregator->get_checkpoint()[1], aggregator->get_checkpoint()[0],
+ (int)boundaries.size() + 1};
+ assert_basic(metric, "test_histogram_uniform_metric_record_v_1_0", record.GetDescription(),
+ prometheus_client::MetricType::Histogram, 3, vals);
+ std::vector<int> correct = aggregator->get_counts();
+ assert_histogram(metric, boundaries, correct);
+}
+
+TEST(PrometheusExporterUtils, TranslateToPrometheusHistogramNormal)
+{
+ std::vector<double> boundaries{2, 4, 6, 8, 10, 12};
+ auto aggregator = std::shared_ptr<metric_sdk::Aggregator<int>>(
+ new metric_sdk::HistogramAggregator<int>(metric_api::InstrumentKind::Counter, boundaries));
+
+ std::vector<metric_sdk::Record> collection;
+ auto record = get_record("histogram-normal", 1, "{label1:v1,label2:v2,label3:v3,}", aggregator);
+ std::vector<int> values{1, 3, 3, 5, 5, 5, 7, 7, 7, 7, 9, 9, 9, 11, 11, 13};
+ for (int i : values)
+ {
+ aggregator->update(i);
+ }
+ aggregator->checkpoint();
+ collection.emplace_back(record);
+
+ auto translated = PrometheusExporterUtils::TranslateToPrometheus(collection);
+ ASSERT_EQ(translated.size(), collection.size());
+
+ auto metric = translated[0];
+ std::vector<int> vals = {aggregator->get_checkpoint()[1], aggregator->get_checkpoint()[0],
+ (int)boundaries.size() + 1};
+ assert_basic(metric, "test_histogram_normal_metric_record_v_1_0", record.GetDescription(),
+ prometheus_client::MetricType::Histogram, 3, vals);
+ std::vector<int> correct = aggregator->get_counts();
+ assert_histogram(metric, boundaries, correct);
+}
+
+TEST(PrometheusExporterUtils, TranslateToPrometheusExact)
+{
+ auto aggregator = std::shared_ptr<metric_sdk::Aggregator<int>>(
+ new metric_sdk::ExactAggregator<int>(metric_api::InstrumentKind::Counter, true));
+
+ std::vector<metric_sdk::Record> collection;
+ int count_num = 100;
+ for (int i = 0; i <= count_num; i++)
+ {
+ aggregator->update(i);
+ }
+ aggregator->checkpoint();
+ auto record = get_record("exact", 1, "{label-1:v1,label_2:v2,label3:v3,}", aggregator);
+ collection.emplace_back(record);
+
+ auto translated = PrometheusExporterUtils::TranslateToPrometheus(collection);
+ ASSERT_EQ(translated.size(), collection.size());
+
+ auto metric = translated[0];
+ std::vector<int> vals = {101, 5050};
+ assert_basic(metric, "test_exact_metric_record_v_1_0", record.GetDescription(),
+ prometheus_client::MetricType::Summary, 3, vals);
+ auto quantile = metric.metric[0].summary.quantile;
+ ASSERT_EQ(quantile.size(), 6);
+ ASSERT_DOUBLE_EQ(quantile[0].value, 0);
+ ASSERT_DOUBLE_EQ(quantile[1].value, 50);
+ ASSERT_DOUBLE_EQ(quantile[2].value, 90);
+ ASSERT_DOUBLE_EQ(quantile[3].value, 95);
+ ASSERT_DOUBLE_EQ(quantile[4].value, 99);
+ ASSERT_DOUBLE_EQ(quantile[5].value, 100);
+}
+
+TEST(PrometheusExporterUtils, TranslateToPrometheusExactNoQuantile)
+{
+ auto aggregator = std::shared_ptr<metric_sdk::Aggregator<int>>(
+ new metric_sdk::ExactAggregator<int>(metric_api::InstrumentKind::Counter, false));
+
+ std::vector<metric_sdk::Record> collection;
+ int count_num = 10;
+ for (int i = 0; i < count_num; i++)
+ {
+ aggregator->update(i);
+ }
+ aggregator->checkpoint();
+ auto record = get_record("exact-no-quantile", 1, "{label1:v1,label2:v2,}", aggregator);
+ collection.emplace_back(record);
+
+ auto translated = PrometheusExporterUtils::TranslateToPrometheus(collection);
+ ASSERT_EQ(translated.size(), collection.size());
+
+ auto metric = translated[0];
+ std::vector<int> vals = {count_num, 45};
+ assert_basic(metric, "test_exact_no_quantile_metric_record_v_1_0", record.GetDescription(),
+ prometheus_client::MetricType::Summary, 2, vals);
+ auto quantile = metric.metric[0].summary.quantile;
+ ASSERT_EQ(quantile.size(), 0);
+}
+
+TEST(PrometheusExporterUtils, TranslateToPrometheusMinMaxSumCount)
+{
+ auto aggregator = std::shared_ptr<metric_sdk::Aggregator<int>>(
+ new metric_sdk::MinMaxSumCountAggregator<int>(metric_api::InstrumentKind::Counter));
+
+ std::vector<metric_sdk::Record> collection;
+ // min: 1, max: 10, sum: 55, count: 10
+ for (int i = 1; i <= 10; i++)
+ {
+ aggregator->update(i);
+ }
+ aggregator->checkpoint();
+ auto record = get_record("mmsc", 1, "{label1:v1,label2:v2,label3:v3,}", aggregator);
+ collection.emplace_back(record);
+
+ auto translated = PrometheusExporterUtils::TranslateToPrometheus(collection);
+ ASSERT_EQ(translated.size(), collection.size());
+
+ auto metric = translated[0];
+ // in this version of implementation, we use the sum/count as a gauge
+ std::vector<double> vals = {5.5};
+ assert_basic(metric, "test_mmsc_metric_record_v_1_0", record.GetDescription(),
+ prometheus_client::MetricType::Gauge, 3, vals);
+}
+
+TEST(PrometheusExporterUtils, TranslateToPrometheusSketch)
+{
+ auto aggregator = std::shared_ptr<metric_sdk::Aggregator<int>>(
+ new metric_sdk::SketchAggregator<int>(metric_api::InstrumentKind::Counter, 0.0005));
+
+ std::vector<metric_sdk::Record> collection;
+ for (int i = 0; i <= 100; i++)
+ {
+ aggregator->update(i);
+ }
+ aggregator->checkpoint();
+ auto record = get_record("sketch", 1, "{label1:v1,label2:v2,}", aggregator);
+ collection.emplace_back(record);
+
+ auto translated = PrometheusExporterUtils::TranslateToPrometheus(collection);
+ ASSERT_EQ(translated.size(), collection.size());
+
+ auto metric = translated[0];
+ std::vector<int> vals = {aggregator->get_checkpoint()[1], aggregator->get_checkpoint()[0]};
+ assert_basic(metric, "test_sketch_metric_record_v_1_0", record.GetDescription(),
+ prometheus_client::MetricType::Summary, 2, vals);
+
+ auto quantile = metric.metric[0].summary.quantile;
+ ASSERT_EQ(quantile.size(), 6);
+ ASSERT_DOUBLE_EQ(quantile[0].value, 0);
+ ASSERT_DOUBLE_EQ(quantile[1].value, 49);
+ ASSERT_DOUBLE_EQ(quantile[2].value, 89);
+ ASSERT_DOUBLE_EQ(quantile[3].value, 94);
+ ASSERT_DOUBLE_EQ(quantile[4].value, 98);
+ ASSERT_DOUBLE_EQ(quantile[5].value, 99);
+}
+
+TEST(PrometheusExporterUtils, TranslateToPrometheusMultipleAggregators)
+{
+ auto counter_aggregator = std::shared_ptr<metric_sdk::Aggregator<double>>(
+ new metric_sdk::CounterAggregator<double>(metric_api::InstrumentKind::Counter));
+ auto gauge_aggregator = std::shared_ptr<metric_sdk::Aggregator<int>>(
+ new metric_sdk::GaugeAggregator<int>(metric_api::InstrumentKind::Counter));
+
+ std::vector<metric_sdk::Record> collection;
+ counter_aggregator->update(10);
+ counter_aggregator->update(20);
+ counter_aggregator->checkpoint();
+ auto record1 = get_record("counter", 1, "{label1:v1,label2:v2,label3:v3,}", counter_aggregator);
+ gauge_aggregator->update(10);
+ gauge_aggregator->update(30);
+ gauge_aggregator->update(20);
+ gauge_aggregator->checkpoint();
+ auto record2 = get_record("gauge", 1, "{label1:v1,}", gauge_aggregator);
+ collection.emplace_back(record1);
+ collection.emplace_back(record2);
+
+ auto translated = PrometheusExporterUtils::TranslateToPrometheus(collection);
+ ASSERT_EQ(translated.size(), collection.size());
+
+ auto metric1 = translated[0];
+ std::vector<int> vals = {30};
+ assert_basic(metric1, "test_counter_metric_record_v_1_0", record1.GetDescription(),
+ prometheus_client::MetricType::Counter, 3, vals);
+ auto metric2 = translated[1];
+ vals = {20};
+ assert_basic(metric2, "test_gauge_metric_record_v_1_0", record2.GetDescription(),
+ prometheus_client::MetricType::Gauge, 1, vals);
+}
+OPENTELEMETRY_END_NAMESPACE
+#endif