Skip to content

Commit a32153a

Browse files
authored
impl(opentelemetry): add support for exporting dynamic monitored resources (#15272)
1 parent ce7e092 commit a32153a

File tree

10 files changed

+653
-140
lines changed

10 files changed

+653
-140
lines changed

google/cloud/opentelemetry/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ add_library(
3838
configure_basic_tracing.h
3939
internal/monitored_resource.cc
4040
internal/monitored_resource.h
41+
internal/monitoring_exporter.cc
42+
internal/monitoring_exporter.h
4143
internal/recordable.cc
4244
internal/recordable.h
4345
internal/resource_detector_impl.cc
@@ -126,6 +128,7 @@ add_subdirectory(integration_tests)
126128
set(opentelemetry_unit_tests
127129
# cmake-format: sort
128130
internal/monitored_resource_test.cc
131+
internal/monitoring_exporter_test.cc
129132
internal/recordable_test.cc
130133
internal/resource_detector_impl_test.cc
131134
internal/time_series_test.cc

google/cloud/opentelemetry/google_cloud_cpp_opentelemetry.bzl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
google_cloud_cpp_opentelemetry_hdrs = [
2020
"configure_basic_tracing.h",
2121
"internal/monitored_resource.h",
22+
"internal/monitoring_exporter.h",
2223
"internal/recordable.h",
2324
"internal/resource_detector_impl.h",
2425
"internal/time_series.h",
@@ -30,6 +31,7 @@ google_cloud_cpp_opentelemetry_hdrs = [
3031
google_cloud_cpp_opentelemetry_srcs = [
3132
"configure_basic_tracing.cc",
3233
"internal/monitored_resource.cc",
34+
"internal/monitoring_exporter.cc",
3335
"internal/recordable.cc",
3436
"internal/resource_detector_impl.cc",
3537
"internal/time_series.cc",
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "google/cloud/opentelemetry/internal/monitoring_exporter.h"
16+
#include "google/cloud/monitoring/v3/metric_connection.h"
17+
#include "google/cloud/opentelemetry/internal/time_series.h"
18+
#include "google/cloud/internal/absl_str_cat_quiet.h"
19+
#include "google/cloud/internal/noexcept_action.h"
20+
#include "google/cloud/log.h"
21+
#include "google/cloud/project.h"
22+
#include <memory>
23+
24+
namespace google {
25+
namespace cloud {
26+
namespace otel_internal {
27+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
28+
namespace {
29+
30+
std::string FormatProjectFullName(std::string const& project) {
31+
return absl::StrCat("projects/", project);
32+
}
33+
34+
} // namespace
35+
36+
MonitoringExporter::MonitoringExporter(
37+
std::shared_ptr<monitoring_v3::MetricServiceConnection> conn,
38+
otel_internal::MonitoredResourceFromDataFn dynamic_resource_fn,
39+
otel_internal::ResourceFilterDataFn resource_filter_fn,
40+
Options const& options)
41+
: client_(std::move(conn)),
42+
formatter_(options.get<otel::MetricNameFormatterOption>()),
43+
use_service_time_series_(options.get<otel::ServiceTimeSeriesOption>()),
44+
mr_proto_(internal::FetchOption<otel::MonitoredResourceOption>(options)),
45+
dynamic_resource_fn_(std::move(dynamic_resource_fn)),
46+
resource_filter_fn_(std::move(resource_filter_fn)) {}
47+
48+
MonitoringExporter::MonitoringExporter(
49+
Project project,
50+
std::shared_ptr<monitoring_v3::MetricServiceConnection> conn,
51+
Options const& options)
52+
: MonitoringExporter(std::move(conn), nullptr, nullptr, options) {
53+
project_ = std::move(project);
54+
}
55+
56+
opentelemetry::sdk::common::ExportResult MonitoringExporter::Export(
57+
opentelemetry::sdk::metrics::ResourceMetrics const& data) noexcept {
58+
auto result =
59+
internal::NoExceptAction<opentelemetry::sdk::common::ExportResult>(
60+
[&] { return ExportImpl(data); });
61+
if (result) return *std::move(result);
62+
GCP_LOG(WARNING) << "Exception thrown while exporting metrics.";
63+
return opentelemetry::sdk::common::ExportResult::kFailure;
64+
}
65+
66+
opentelemetry::sdk::common::ExportResult MonitoringExporter::SendRequests(
67+
std::vector<google::monitoring::v3::CreateTimeSeriesRequest> const&
68+
requests) {
69+
auto result = opentelemetry::sdk::common::ExportResult::kSuccess;
70+
for (auto const& request : requests) {
71+
auto status = use_service_time_series_
72+
? client_.CreateServiceTimeSeries(request)
73+
: client_.CreateTimeSeries(request);
74+
if (status.ok()) continue;
75+
GCP_LOG(WARNING) << "Cloud Monitoring Export failed with status=" << status;
76+
// Ultimately, we can only report one error, even though we may send
77+
// multiple RPCs. If *all* failures are `kInvalidArgument` we should
78+
// report that. Otherwise, we will report a generic failure.
79+
if (status.code() == StatusCode::kInvalidArgument &&
80+
result == opentelemetry::sdk::common::ExportResult::kSuccess) {
81+
result =
82+
opentelemetry::sdk::common::ExportResult::kFailureInvalidArgument;
83+
} else if (status.code() != StatusCode::kInvalidArgument) {
84+
result = opentelemetry::sdk::common::ExportResult::kFailure;
85+
}
86+
}
87+
return result;
88+
}
89+
90+
opentelemetry::sdk::common::ExportResult MonitoringExporter::ExportImpl(
91+
opentelemetry::sdk::metrics::ResourceMetrics const& data) {
92+
opentelemetry::sdk::common::ExportResult result =
93+
opentelemetry::sdk::common::ExportResult::kSuccess;
94+
if (otel_internal::IsEmptyTimeSeries(data)) {
95+
GCP_LOG(INFO) << "Cloud Monitoring Export skipped. No data.";
96+
// Return early to save the littlest bit of processing.
97+
return result;
98+
}
99+
100+
std::vector<google::monitoring::v3::CreateTimeSeriesRequest> requests;
101+
if (dynamic_resource_fn_) {
102+
auto tss_map = otel_internal::ToTimeSeriesWithResources(
103+
data, formatter_, resource_filter_fn_, dynamic_resource_fn_);
104+
for (auto& tss : tss_map) {
105+
requests = otel_internal::ToRequests(FormatProjectFullName(tss.first),
106+
std::move(tss.second));
107+
result = SendRequests(requests);
108+
}
109+
} else {
110+
auto tss = otel_internal::ToTimeSeries(data, formatter_);
111+
auto mr = otel_internal::ToMonitoredResource(data, mr_proto_);
112+
requests =
113+
otel_internal::ToRequests(project_->FullName(), mr, std::move(tss));
114+
result = SendRequests(requests);
115+
}
116+
return result;
117+
}
118+
119+
Options DefaultOptions(Options o) {
120+
if (!o.has<otel::MetricNameFormatterOption>()) {
121+
o.set<otel::MetricNameFormatterOption>([](std::string s) {
122+
return "workload.googleapis.com/" + std::move(s);
123+
});
124+
}
125+
return o;
126+
}
127+
128+
std::unique_ptr<opentelemetry::sdk::metrics::PushMetricExporter>
129+
MakeMonitoringExporter(MonitoredResourceFromDataFn dynamic_resource_fn,
130+
ResourceFilterDataFn resource_filter_fn,
131+
Options options) {
132+
auto connection = monitoring_v3::MakeMetricServiceConnection(options);
133+
options = DefaultOptions(std::move(options));
134+
return std::make_unique<otel_internal::MonitoringExporter>(
135+
std::move(connection), std::move(dynamic_resource_fn),
136+
std::move(resource_filter_fn), std::move(options));
137+
}
138+
139+
std::unique_ptr<opentelemetry::sdk::metrics::PushMetricExporter>
140+
MakeMonitoringExporter(
141+
MonitoredResourceFromDataFn dynamic_resource_fn,
142+
ResourceFilterDataFn resource_filter_fn,
143+
std::shared_ptr<monitoring_v3::MetricServiceConnection> conn,
144+
Options options) {
145+
options = DefaultOptions(std::move(options));
146+
return std::make_unique<otel_internal::MonitoringExporter>(
147+
std::move(conn), std::move(dynamic_resource_fn),
148+
std::move(resource_filter_fn), std::move(options));
149+
}
150+
151+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
152+
} // namespace otel_internal
153+
} // namespace cloud
154+
} // namespace google
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_OPENTELEMETRY_INTERNAL_MONITORING_EXPORTER_H
16+
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_OPENTELEMETRY_INTERNAL_MONITORING_EXPORTER_H
17+
18+
#include "google/cloud/monitoring/v3/metric_client.h"
19+
#include "google/cloud/monitoring/v3/metric_connection.h"
20+
#include "google/cloud/opentelemetry/monitoring_exporter.h"
21+
#include "google/cloud/project.h"
22+
#include "google/cloud/version.h"
23+
#include "absl/types/optional.h"
24+
#include <opentelemetry/sdk/metrics/data/metric_data.h>
25+
#include <opentelemetry/sdk/metrics/push_metric_exporter.h>
26+
#include <chrono>
27+
#include <functional>
28+
#include <memory>
29+
#include <string>
30+
31+
namespace google {
32+
namespace cloud {
33+
namespace otel_internal {
34+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
35+
36+
// For use with dynamic monitored resources, this function constructs the
37+
// correct MonitoredResource from the PointDataAttributes passed in. This
38+
// function is called in ToTimeSeriesWithResources.
39+
using MonitoredResourceFromDataFn =
40+
std::function<std::pair<std::string, google::api::MonitoredResource>(
41+
opentelemetry::sdk::metrics::PointDataAttributes const&)>;
42+
43+
// For use with dynamic monitored resources, this function is used in ToMetric
44+
// to indicate which labels should be skipped when populating the labels field
45+
// of the google::api::Metric proto.
46+
using ResourceFilterDataFn = std::function<bool(std::string const&)>;
47+
48+
class MonitoringExporter final
49+
: public opentelemetry::sdk::metrics::PushMetricExporter {
50+
public:
51+
MonitoringExporter(
52+
std::shared_ptr<monitoring_v3::MetricServiceConnection> conn,
53+
otel_internal::MonitoredResourceFromDataFn dynamic_resource_fn,
54+
otel_internal::ResourceFilterDataFn resource_filter_fn,
55+
Options const& options);
56+
57+
MonitoringExporter(
58+
Project project,
59+
std::shared_ptr<monitoring_v3::MetricServiceConnection> conn,
60+
Options const& options);
61+
62+
opentelemetry::sdk::metrics::AggregationTemporality GetAggregationTemporality(
63+
opentelemetry::sdk::metrics::InstrumentType) const noexcept override {
64+
return opentelemetry::sdk::metrics::AggregationTemporality::kCumulative;
65+
}
66+
67+
opentelemetry::sdk::common::ExportResult Export(
68+
opentelemetry::sdk::metrics::ResourceMetrics const& data) noexcept
69+
override;
70+
71+
bool ForceFlush(std::chrono::microseconds) noexcept override { return false; }
72+
73+
bool Shutdown(std::chrono::microseconds) noexcept override { return true; }
74+
75+
private:
76+
opentelemetry::sdk::common::ExportResult ExportImpl(
77+
opentelemetry::sdk::metrics::ResourceMetrics const& data);
78+
opentelemetry::sdk::common::ExportResult SendRequests(
79+
std::vector<google::monitoring::v3::CreateTimeSeriesRequest> const&
80+
requests);
81+
82+
absl::optional<Project> project_;
83+
monitoring_v3::MetricServiceClient client_;
84+
otel::MetricNameFormatterOption::Type formatter_;
85+
bool use_service_time_series_;
86+
absl::optional<google::api::MonitoredResource> mr_proto_;
87+
otel_internal::MonitoredResourceFromDataFn dynamic_resource_fn_;
88+
otel_internal::ResourceFilterDataFn resource_filter_fn_;
89+
};
90+
91+
Options DefaultOptions(Options o);
92+
93+
std::unique_ptr<opentelemetry::sdk::metrics::PushMetricExporter>
94+
MakeMonitoringExporter(MonitoredResourceFromDataFn dynamic_resource_fn,
95+
ResourceFilterDataFn resource_filter_fn,
96+
Options options = {});
97+
98+
std::unique_ptr<opentelemetry::sdk::metrics::PushMetricExporter>
99+
MakeMonitoringExporter(
100+
MonitoredResourceFromDataFn dynamic_resource_fn,
101+
ResourceFilterDataFn resource_filter_fn,
102+
std::shared_ptr<monitoring_v3::MetricServiceConnection> conn,
103+
Options options = {});
104+
105+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
106+
} // namespace otel_internal
107+
} // namespace cloud
108+
} // namespace google
109+
110+
#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_OPENTELEMETRY_INTERNAL_MONITORING_EXPORTER_H

0 commit comments

Comments
 (0)