diff --git a/CHANGELOG.md b/CHANGELOG.md index 57307f67fa..f889a287f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ Increment the: * [DEPRECATION] Deprecate ZPAGES [#2291](https://github.com/open-telemetry/opentelemetry-cpp/pull/2291) +* [EXPORTER] Prometheus exporter emit resource attributes + [#2301](https://github.com/open-telemetry/opentelemetry-cpp/pull/2301) * [EXPORTER] Remove explicit timestamps from metric points exported by Prometheus [#2324](https://github.com/open-telemetry/opentelemetry-cpp/pull/2324) * [EXPORTER] Handle attribute key collisions caused by sanitation diff --git a/exporters/prometheus/include/opentelemetry/exporters/prometheus/collector.h b/exporters/prometheus/include/opentelemetry/exporters/prometheus/collector.h index 46d270905b..5dfa983088 100644 --- a/exporters/prometheus/include/opentelemetry/exporters/prometheus/collector.h +++ b/exporters/prometheus/include/opentelemetry/exporters/prometheus/collector.h @@ -31,7 +31,7 @@ class PrometheusCollector : public prometheus_client::Collectable * This constructor initializes the collection for metrics to export * in this class with default capacity */ - explicit PrometheusCollector(sdk::metrics::MetricReader *reader); + explicit PrometheusCollector(sdk::metrics::MetricReader *reader, bool populate_target_info); /** * Collects all metrics data from metricsToCollect collection. @@ -42,6 +42,7 @@ class PrometheusCollector : public prometheus_client::Collectable private: sdk::metrics::MetricReader *reader_; + bool populate_target_info_; /* * Lock when operating the metricsToCollect collection diff --git a/exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter_options.h b/exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter_options.h index 76a08d2a4a..3f36d780ee 100644 --- a/exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter_options.h +++ b/exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter_options.h @@ -22,6 +22,9 @@ struct PrometheusExporterOptions // The endpoint the Prometheus backend can collect metrics from std::string url; + + // Populating target_info + bool populate_target_info = true; }; } // namespace metrics diff --git a/exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter_utils.h b/exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter_utils.h index 76c62d58ba..96c7ac8826 100644 --- a/exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter_utils.h +++ b/exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter_utils.h @@ -26,12 +26,25 @@ class PrometheusExporterUtils * to Prometheus metrics data collection * * @param records a collection of metrics in OpenTelemetry + * @param populate_target_info whether to populate target_info * @return a collection of translated metrics that is acceptable by Prometheus */ static std::vector<::prometheus::MetricFamily> TranslateToPrometheus( - const sdk::metrics::ResourceMetrics &data); + const sdk::metrics::ResourceMetrics &data, + bool populate_target_info = true); private: + /** + * Append key-value pair to prometheus labels. + * + * @param name label name + * @param value label value + * @param labels target labels + */ + static void AddPrometheusLabel(std::string name, + std::string value, + std::vector<::prometheus::ClientMetric::Label> *labels); + static opentelemetry::sdk::metrics::AggregationType getAggregationType( const opentelemetry::sdk::metrics::PointType &point_type); @@ -41,6 +54,13 @@ class PrometheusExporterUtils static ::prometheus::MetricType TranslateType(opentelemetry::sdk::metrics::AggregationType kind, bool is_monotonic = true); + /** + * Add a target_info metric to collect resource attributes + */ + static void SetTarget(const sdk::metrics::ResourceMetrics &data, + const opentelemetry::sdk::instrumentationscope::InstrumentationScope *scope, + std::vector<::prometheus::MetricFamily> *output); + /** * Set metric data for: * Counter => Prometheus Counter @@ -50,7 +70,8 @@ class PrometheusExporterUtils const opentelemetry::sdk::metrics::PointAttributes &labels, const opentelemetry::sdk::instrumentationscope::InstrumentationScope *scope, ::prometheus::MetricType type, - ::prometheus::MetricFamily *metric_family); + ::prometheus::MetricFamily *metric_family, + const opentelemetry::sdk::resource::Resource *resource); /** * Set metric data for: @@ -62,7 +83,8 @@ class PrometheusExporterUtils const std::vector &counts, const opentelemetry::sdk::metrics::PointAttributes &labels, const opentelemetry::sdk::instrumentationscope::InstrumentationScope *scope, - ::prometheus::MetricFamily *metric_family); + ::prometheus::MetricFamily *metric_family, + const opentelemetry::sdk::resource::Resource *resource); /** * Set time and labels to metric data @@ -70,7 +92,8 @@ class PrometheusExporterUtils static void SetMetricBasic( ::prometheus::ClientMetric &metric, const opentelemetry::sdk::metrics::PointAttributes &labels, - const opentelemetry::sdk::instrumentationscope::InstrumentationScope *scope); + const opentelemetry::sdk::instrumentationscope::InstrumentationScope *scope, + const opentelemetry::sdk::resource::Resource *resource); /** * Convert attribute value to string diff --git a/exporters/prometheus/src/collector.cc b/exporters/prometheus/src/collector.cc index 29c0bc8db2..33b9a9b8c8 100644 --- a/exporters/prometheus/src/collector.cc +++ b/exporters/prometheus/src/collector.cc @@ -17,7 +17,10 @@ namespace metrics * This constructor initializes the collection for metrics to export * in this class with default capacity */ -PrometheusCollector::PrometheusCollector(sdk::metrics::MetricReader *reader) : reader_(reader) {} +PrometheusCollector::PrometheusCollector(sdk::metrics::MetricReader *reader, + bool populate_target_info) + : reader_(reader), populate_target_info_(populate_target_info) +{} /** * Collects all metrics data from metricsToCollect collection. @@ -36,8 +39,10 @@ std::vector PrometheusCollector::Collect() cons collection_lock_.lock(); std::vector result; - reader_->Collect([&result](sdk::metrics::ResourceMetrics &metric_data) { - auto prometheus_metric_data = PrometheusExporterUtils::TranslateToPrometheus(metric_data); + + reader_->Collect([&result, this](sdk::metrics::ResourceMetrics &metric_data) { + auto prometheus_metric_data = + PrometheusExporterUtils::TranslateToPrometheus(metric_data, this->populate_target_info_); for (auto &data : prometheus_metric_data) result.emplace_back(data); return true; diff --git a/exporters/prometheus/src/exporter.cc b/exporters/prometheus/src/exporter.cc index 7691e32f39..6022c2c33b 100644 --- a/exporters/prometheus/src/exporter.cc +++ b/exporters/prometheus/src/exporter.cc @@ -30,7 +30,8 @@ PrometheusExporter::PrometheusExporter(const PrometheusExporterOptions &options) Shutdown(); // set MetricReader in shutdown state. return; } - collector_ = std::shared_ptr(new PrometheusCollector(this)); + collector_ = std::shared_ptr( + new PrometheusCollector(this, options_.populate_target_info)); exposer_->RegisterCollectable(collector_); } diff --git a/exporters/prometheus/src/exporter_utils.cc b/exporters/prometheus/src/exporter_utils.cc index c0f034ffe5..42fafc6336 100644 --- a/exporters/prometheus/src/exporter_utils.cc +++ b/exporters/prometheus/src/exporter_utils.cc @@ -1,14 +1,22 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +#include #include +#include +#include +#include #include #include + #include "prometheus/metric_family.h" +#include "prometheus/metric_type.h" -#include #include "opentelemetry/exporters/prometheus/exporter_utils.h" #include "opentelemetry/sdk/metrics/export/metric_producer.h" +#include "opentelemetry/sdk/resource/resource.h" +#include "opentelemetry/sdk/resource/semantic_conventions.h" +#include "opentelemetry/trace/semantic_conventions.h" #include "opentelemetry/sdk/common/global_log_handler.h" @@ -111,11 +119,25 @@ std::string SanitizeName(std::string name) * @return a collection of translated metrics that is acceptable by Prometheus */ std::vector PrometheusExporterUtils::TranslateToPrometheus( - const sdk::metrics::ResourceMetrics &data) + const sdk::metrics::ResourceMetrics &data, + bool populate_target_info) { // initialize output vector + std::size_t reserve_size = 1; + for (const auto &instrumentation_info : data.scope_metric_data_) + { + reserve_size += instrumentation_info.metric_data_.size(); + } + std::vector output; + output.reserve(reserve_size); + + // Append target_info as the first metric + if (populate_target_info && !data.scope_metric_data_.empty()) + { + SetTarget(data, (*data.scope_metric_data_.begin()).scope_, &output); + } for (const auto &instrumentation_info : data.scope_metric_data_) { @@ -151,10 +173,11 @@ std::vector PrometheusExporterUtils::TranslateT } else { - sum = nostd::get(histogram_point_data.sum_); + sum = static_cast(nostd::get(histogram_point_data.sum_)); } SetData(std::vector{sum, (double)histogram_point_data.count_}, boundaries, counts, - point_data_attr.attributes, instrumentation_info.scope_, &metric_family); + point_data_attr.attributes, instrumentation_info.scope_, &metric_family, + data.resource_); } else if (type == prometheus_client::MetricType::Gauge) { @@ -165,7 +188,7 @@ std::vector PrometheusExporterUtils::TranslateT nostd::get(point_data_attr.point_data); std::vector values{last_value_point_data.value_}; SetData(values, point_data_attr.attributes, instrumentation_info.scope_, type, - &metric_family); + &metric_family, data.resource_); } else if (nostd::holds_alternative(point_data_attr.point_data)) { @@ -173,7 +196,7 @@ std::vector PrometheusExporterUtils::TranslateT nostd::get(point_data_attr.point_data); std::vector values{sum_point_data.value_}; SetData(values, point_data_attr.attributes, instrumentation_info.scope_, type, - &metric_family); + &metric_family, data.resource_); } else { @@ -190,7 +213,7 @@ std::vector PrometheusExporterUtils::TranslateT nostd::get(point_data_attr.point_data); std::vector values{sum_point_data.value_}; SetData(values, point_data_attr.attributes, instrumentation_info.scope_, type, - &metric_family); + &metric_family, data.resource_); } else { @@ -206,6 +229,17 @@ std::vector PrometheusExporterUtils::TranslateT return output; } +void PrometheusExporterUtils::AddPrometheusLabel( + std::string name, + std::string value, + std::vector<::prometheus::ClientMetric::Label> *labels) +{ + prometheus_client::ClientMetric::Label prometheus_label; + prometheus_label.name = std::move(name); + prometheus_label.value = std::move(value); + labels->emplace_back(std::move(prometheus_label)); +} + metric_sdk::AggregationType PrometheusExporterUtils::getAggregationType( const metric_sdk::PointType &point_type) { @@ -259,6 +293,37 @@ prometheus_client::MetricType PrometheusExporterUtils::TranslateType( } } +void PrometheusExporterUtils::SetTarget( + const sdk::metrics::ResourceMetrics &data, + const opentelemetry::sdk::instrumentationscope::InstrumentationScope *scope, + std::vector<::prometheus::MetricFamily> *output) +{ + if (output == nullptr || data.resource_ == nullptr) + { + return; + } + + prometheus_client::MetricFamily metric_family; + metric_family.name = "target"; + metric_family.help = "Target metadata"; + metric_family.type = prometheus_client::MetricType::Info; + metric_family.metric.emplace_back(); + + prometheus_client::ClientMetric &metric = metric_family.metric.back(); + metric.info.value = 1.0; + + metric_sdk::PointAttributes empty_attributes; + SetMetricBasic(metric, empty_attributes, scope, data.resource_); + + for (auto &label : data.resource_->GetAttributes()) + { + AddPrometheusLabel(SanitizeName(label.first), AttributeValueToString(label.second), + &metric.label); + } + + output->emplace_back(std::move(metric_family)); +} + /** * Set metric data for: * sum => Prometheus Counter @@ -269,11 +334,12 @@ void PrometheusExporterUtils::SetData( const metric_sdk::PointAttributes &labels, const opentelemetry::sdk::instrumentationscope::InstrumentationScope *scope, prometheus_client::MetricType type, - prometheus_client::MetricFamily *metric_family) + prometheus_client::MetricFamily *metric_family, + const opentelemetry::sdk::resource::Resource *resource) { metric_family->metric.emplace_back(); prometheus_client::ClientMetric &metric = metric_family->metric.back(); - SetMetricBasic(metric, labels, scope); + SetMetricBasic(metric, labels, scope, resource); SetValue(values, type, &metric); } @@ -288,11 +354,12 @@ void PrometheusExporterUtils::SetData( const std::vector &counts, const metric_sdk::PointAttributes &labels, const opentelemetry::sdk::instrumentationscope::InstrumentationScope *scope, - prometheus_client::MetricFamily *metric_family) + prometheus_client::MetricFamily *metric_family, + const opentelemetry::sdk::resource::Resource *resource) { metric_family->metric.emplace_back(); prometheus_client::ClientMetric &metric = metric_family->metric.back(); - SetMetricBasic(metric, labels, scope); + SetMetricBasic(metric, labels, scope, resource); SetValue(values, boundaries, counts, &metric); } @@ -302,9 +369,10 @@ void PrometheusExporterUtils::SetData( void PrometheusExporterUtils::SetMetricBasic( prometheus_client::ClientMetric &metric, const metric_sdk::PointAttributes &labels, - const opentelemetry::sdk::instrumentationscope::InstrumentationScope *scope) + const opentelemetry::sdk::instrumentationscope::InstrumentationScope *scope, + const opentelemetry::sdk::resource::Resource *resource) { - if (labels.empty()) + if (labels.empty() && nullptr == resource) { return; } @@ -410,7 +478,7 @@ void PrometheusExporterUtils::SetValue(std::vector values, const auto &value_var = values[0]; if (nostd::holds_alternative(value_var)) { - value = nostd::get(value_var); + value = static_cast(nostd::get(value_var)); } else { @@ -445,9 +513,9 @@ void PrometheusExporterUtils::SetValue(std::vector values, const std::vector &counts, prometheus_client::ClientMetric *metric) { - metric->histogram.sample_sum = values[0]; - metric->histogram.sample_count = values[1]; - int cumulative = 0; + metric->histogram.sample_sum = static_cast(values[0]); + metric->histogram.sample_count = static_cast(values[1]); + std::uint64_t cumulative = 0; std::vector buckets; uint32_t idx = 0; for (const auto &boundary : boundaries) diff --git a/exporters/prometheus/test/collector_test.cc b/exporters/prometheus/test/collector_test.cc index 8947dcfd27..578e01d8f8 100644 --- a/exporters/prometheus/test/collector_test.cc +++ b/exporters/prometheus/test/collector_test.cc @@ -73,12 +73,12 @@ TEST(PrometheusCollector, BasicTests) MockMetricReader *reader = new MockMetricReader(); MockMetricProducer *producer = new MockMetricProducer(); reader->SetMetricProducer(producer); - PrometheusCollector collector(reader); + PrometheusCollector collector(reader, true); auto data = collector.Collect(); // Collection size should be the same as the size // of the records collection produced by MetricProducer. - ASSERT_EQ(data.size(), 1); + ASSERT_EQ(data.size(), 2); delete reader; delete producer; } diff --git a/exporters/prometheus/test/exporter_utils_test.cc b/exporters/prometheus/test/exporter_utils_test.cc index 3c945c6735..8ff324d7ac 100644 --- a/exporters/prometheus/test/exporter_utils_test.cc +++ b/exporters/prometheus/test/exporter_utils_test.cc @@ -6,11 +6,11 @@ #include "prometheus/metric_type.h" #include "opentelemetry/exporters/prometheus/exporter_utils.h" +#include "opentelemetry/sdk/resource/resource.h" #include "prometheus_test_helper.h" using opentelemetry::exporter::metrics::PrometheusExporterUtils; namespace metric_sdk = opentelemetry::sdk::metrics; -namespace metric_api = opentelemetry::metrics; namespace prometheus_client = ::prometheus; OPENTELEMETRY_BEGIN_NAMESPACE @@ -20,7 +20,7 @@ void assert_basic(prometheus_client::MetricFamily &metric, const std::string &sanitized_name, const std::string &description, prometheus_client::MetricType type, - int label_num, + size_t label_num, std::vector vals) { ASSERT_EQ(metric.name, sanitized_name + "_unit"); // name sanitized @@ -97,45 +97,130 @@ TEST(PrometheusExporterUtils, TranslateToPrometheusEmptyInputReturnsEmptyCollect TEST(PrometheusExporterUtils, TranslateToPrometheusIntegerCounter) { + + opentelemetry::sdk::resource::Resource resource = opentelemetry::sdk::resource::Resource::Create( + {{"service.name", "test_service"}, + {"service.namespace", "test_namespace"}, + {"service.instance.id", "localhost:8000"}, + {"custom_resource_attr", "custom_resource_value"}}); TestDataPoints dp; metric_sdk::ResourceMetrics metrics_data = dp.CreateSumPointData(); + metrics_data.resource_ = &resource; auto translated = PrometheusExporterUtils::TranslateToPrometheus(metrics_data); - ASSERT_EQ(translated.size(), 1); + ASSERT_EQ(translated.size(), 2); - auto metric1 = translated[0]; + auto metric1 = translated[1]; std::vector vals = {10}; assert_basic(metric1, "library_name", "description", prometheus_client::MetricType::Counter, 3, vals); + + int checked_label_num = 0; + for (auto &label : translated[0].metric[0].label) + { + if (label.name == "service_namespace") + { + ASSERT_EQ(label.value, "test_namespace"); + checked_label_num++; + } + else if (label.name == "service_name") + { + ASSERT_EQ(label.value, "test_service"); + checked_label_num++; + } + else if (label.name == "service_instance_id") + { + ASSERT_EQ(label.value, "localhost:8000"); + checked_label_num++; + } + else if (label.name == "custom_resource_attr") + { + ASSERT_EQ(label.value, "custom_resource_value"); + checked_label_num++; + } + } + ASSERT_EQ(checked_label_num, 4); } TEST(PrometheusExporterUtils, TranslateToPrometheusIntegerLastValue) { + opentelemetry::sdk::resource::Resource resource = opentelemetry::sdk::resource::Resource::Create( + {{"service.name", "test_service"}, + {"service.instance.id", "localhost:8000"}, + {"custom_resource_attr", "custom_resource_value"}}); TestDataPoints dp; metric_sdk::ResourceMetrics metrics_data = dp.CreateLastValuePointData(); + metrics_data.resource_ = &resource; auto translated = PrometheusExporterUtils::TranslateToPrometheus(metrics_data); - ASSERT_EQ(translated.size(), 1); + ASSERT_EQ(translated.size(), 2); - auto metric1 = translated[0]; + auto metric1 = translated[1]; std::vector vals = {10}; assert_basic(metric1, "library_name", "description", prometheus_client::MetricType::Gauge, 3, vals); + + int checked_label_num = 0; + for (auto &label : translated[0].metric[0].label) + { + if (label.name == "service_name") + { + ASSERT_EQ(label.value, "test_service"); + checked_label_num++; + } + else if (label.name == "service_instance_id") + { + ASSERT_EQ(label.value, "localhost:8000"); + checked_label_num++; + } + else if (label.name == "custom_resource_attr") + { + ASSERT_EQ(label.value, "custom_resource_value"); + checked_label_num++; + } + } + ASSERT_EQ(checked_label_num, 3); } TEST(PrometheusExporterUtils, TranslateToPrometheusHistogramNormal) { + opentelemetry::sdk::resource::Resource resource = opentelemetry::sdk::resource::Resource::Create( + {{"service.instance.id", "localhost:8001"}, + {"custom_resource_attr", "custom_resource_value"}}); TestDataPoints dp; metric_sdk::ResourceMetrics metrics_data = dp.CreateHistogramPointData(); + metrics_data.resource_ = &resource; auto translated = PrometheusExporterUtils::TranslateToPrometheus(metrics_data); - ASSERT_EQ(translated.size(), 1); + ASSERT_EQ(translated.size(), 2); - auto metric = translated[0]; + auto metric = translated[1]; std::vector vals = {3, 900.5, 4}; assert_basic(metric, "library_name", "description", prometheus_client::MetricType::Histogram, 3, vals); assert_histogram(metric, std::list{10.1, 20.2, 30.2}, {200, 300, 400, 500}); + + int checked_label_num = 0; + for (auto &label : translated[0].metric[0].label) + { + if (label.name == "service_name") + { + // default service name is "unknown_service" + ASSERT_EQ(label.value, "unknown_service"); + checked_label_num++; + } + else if (label.name == "service_instance_id") + { + ASSERT_EQ(label.value, "localhost:8001"); + checked_label_num++; + } + else if (label.name == "custom_resource_attr") + { + ASSERT_EQ(label.value, "custom_resource_value"); + checked_label_num++; + } + } + ASSERT_EQ(checked_label_num, 3); } class SanitizeTest : public ::testing::Test @@ -152,7 +237,11 @@ class SanitizeTest : public ::testing::Test metric_sdk::InstrumentValueType::kDouble}; std::vector result = PrometheusExporterUtils::TranslateToPrometheus( {&resource_, - {{instrumentation_scope_.get(), {{instrument_descriptor, {}, {}, {}, {{{}, {}}}}}}}}); + std::vector{ + {instrumentation_scope_.get(), + std::vector{ + {{instrument_descriptor, {}, {}, {}, {{{}, {}}}}}}}}}, + false); EXPECT_EQ(result.begin()->name, sanitized + "_unit"); } @@ -163,8 +252,11 @@ class SanitizeTest : public ::testing::Test metric_sdk::InstrumentValueType::kDouble}; std::vector result = PrometheusExporterUtils::TranslateToPrometheus( {&resource_, - {{instrumentation_scope_.get(), - {{instrument_descriptor, {}, {}, {}, {{{{original, "value"}}, {}}}}}}}}); + std::vector{ + {instrumentation_scope_.get(), + std::vector{ + {instrument_descriptor, {}, {}, {}, {{{{original, "value"}}, {}}}}}}}}, + false); EXPECT_EQ(result.begin()->metric.begin()->label.begin()->name, sanitized); } }; @@ -204,8 +296,24 @@ class AttributeCollisionTest : public ::testing::Test { std::vector result = PrometheusExporterUtils::TranslateToPrometheus( {&resource_, - {{instrumentation_scope_.get(), {{instrument_descriptor_, {}, {}, {}, {{attrs, {}}}}}}}}); - EXPECT_EQ(result.begin()->metric.begin()->label, expected); + std::vector{ + {instrumentation_scope_.get(), + std::vector{ + {instrument_descriptor_, {}, {}, {}, {{attrs, {}}}}}}}}, + false); + for (auto &expected_kv : expected) + { + bool found = false; + for (auto &found_kv : result.begin()->metric.begin()->label) + { + if (found_kv.name == expected_kv.name) + { + EXPECT_EQ(found_kv.value, expected_kv.value); + found = true; + } + } + EXPECT_TRUE(found); + } } }; diff --git a/sdk/include/opentelemetry/sdk/metrics/export/metric_producer.h b/sdk/include/opentelemetry/sdk/metrics/export/metric_producer.h index 2a0c87fcaa..11eb113e1e 100644 --- a/sdk/include/opentelemetry/sdk/metrics/export/metric_producer.h +++ b/sdk/include/opentelemetry/sdk/metrics/export/metric_producer.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include "opentelemetry/nostd/function_ref.h" @@ -31,14 +32,41 @@ namespace metrics */ struct ScopeMetrics { - const opentelemetry::sdk::instrumentationscope::InstrumentationScope *scope_; + const opentelemetry::sdk::instrumentationscope::InstrumentationScope *scope_ = nullptr; std::vector metric_data_; + + template + inline ScopeMetrics(ScopePtr &&scope, MetricDataType &&metric) + : scope_{std::forward(scope)}, metric_data_{std::forward(metric)} + {} + + inline ScopeMetrics() {} + inline ScopeMetrics(const ScopeMetrics &) = default; + inline ScopeMetrics(ScopeMetrics &&) = default; + + inline ScopeMetrics &operator=(const ScopeMetrics &) = default; + + inline ScopeMetrics &operator=(ScopeMetrics &&) = default; }; struct ResourceMetrics { - const opentelemetry::sdk::resource::Resource *resource_; + const opentelemetry::sdk::resource::Resource *resource_ = nullptr; std::vector scope_metric_data_; + + template + inline ResourceMetrics(ResourcePtr &&resource, ScopeMetricsType &&scope_metric_data) + : resource_{std::forward(resource)}, + scope_metric_data_{std::forward(scope_metric_data)} + {} + + inline ResourceMetrics() {} + inline ResourceMetrics(const ResourceMetrics &) = default; + inline ResourceMetrics(ResourceMetrics &&) = default; + + inline ResourceMetrics &operator=(const ResourceMetrics &) = default; + + inline ResourceMetrics &operator=(ResourceMetrics &&) = default; }; /**