Skip to content

Commit 6d79425

Browse files
feat(storage): add GrpcMetricsExcludedLabelsOption for gRPC metrics label filtering (#15735)
1 parent 6814321 commit 6d79425

File tree

7 files changed

+197
-1
lines changed

7 files changed

+197
-1
lines changed

google/cloud/opentelemetry/internal/monitoring_exporter.cc

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,41 @@ std::string FormatProjectFullName(std::string const& project) {
3131
return absl::StrCat("projects/", project);
3232
}
3333

34+
otel_internal::ResourceFilterDataFn MakeResourceFilterFn(
35+
Options const& options) {
36+
if (!options.has<otel_internal::ResourceFilterDataFnOption>()) {
37+
return nullptr;
38+
}
39+
40+
// Get the metric labels set to be excluded.
41+
auto const& excluded =
42+
options.get<otel_internal::ResourceFilterDataFnOption>();
43+
if (excluded.empty()) return nullptr;
44+
45+
// Capture by value to avoid dangling reference in the lambda.
46+
return [excluded = std::move(excluded)](std::string const& key) -> bool {
47+
return excluded.count(key) > 0;
48+
};
49+
}
50+
51+
otel_internal::MonitoredResourceFromDataFn MakeDynamicResourceFn(
52+
Options const& options, absl::optional<Project> const& project,
53+
absl::optional<google::api::MonitoredResource> const& mr_proto) {
54+
if (!options.has<otel_internal::ResourceFilterDataFnOption>()) {
55+
return nullptr;
56+
}
57+
58+
// `resource_filter_fn_` and `dynamic_resource_fn_` are meant to be used as a
59+
// pair. Here we have a filter but no dynamic function, create a default one
60+
// that returns the same project and monitored resource for all data points.
61+
auto project_id = project->project_id();
62+
auto monitored_resource = mr_proto.value_or(google::api::MonitoredResource{});
63+
return [project_id, monitored_resource](
64+
opentelemetry::sdk::metrics::PointDataAttributes const&) {
65+
return std::make_pair(project_id, monitored_resource);
66+
};
67+
}
68+
3469
} // namespace
3570

3671
MonitoringExporter::MonitoringExporter(
@@ -51,6 +86,8 @@ MonitoringExporter::MonitoringExporter(
5186
Options const& options)
5287
: MonitoringExporter(std::move(conn), nullptr, nullptr, options) {
5388
project_ = std::move(project);
89+
resource_filter_fn_ = MakeResourceFilterFn(options);
90+
dynamic_resource_fn_ = MakeDynamicResourceFn(options, project_, mr_proto_);
5491
}
5592

5693
opentelemetry::sdk::common::ExportResult MonitoringExporter::Export(

google/cloud/opentelemetry/internal/monitoring_exporter.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ using MonitoredResourceFromDataFn =
4545
// of the google::api::Metric proto.
4646
using ResourceFilterDataFn = std::function<bool(std::string const&)>;
4747

48+
// Filter resource labels. A set of OpenTelemetry resource attribute keys to
49+
// exclude from metric labels when exporting metrics.
50+
struct ResourceFilterDataFnOption {
51+
using Type = std::set<std::string>;
52+
};
53+
4854
class MonitoringExporter final
4955
: public opentelemetry::sdk::metrics::PushMetricExporter {
5056
public:

google/cloud/opentelemetry/internal/monitoring_exporter_test.cc

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,40 @@ TEST(MonitoringExporter, ExportSuccess) {
159159
EXPECT_EQ(result, opentelemetry::sdk::common::ExportResult::kSuccess);
160160
}
161161

162+
TEST(MonitoringExporterTest, MakeFilterNoOption) {
163+
auto mock =
164+
std::make_shared<monitoring_v3_mocks::MockMetricServiceConnection>();
165+
Options options;
166+
167+
auto exporter = std::make_unique<MonitoringExporter>(Project("test-project"),
168+
mock, options);
169+
EXPECT_NE(exporter, nullptr);
170+
}
171+
172+
TEST(MonitoringExporterTest, MakeFilterEmptySet) {
173+
auto mock =
174+
std::make_shared<monitoring_v3_mocks::MockMetricServiceConnection>();
175+
Options options;
176+
options.set<otel_internal::ResourceFilterDataFnOption>(
177+
std::set<std::string>{});
178+
179+
auto exporter = std::make_unique<MonitoringExporter>(Project("test-project"),
180+
mock, options);
181+
EXPECT_NE(exporter, nullptr);
182+
}
183+
184+
TEST(MonitoringExporterTest, MakeFilterWithExcludedKeys) {
185+
auto mock =
186+
std::make_shared<monitoring_v3_mocks::MockMetricServiceConnection>();
187+
Options options;
188+
std::set<std::string> excluded{"service_name", "service_version"};
189+
options.set<otel_internal::ResourceFilterDataFnOption>(excluded);
190+
191+
auto exporter = std::make_unique<MonitoringExporter>(Project("test-project"),
192+
mock, options);
193+
EXPECT_NE(exporter, nullptr);
194+
}
195+
162196
} // namespace
163197
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
164198
} // namespace otel_internal

google/cloud/storage/grpc_plugin.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,27 @@ struct GrpcMetricsExportTimeoutOption {
131131
using Type = std::chrono::seconds;
132132
};
133133

134+
/**
135+
* gRPC telemetry excluded labels.
136+
*
137+
* A set of OpenTelemetry resource attribute keys to exclude from metric labels
138+
* when exporting gRPC telemetry. For example, to exclude the `service.name`
139+
* label, configure the option with `{"service_name"}`.
140+
*
141+
* @par Example: Exclude specific labels from telemetry
142+
* @code
143+
* namespace gcs_ex = google::cloud::storage_experimental;
144+
* auto client = google::cloud::storage::MakeGrpcClient(
145+
* google::cloud::Options{}
146+
* .set<gcs_ex::EnableGrpcMetricsOption>(true)
147+
* .set<gcs_ex::GrpcMetricsExcludedLabelsOption>(
148+
* std::set<std::string>{"service_name", "service_version"}));
149+
* @endcode
150+
*/
151+
struct GrpcMetricsExcludedLabelsOption {
152+
using Type = std::set<std::string>;
153+
};
154+
134155
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
135156
} // namespace storage_experimental
136157
} // namespace cloud

google/cloud/storage/grpc_plugin_test.cc

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,40 @@ TEST(GrpcPluginTest, BackwardsCompatibilityShims) {
138138
}
139139
#include "google/cloud/internal/diagnostics_pop.inc"
140140

141+
TEST(GrpcPluginTest, GrpcMetricsExcludedLabelsOption) {
142+
auto const expected =
143+
std::set<std::string>{"service_name", "service_version", "custom_label"};
144+
auto opts =
145+
google::cloud::Options{}
146+
.set<storage_experimental::GrpcMetricsExcludedLabelsOption>(expected);
147+
148+
EXPECT_EQ(expected,
149+
opts.get<storage_experimental::GrpcMetricsExcludedLabelsOption>());
150+
}
151+
152+
TEST(GrpcPluginTest, GrpcMetricsExcludedLabelsOptionEmpty) {
153+
auto const expected = std::set<std::string>{};
154+
auto opts =
155+
google::cloud::Options{}
156+
.set<storage_experimental::GrpcMetricsExcludedLabelsOption>(expected);
157+
158+
EXPECT_TRUE(opts.get<storage_experimental::GrpcMetricsExcludedLabelsOption>()
159+
.empty());
160+
}
161+
162+
TEST(GrpcPluginTest, GrpcMetricsExcludedLabelsOptionSingle) {
163+
auto const expected = std::set<std::string>{"service_name"};
164+
auto opts =
165+
google::cloud::Options{}
166+
.set<storage_experimental::GrpcMetricsExcludedLabelsOption>(expected);
167+
168+
EXPECT_EQ(
169+
1,
170+
opts.get<storage_experimental::GrpcMetricsExcludedLabelsOption>().size());
171+
EXPECT_EQ(expected,
172+
opts.get<storage_experimental::GrpcMetricsExcludedLabelsOption>());
173+
}
174+
141175
} // namespace
142176
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
143177
} // namespace storage

google/cloud/storage/internal/grpc/metrics_exporter_impl.cc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
#include "google/cloud/storage/internal/grpc/metrics_exporter_impl.h"
1818
#include "google/cloud/monitoring/v3/metric_connection.h"
19-
#include "google/cloud/opentelemetry/monitoring_exporter.h"
19+
#include "google/cloud/opentelemetry/internal/monitoring_exporter.h"
2020
#include "google/cloud/storage/grpc_plugin.h"
2121
#include "google/cloud/storage/internal/grpc/metrics_exporter_options.h"
2222
#include "google/cloud/storage/internal/grpc/metrics_meter_provider.h"
@@ -91,6 +91,11 @@ absl::optional<ExporterConfig> MakeMeterProviderConfig(
9191
if (!project) return absl::nullopt;
9292

9393
auto exporter_options = MetricsExporterOptions(*project, resource);
94+
if (options.has<storage_experimental::GrpcMetricsExcludedLabelsOption>()) {
95+
exporter_options.set<otel_internal::ResourceFilterDataFnOption>(
96+
options.get<storage_experimental::GrpcMetricsExcludedLabelsOption>());
97+
}
98+
9499
auto exporter_connection_options = MetricsExporterConnectionOptions(options);
95100
return ExporterConfig{std::move(*project), std::move(exporter_options),
96101
std::move(exporter_connection_options),

google/cloud/storage/internal/grpc/metrics_exporter_impl_test.cc

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#ifdef GOOGLE_CLOUD_CPP_STORAGE_WITH_OTEL_METRICS
1616

1717
#include "google/cloud/storage/internal/grpc/metrics_exporter_impl.h"
18+
#include "google/cloud/opentelemetry/internal/monitoring_exporter.h"
1819
#include "google/cloud/opentelemetry/monitoring_exporter.h"
1920
#include "google/cloud/storage/grpc_plugin.h"
2021
#include "google/cloud/storage/internal/grpc/default_options.h"
@@ -156,6 +157,64 @@ TEST(GrpcMetricsExporter, ReaderOptionsAreSetFromConfig) {
156157
std::chrono::milliseconds(expected_timeout));
157158
}
158159

160+
TEST(MakeMeterProviderConfigTest, NoExcludedLabels) {
161+
auto resource = opentelemetry::sdk::resource::Resource::Create(
162+
{{"service.name", "test-service"}, {"service.version", "1.0.0"}});
163+
164+
Options options;
165+
options.set<storage_experimental::EnableGrpcMetricsOption>(true);
166+
options.set<storage::ProjectIdOption>("test-project");
167+
168+
auto config = MakeMeterProviderConfig(resource, options);
169+
170+
ASSERT_TRUE(config.has_value());
171+
EXPECT_FALSE(config->exporter_options
172+
.has<otel_internal::ResourceFilterDataFnOption>());
173+
}
174+
175+
TEST(MakeMeterProviderConfigTest, WithExcludedLabels) {
176+
auto resource = opentelemetry::sdk::resource::Resource::Create(
177+
{{"service.name", "test-service"}, {"service.version", "1.0.0"}});
178+
179+
std::set<std::string> excluded_labels{"service_name", "service_version"};
180+
Options options;
181+
options.set<storage_experimental::EnableGrpcMetricsOption>(true);
182+
options.set<storage::ProjectIdOption>("test-project");
183+
options.set<storage_experimental::GrpcMetricsExcludedLabelsOption>(
184+
excluded_labels);
185+
186+
auto config = MakeMeterProviderConfig(resource, options);
187+
188+
ASSERT_TRUE(config.has_value());
189+
EXPECT_TRUE(config->exporter_options
190+
.has<otel_internal::ResourceFilterDataFnOption>());
191+
192+
auto actual_excluded =
193+
config->exporter_options.get<otel_internal::ResourceFilterDataFnOption>();
194+
EXPECT_EQ(excluded_labels, actual_excluded);
195+
}
196+
197+
TEST(MakeMeterProviderConfigTest, EmptyExcludedLabels) {
198+
auto resource = opentelemetry::sdk::resource::Resource::Create(
199+
{{"service.name", "test-service"}});
200+
201+
Options options;
202+
options.set<storage_experimental::EnableGrpcMetricsOption>(true);
203+
options.set<storage::ProjectIdOption>("test-project");
204+
options.set<storage_experimental::GrpcMetricsExcludedLabelsOption>(
205+
std::set<std::string>{});
206+
207+
auto config = MakeMeterProviderConfig(resource, options);
208+
209+
ASSERT_TRUE(config.has_value());
210+
EXPECT_TRUE(config->exporter_options
211+
.has<otel_internal::ResourceFilterDataFnOption>());
212+
213+
auto actual_excluded =
214+
config->exporter_options.get<otel_internal::ResourceFilterDataFnOption>();
215+
EXPECT_TRUE(actual_excluded.empty());
216+
}
217+
159218
} // namespace
160219
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
161220
} // namespace storage_internal

0 commit comments

Comments
 (0)