diff --git a/exporters/CMakeLists.txt b/exporters/CMakeLists.txt index 2ae8fd92bb..862d2c779e 100644 --- a/exporters/CMakeLists.txt +++ b/exporters/CMakeLists.txt @@ -19,7 +19,7 @@ endif() add_subdirectory(ostream) add_subdirectory(memory) -if(WITH_PROMETHEUS AND WITH_METRICS_PREVIEW) +if(WITH_PROMETHEUS) add_subdirectory(prometheus) endif() diff --git a/exporters/prometheus/BUILD b/exporters/prometheus/BUILD index e92f9412eb..47c9bbab16 100644 --- a/exporters/prometheus/BUILD +++ b/exporters/prometheus/BUILD @@ -15,7 +15,7 @@ package(default_visibility = ["//visibility:public"]) cc_library( - name = "prometheus_exporter", + name = "prometheus_exporter_deprecated", srcs = [ "src/prometheus_exporter.cc", ], @@ -25,8 +25,8 @@ cc_library( strip_include_prefix = "include", tags = ["prometheus"], deps = [ - ":prometheus_collector", - ":prometheus_exporter_utils", + ":prometheus_collector_deprecated", + ":prometheus_exporter_utils_deprecated", "//api", "//sdk:headers", "@com_github_jupp0r_prometheus_cpp//core", @@ -35,7 +35,7 @@ cc_library( ) cc_library( - name = "prometheus_exporter_utils", + name = "prometheus_exporter_utils_deprecated", srcs = [ "src/prometheus_exporter_utils.cc", ], @@ -53,7 +53,7 @@ cc_library( ) cc_library( - name = "prometheus_collector", + name = "prometheus_collector_deprecated", srcs = [ "src/prometheus_collector.cc", ], @@ -63,7 +63,7 @@ cc_library( strip_include_prefix = "include", tags = ["prometheus"], deps = [ - ":prometheus_exporter_utils", + ":prometheus_exporter_utils_deprecated", "//api", "//sdk:headers", "@com_github_jupp0r_prometheus_cpp//core", @@ -72,7 +72,7 @@ cc_library( ) cc_test( - name = "prometheus_exporter_test", + name = "prometheus_exporter_test_deprecated", srcs = [ "test/prometheus_exporter_test.cc", ], @@ -81,7 +81,64 @@ cc_test( "test", ], deps = [ - ":prometheus_exporter", + ":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/exporters/prometheus/CMakeLists.txt b/exporters/prometheus/CMakeLists.txt index 753fa18cac..56523ec848 100755 --- a/exporters/prometheus/CMakeLists.txt +++ b/exporters/prometheus/CMakeLists.txt @@ -16,40 +16,75 @@ 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 "$" + "$") -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 "$" - "$") + 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}) -set(PROMETHEUS_EXPORTER_TARGETS prometheus_exporter_deprecated) -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_deprecated - PUBLIC opentelemetry_metrics_deprecated 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() +else() + + add_library(prometheus_exporter src/exporter.cc src/collector.cc + src/exporter_utils.cc) + target_include_directories( + prometheus_exporter + PUBLIC "$" + "$") + + 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") + install( + DIRECTORY include/opentelemetry/exporters/prometheus + DESTINATION include/opentelemetry/exporters/ + FILES_MATCHING + PATTERN "*.h") -if(BUILD_TESTING) - add_subdirectory(test) + if(BUILD_TESTING) + add_subdirectory(test) + endif() endif() diff --git a/exporters/prometheus/include/opentelemetry/exporters/prometheus/collector.h b/exporters/prometheus/include/opentelemetry/exporters/prometheus/collector.h new file mode 100644 index 0000000000..68f50d29fa --- /dev/null +++ b/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 +# include +# include + +# include +# include +# 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 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> &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> 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/exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter.h b/exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter.h new file mode 100644 index 0000000000..6945f09bb9 --- /dev/null +++ b/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 +# include +# include + +# include +# 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 GetOtlpDefaultHttpEndpoint() +{ + constexpr char kPrometheusEndpointEnv[] = "PROMETHEUS_EXPORTER_ENDPOINT"; + constexpr char kPrometheusEndpointDefault[] = "http://localhost:8080"; + + 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 = GetOtlpDefaultHttpEndpoint(); +}; + +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 &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 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/exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter_utils.h b/exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter_utils.h new file mode 100644 index 0000000000..1cb7202ce5 --- /dev/null +++ b/exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter_utils.h @@ -0,0 +1,129 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#ifndef ENABLE_METRICS_PREVIEW + +# include +# include +# include +# 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> &data); + +private: + /** + * Set value to metric family according to record + */ + static void SetMetricFamily(sdk::metrics::ResourceMetrics &data, + ::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 + */ + static void SetMetricFamilyByAggregator(const sdk::metrics::ResourceMetrics &data, + std::string labels_str, + ::prometheus::MetricFamily *metric_family); + + 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 + static void SetData(std::vector values, + const std::string &labels, + ::prometheus::MetricType type, + std::chrono::nanoseconds time, + ::prometheus::MetricFamily *metric_family); + + /** + * Set metric data for: + * Histogram => Prometheus Histogram + */ + template + static void SetData(std::vector values, + const opentelemetry::sdk::metrics::ListType &boundaries, + const std::vector &counts, + const std::string &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 std::string &labels); + + /** + * Parse a string of labels (key:value) into a vector of pairs + * {,} + * {l1:v1,l2:v2,...,} + */ + static std::vector> ParseLabel(std::string labels); + + /** + * Handle Counter and Gauge. + */ + template + static void SetValue(std::vector values, + ::prometheus::MetricType type, + ::prometheus::ClientMetric *metric); + + /** + * Handle Gauge from MinMaxSumCount + */ + static void SetValue(double value, ::prometheus::ClientMetric *metric); + + /** + * Handle Histogram + */ + template + static void SetValue(std::vector values, + const std::list &boundaries, + const std::vector &counts, + ::prometheus::ClientMetric *metric); +}; +} // namespace metrics +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/exporters/prometheus/src/collector.cc b/exporters/prometheus/src/collector.cc new file mode 100644 index 0000000000..03793a8eed --- /dev/null +++ b/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 PrometheusCollector::Collect() const +{ + this->collection_lock_.lock(); + if (metrics_to_collect_.empty()) + { + this->collection_lock_.unlock(); + return {}; + } + + std::vector result; + + // copy the intermediate collection, and then clear it + std::vector> 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> &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/exporters/prometheus/src/exporter.cc b/exporters/prometheus/src/exporter.cc new file mode 100644 index 0000000000..a0bd9e27ab --- /dev/null +++ b/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(new PrometheusCollector); + + exposer_->RegisterCollectable(collector_); +} + +/** + * PrometheusExporter constructor with no parameters + * Used for testing only + */ +PrometheusExporter::PrometheusExporter() : is_shutdown_(false) +{ + collector_ = std::unique_ptr(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 &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/exporters/prometheus/src/exporter_utils.cc b/exporters/prometheus/src/exporter_utils.cc new file mode 100644 index 0000000000..39131e210f --- /dev/null +++ b/exporters/prometheus/src/exporter_utils.cc @@ -0,0 +1,327 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifndef ENABLE_METRICS_PREVIEW +# include +# include +# include + +# include +# include "opentelemetry/exporters/prometheus/exporter_utils.h" +# include "opentelemetry/sdk/metrics/export/metric_producer.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 PrometheusExporterUtils::TranslateToPrometheus( + const std::vector> &data) +{ + if (data.empty()) + { + return {}; + } + + // initialize output vector + std::vector output(data.size()); + + // iterate through the vector and set result data into it + int i = 0; + for (const auto &r : data) + { + SetMetricFamily(*r, &output[i]); + i++; + } + + return output; +} + +/** + * Set value to metric family according to record + */ +void PrometheusExporterUtils::SetMetricFamily(sdk::metrics::ResourceMetrics &data, + prometheus_client::MetricFamily *metric_family) +{ + SetMetricFamilyByAggregator(data, "", 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 + */ +void PrometheusExporterUtils::SetMetricFamilyByAggregator( + const sdk::metrics::ResourceMetrics &data, + std::string labels_str, + prometheus_client::MetricFamily *metric_family) +{ + for (const auto &instrumentation_info : data.instrumentation_info_metric_data_) + { + auto origin_name = instrumentation_info.instrumentation_library_->GetName(); + auto sanitized = SanitizeNames(origin_name); + metric_family->name = sanitized; + for (const auto &metric_data : instrumentation_info.metric_data_) + { + 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(point_data_attr.point_data); + auto boundaries = histogram_point_data.boundaries_; + auto counts = histogram_point_data.counts_; + SetData(std::vector{nostd::get(histogram_point_data.sum_), + (double)histogram_point_data.count_}, + boundaries, counts, labels_str, time, metric_family); + } + else // Counter, Untyped + { + auto sum_point_data = nostd::get(point_data_attr.point_data); + std::vector values{sum_point_data.value_}; + SetData(values, labels_str, type, time, metric_family); + } + } + } + } +} + +metric_sdk::AggregationType PrometheusExporterUtils::getAggregationType( + const metric_sdk::PointType &point_type) +{ + if (nostd::holds_alternative(point_type)) + { + return metric_sdk::AggregationType::kSum; + } + else if (nostd::holds_alternative(point_type)) + { + return metric_sdk::AggregationType::kDrop; + } + else if (nostd::holds_alternative(point_type)) + { + return metric_sdk::AggregationType::kHistogram; + } + else if (nostd::holds_alternative(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 +void PrometheusExporterUtils::SetData(std::vector 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 +void PrometheusExporterUtils::SetData(std::vector values, + const opentelemetry::sdk::metrics::ListType &boundaries, + const std::vector &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); + if (nostd::holds_alternative>(boundaries)) + { + SetValue(values, nostd::get>(boundaries), counts, &metric); + } + else + { + SetValue(values, nostd::get>(boundaries), counts, &metric); + } +} + +/** + * 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> PrometheusExporterUtils::ParseLabel( + std::string labels) +{ + if (labels.size() < 3) + { + return {}; + } + labels = labels.substr(1, labels.size() - 2); + + std::vector 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> 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(label, value)); + } + + return result; +} + +/** + * Handle Counter. + */ +template +void PrometheusExporterUtils::SetValue(std::vector values, + prometheus_client::MetricType type, + prometheus_client::ClientMetric *metric) +{ + double value = 0.0; + const auto &value_var = values[0]; + if (nostd::holds_alternative(value_var)) + { + value = nostd::get(value_var); + } + else + { + value = nostd::get(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 +void PrometheusExporterUtils::SetValue(std::vector values, + const std::list &boundaries, + const std::vector &counts, + prometheus_client::ClientMetric *metric) +{ + metric->histogram.sample_sum = values[0]; + metric->histogram.sample_count = values[1]; + int cumulative = 0; + std::vector 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::infinity(); + buckets.emplace_back(bucket); + metric->histogram.bucket = buckets; +} + +} // namespace metrics +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif