diff --git a/google/cloud/opentelemetry/internal/monitored_resource.cc b/google/cloud/opentelemetry/internal/monitored_resource.cc index 03260ca6c0dd3..eb2cb79c2cdb2 100644 --- a/google/cloud/opentelemetry/internal/monitored_resource.cc +++ b/google/cloud/opentelemetry/internal/monitored_resource.cc @@ -49,11 +49,6 @@ struct AsStringVisitor { std::string operator()(bool const& v) const { return v ? "true" : "false"; } }; -struct OTelKeyMatch { - std::vector otel_keys; - absl::optional fallback = absl::nullopt; -}; - class MonitoredResourceProvider { public: MonitoredResourceProvider( diff --git a/google/cloud/opentelemetry/internal/monitored_resource.h b/google/cloud/opentelemetry/internal/monitored_resource.h index be03b938f0bea..f717c1cd8fe0c 100644 --- a/google/cloud/opentelemetry/internal/monitored_resource.h +++ b/google/cloud/opentelemetry/internal/monitored_resource.h @@ -15,16 +15,23 @@ #ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_OPENTELEMETRY_INTERNAL_MONITORED_RESOURCE_H #define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_OPENTELEMETRY_INTERNAL_MONITORED_RESOURCE_H +#include "absl/types/optional.h" #include "google/cloud/version.h" #include #include #include +#include namespace google { namespace cloud { namespace otel_internal { GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN +struct OTelKeyMatch { + std::vector otel_keys; + absl::optional fallback = absl::nullopt; +}; + std::string AsString( opentelemetry::sdk::common::OwnedAttributeValue const& attribute); diff --git a/google/cloud/opentelemetry/internal/time_series.cc b/google/cloud/opentelemetry/internal/time_series.cc index cab7172e87765..599c6469f8e83 100644 --- a/google/cloud/opentelemetry/internal/time_series.cc +++ b/google/cloud/opentelemetry/internal/time_series.cc @@ -21,6 +21,8 @@ #include #include #include +#include +#include namespace google { namespace cloud { @@ -215,6 +217,7 @@ std::vector ToTimeSeries( } } } + WithExtraLabels(data, tss); return tss; } @@ -236,6 +239,41 @@ std::vector ToRequests( return requests; } +void WithExtraLabels( + opentelemetry::sdk::metrics::ResourceMetrics const& data, + std::vector& tss, + std::unordered_map const& extra_labels) { + if (!data.resource_) { + return; + } + + opentelemetry::sdk::resource::ResourceAttributes const& attributes = + data.resource_->GetAttributes(); + for (auto const& kv : extra_labels) { + auto const& oks = kv.second.otel_keys; + auto found = std::find_first_of( + oks.begin(), oks.end(), attributes.begin(), attributes.end(), + [](auto const& key, auto const& attr) { return key == attr.first; }); + + std::string value; + if (found != oks.end()) { + value = AsString(attributes.at(*found)); + } else if (kv.second.fallback) { + value = *kv.second.fallback; + } + if (value.empty()) { + continue; + } + + for (auto& ts : tss) { + auto& labels = *((*ts.mutable_metric()).mutable_labels()); + if (labels.find(kv.first) == labels.end()) { + labels[kv.first] = value; + } + } + } +} + GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END } // namespace otel_internal } // namespace cloud diff --git a/google/cloud/opentelemetry/internal/time_series.h b/google/cloud/opentelemetry/internal/time_series.h index 2692408db94f8..d15217c289bee 100644 --- a/google/cloud/opentelemetry/internal/time_series.h +++ b/google/cloud/opentelemetry/internal/time_series.h @@ -15,14 +15,18 @@ #ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_OPENTELEMETRY_INTERNAL_TIME_SERIES_H #define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_OPENTELEMETRY_INTERNAL_TIME_SERIES_H +#include "google/cloud/opentelemetry/internal/monitored_resource.h" #include "google/cloud/version.h" #include "absl/types/optional.h" #include #include #include #include +#include +#include #include #include +#include namespace google { namespace cloud { @@ -34,6 +38,15 @@ GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN // See: https://cloud.google.com/monitoring/quotas auto constexpr kMaxTimeSeriesPerRequest = 200; +std::unordered_map const kExtraLabelsLookup = { + {"service_name", + {{opentelemetry::sdk::resource::SemanticConventions::kServiceName}}}, + {"service_namespace", + {{opentelemetry::sdk::resource::SemanticConventions::kServiceNamespace}}}, + {"service_instance_id", + {{opentelemetry::sdk::resource::SemanticConventions:: + kServiceInstanceId}}}}; + google::api::Metric ToMetric( opentelemetry::sdk::metrics::MetricData const& metric_data, opentelemetry::sdk::metrics::PointAttributes const& attributes, @@ -89,6 +102,21 @@ std::vector ToRequests( std::string const& project, google::api::MonitoredResource const& mr_proto, std::vector tss); +/** + * Copy some resource labels into metric labels. + * + * Some resource labels need to be copied into metric labels as they are not + * directly accepted by Google Cloud Monitoring as resource labels. + * + * For example, service resource labels need to be copied into metric labels. + * See: + * https://github.com/GoogleCloudPlatform/opentelemetry-operations-go/blob/main/exporter/collector/breaking-changes.md#labels + */ +void WithExtraLabels(opentelemetry::sdk::metrics::ResourceMetrics const& data, + std::vector& tss, + std::unordered_map const& + extra_labels = kExtraLabelsLookup); + GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END } // namespace otel_internal } // namespace cloud diff --git a/google/cloud/opentelemetry/internal/time_series_test.cc b/google/cloud/opentelemetry/internal/time_series_test.cc index ba1e61cca7468..11d3ecb92e6f6 100644 --- a/google/cloud/opentelemetry/internal/time_series_test.cc +++ b/google/cloud/opentelemetry/internal/time_series_test.cc @@ -20,11 +20,13 @@ #include "google/cloud/testing_util/scoped_log.h" #include #include +#include #include #include #include #include #include +#include namespace google { namespace cloud { @@ -155,6 +157,18 @@ auto IsTestResource() { Pair("instance_id", "1020304050607080900")))); } +auto ResourceWithAllExtraLabels() { + opentelemetry::sdk::common::AttributeMap labels; + for (auto const& kv : kExtraLabelsLookup) { + auto const& oks = kv.second.otel_keys; + if (oks.empty()) { + continue; + } + labels.SetAttribute(oks[0], kv.first); + } + return opentelemetry::sdk::resource::Resource::Create(labels); +} + auto MetricType(std::string const& type) { return ResultOf( "metric.type", @@ -697,6 +711,82 @@ TEST(ToRequests, Batches) { } } +TEST(WithExtraLabels, CopyLabels) { + opentelemetry::sdk::metrics::PointDataAttributes pda; + pda.point_data = opentelemetry::sdk::metrics::SumPointData{}; + + opentelemetry::sdk::metrics::MetricData md; + md.point_data_attr_.push_back(std::move(pda)); + md.instrument_descriptor.name_ = "metric-name"; + md.instrument_descriptor.unit_ = "unit"; + md.instrument_descriptor.type_ = {}; + md.instrument_descriptor.value_type_ = {}; + + opentelemetry::sdk::metrics::ScopeMetrics sm; + sm.metric_data_.push_back(md); + sm.metric_data_.push_back(std::move(md)); + + opentelemetry::sdk::metrics::ResourceMetrics rm; + auto resource = ResourceWithAllExtraLabels(); + rm.resource_ = &resource; + rm.scope_metric_data_.push_back(std::move(sm)); + + auto tss = ToTimeSeries(rm, PrefixWithWorkload); + for (auto const& kv : kExtraLabelsLookup) { + auto const& oks = kv.second.otel_keys; + if (oks.empty()) { + continue; + } + for (auto const& ts : tss) { + auto const& labels = ts.metric().labels(); + ASSERT_TRUE(labels.find(kv.first) != labels.end()); + EXPECT_EQ(labels.at(kv.first), kv.first); + } + } +} + +TEST(WithExtraLabels, NoOverwrites) { + opentelemetry::sdk::metrics::PointDataAttributes pda; + opentelemetry::sdk::common::OrderedAttributeMap existing_labels; + existing_labels.SetAttribute("service_name", "do_not_overwrite"); + pda.attributes = existing_labels; + pda.point_data = opentelemetry::sdk::metrics::SumPointData{}; + + opentelemetry::sdk::metrics::MetricData md; + md.point_data_attr_.push_back(std::move(pda)); + md.instrument_descriptor.name_ = "metric-name"; + md.instrument_descriptor.unit_ = "unit"; + md.instrument_descriptor.type_ = {}; + md.instrument_descriptor.value_type_ = {}; + + opentelemetry::sdk::metrics::ScopeMetrics sm; + sm.metric_data_.push_back(md); + sm.metric_data_.push_back(std::move(md)); + + opentelemetry::sdk::metrics::ResourceMetrics rm; + auto resource = ResourceWithAllExtraLabels(); + rm.resource_ = &resource; + rm.scope_metric_data_.push_back(std::move(sm)); + + auto tss = ToTimeSeries(rm, PrefixWithWorkload); + for (auto const& kv : kExtraLabelsLookup) { + auto const& oks = kv.second.otel_keys; + if (oks.empty()) { + continue; + } + for (auto const& ts : tss) { + auto const& labels = ts.metric().labels(); + ASSERT_TRUE(labels.find(kv.first) != labels.end()); + if (existing_labels.GetAttributes().find(kv.first) == + existing_labels.GetAttributes().end()) { + EXPECT_EQ(labels.at(kv.first), kv.first); + } else { + EXPECT_EQ(labels.at(kv.first), "do_not_overwrite"); + } + } + } +} + } // namespace GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END } // namespace otel_internal