1616#include " google/cloud/bigtable/options.h"
1717#include " google/cloud/bigtable/testing/table_integration_test.h"
1818#include " google/cloud/credentials.h"
19+ #include " google/cloud/internal/getenv.h"
1920#include " google/cloud/testing_util/scoped_environment.h"
2021#include " google/cloud/testing_util/status_matchers.h"
2122#include " absl/strings/str_cat.h"
2829namespace google {
2930namespace cloud {
3031namespace bigtable {
31- namespace testing {
32+ GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
3233namespace {
3334
3435using ::google::cloud::bigtable::testing::TableTestEnvironment;
3536using ::google::cloud::testing_util::ScopedEnvironment;
37+ using ::testing::AllOf;
38+ using ::testing::Contains;
39+ using ::testing::Each;
3640using ::testing::Eq;
41+ using ::testing::ExplainMatchResult;
42+ using ::testing::HasSubstr;
3743using ::testing::IsEmpty;
3844using ::testing::Not;
45+ using ::testing::Property;
3946using ::testing::StartsWith;
4047
48+ MATCHER_P (MetricType, matcher, " " ) {
49+ return ExplainMatchResult (matcher, arg.metric ().type (), result_listener);
50+ }
51+
52+ MATCHER_P (ResourceType, matcher, " " ) {
53+ return ExplainMatchResult (matcher, arg.resource ().type (), result_listener);
54+ }
55+
56+ MATCHER_P2 (HasMetricLabel, key, val_matcher, " " ) {
57+ auto const & labels = arg.metric ().labels ();
58+ auto it = labels.find (key);
59+ if (it == labels.end ()) {
60+ *result_listener << " no metric label '" << key << " '" ;
61+ return false ;
62+ }
63+ return ExplainMatchResult (val_matcher, it->second , result_listener);
64+ }
65+
66+ MATCHER_P2 (HasResourceLabel, key, val_matcher, " " ) {
67+ auto const & labels = arg.resource ().labels ();
68+ auto it = labels.find (key);
69+ if (it == labels.end ()) {
70+ *result_listener << " no resource label '" << key << " '" ;
71+ return false ;
72+ }
73+ return ExplainMatchResult (val_matcher, it->second , result_listener);
74+ }
75+
76+ MATCHER_P (HasTimeSeries, ts_matcher, " " ) {
77+ return ExplainMatchResult (Contains (ts_matcher), arg.time_series (),
78+ result_listener);
79+ }
80+
4181class ObservabilityIntegrationTest
4282 : public ::google::cloud::bigtable::testing::TableIntegrationTest {
4383 protected:
@@ -103,11 +143,19 @@ TEST_F(ObservabilityIntegrationTest, VerifyOperationAndAttemptMetrics) {
103143 std::chrono::seconds (5 ));
104144
105145 auto const table_id = TableTestEnvironment::table_id ();
146+ bool const is_dynamic = google::cloud::internal::GetEnv (
147+ " GOOGLE_CLOUD_CPP_BIGTABLE_TESTING_CHANNEL_POOL" )
148+ .value_or (" " ) == " dynamic" ;
106149
107150 // Add scoped connection to ensure metrics are flushed on destruction.
108151 {
109- auto conn = MakeDataConnection (
110- {InstanceResource (Project (project_id ()), instance_id ())}, options);
152+ std::shared_ptr<DataConnection> conn;
153+ if (is_dynamic) {
154+ conn = MakeDataConnection (
155+ {InstanceResource (Project (project_id ()), instance_id ())}, options);
156+ } else {
157+ conn = MakeDataConnection (options);
158+ }
111159 auto table = Table (std::move (conn),
112160 TableResource (project_id (), instance_id (), table_id));
113161
@@ -126,52 +174,38 @@ TEST_F(ObservabilityIntegrationTest, VerifyOperationAndAttemptMetrics) {
126174 }
127175
128176 auto recorded = collector_service_.recorded_metrics ();
129- ASSERT_FALSE (recorded.empty ());
130-
131- bool found_operation_latencies = false ;
132- bool found_attempt_latencies = false ;
133-
134- for (auto const & req : recorded) {
135- EXPECT_EQ (req.name (), absl::StrCat (" projects/" , project_id ()));
136-
137- for (auto const & ts : req.time_series ()) {
138- auto const & metric_type = ts.metric ().type ();
139- EXPECT_THAT (metric_type,
140- StartsWith (" bigtable.googleapis.com/internal/client/" ));
141-
142- if (metric_type.find (" operation_latencies" ) != std::string::npos) {
143- found_operation_latencies = true ;
144- }
145- if (metric_type.find (" attempt_latencies" ) != std::string::npos) {
146- found_attempt_latencies = true ;
147- }
148-
149- auto const & labels = ts.resource ().labels ();
150- auto project_it = labels.find (" project_id" );
151- if (project_it != labels.end ()) {
152- EXPECT_EQ (project_it->second , project_id ());
153- }
154- auto instance_it = labels.find (" instance" );
155- if (instance_it != labels.end ()) {
156- EXPECT_EQ (instance_it->second , instance_id ());
157- }
158- auto table_it = labels.find (" table" );
159- if (table_it != labels.end ()) {
160- EXPECT_EQ (table_it->second , table_id);
161- }
162- auto zone_it = labels.find (" zone" );
163- if (zone_it != labels.end () && !TableTestEnvironment::zone_a ().empty ()) {
164- std::vector<absl::string_view> parts =
165- absl::StrSplit (TableTestEnvironment::zone_a (), ' -' );
166- auto prefix = parts.size () >= 2 ? absl::StrCat (parts[0 ], " -" , parts[1 ])
167- : TableTestEnvironment::zone_a ();
168- EXPECT_THAT (zone_it->second , StartsWith (prefix));
169- }
170- }
177+ ASSERT_THAT (recorded, Not (IsEmpty ()));
178+ EXPECT_THAT (
179+ recorded,
180+ Each (Property (&google::monitoring::v3::CreateTimeSeriesRequest::name,
181+ Eq (absl::StrCat (" projects/" , project_id ())))));
182+
183+ auto has_resource_labels = AllOf (HasResourceLabel (" project_id" , project_id ()),
184+ HasResourceLabel (" instance" , instance_id ()),
185+ HasResourceLabel (" table" , table_id));
186+
187+ if (!TableTestEnvironment::zone_a ().empty ()) {
188+ std::vector<absl::string_view> parts =
189+ absl::StrSplit (TableTestEnvironment::zone_a (), ' -' );
190+ auto prefix = parts.size () >= 2 ? absl::StrCat (parts[0 ], " -" , parts[1 ])
191+ : TableTestEnvironment::zone_a ();
192+ EXPECT_THAT (recorded, Contains (HasTimeSeries (AllOf (
193+ MetricType (HasSubstr (" operation_latencies" )),
194+ has_resource_labels,
195+ HasResourceLabel (" zone" , StartsWith (prefix))))));
196+ EXPECT_THAT (recorded, Contains (HasTimeSeries (AllOf (
197+ MetricType (HasSubstr (" attempt_latencies" )),
198+ has_resource_labels,
199+ HasResourceLabel (" zone" , StartsWith (prefix))))));
200+ } else {
201+ EXPECT_THAT (recorded, Contains (HasTimeSeries (AllOf (
202+ MetricType (HasSubstr (" operation_latencies" )),
203+ has_resource_labels))));
204+ EXPECT_THAT (
205+ recorded,
206+ Contains (HasTimeSeries (AllOf (MetricType (HasSubstr (" attempt_latencies" )),
207+ has_resource_labels))));
171208 }
172-
173- EXPECT_TRUE (found_operation_latencies);
174- EXPECT_TRUE (found_attempt_latencies);
175209}
176210
177211TEST_F (ObservabilityIntegrationTest, VerifyOutstandingRpcsMetric) {
@@ -191,89 +225,58 @@ TEST_F(ObservabilityIntegrationTest, VerifyOutstandingRpcsMetric) {
191225 std::chrono::seconds (5 ));
192226
193227 auto const table_id = TableTestEnvironment::table_id ();
228+ bool const is_dynamic = google::cloud::internal::GetEnv (
229+ " GOOGLE_CLOUD_CPP_BIGTABLE_TESTING_CHANNEL_POOL" )
230+ .value_or (" " ) == " dynamic" ;
231+ std::string const expected_lb_policy =
232+ is_dynamic ? " RANDOM_TWO_LEAST_USED" : " ROUND_ROBIN" ;
194233
195- auto verify_pool_metric = [this , &options, &table_id](
196- bool dynamic_pool,
197- std::string const & expected_lb_policy) {
198- collector_service_.Clear ();
199- {
200- std::shared_ptr<DataConnection> conn;
201- if (dynamic_pool) {
202- conn = MakeDataConnection (
203- {InstanceResource (Project (project_id ()), instance_id ())}, options);
204- } else {
205- conn = MakeDataConnection (options);
206- }
207- auto table = Table (std::move (conn),
208- TableResource (project_id (), instance_id (), table_id));
209-
210- std::string const row_key = " observability-rpc-" + expected_lb_policy;
211- std::vector<Cell> expected{{row_key, " family4" , " c0" , 1000 , " v1000" },
212- {row_key, " family4" , " c1" , 2000 , " v2000" }};
213-
214- // Perform mutations and read calls
215- Apply (table, row_key, expected);
216- auto actual = ReadRows (table, Filter::RowKeysRegex (row_key));
217- CheckEqualUnordered (expected, actual);
218-
219- // Wait for the periodic 5-second exporter background thread to flush
220- // metrics while conn is active
221- std::this_thread::sleep_for (std::chrono::seconds (6 ));
234+ collector_service_.Clear ();
235+ {
236+ std::shared_ptr<DataConnection> conn;
237+ if (is_dynamic) {
238+ conn = MakeDataConnection (
239+ {InstanceResource (Project (project_id ()), instance_id ())}, options);
240+ } else {
241+ conn = MakeDataConnection (options);
222242 }
243+ auto table = Table (std::move (conn),
244+ TableResource (project_id (), instance_id (), table_id));
223245
224- auto recorded = collector_service_.recorded_metrics ();
225- ASSERT_THAT (recorded, Not (IsEmpty ()));
226-
227- bool found_outstanding_rpcs = false ;
228- for (auto const & req : recorded) {
229- EXPECT_THAT (req.name (), Eq (absl::StrCat (" projects/" , project_id ())));
230-
231- for (auto const & ts : req.time_series ()) {
232- auto const & metric_type = ts.metric ().type ();
233- EXPECT_THAT (metric_type,
234- StartsWith (" bigtable.googleapis.com/internal/client/" ));
235-
236- if (metric_type.find (" connection_pool/outstanding_rpcs" ) !=
237- std::string::npos) {
238- found_outstanding_rpcs = true ;
239- EXPECT_THAT (ts.resource ().type (),
240- Eq (" bigtable.googleapis.com/Client" ));
241-
242- auto const & res_labels = ts.resource ().labels ();
243- auto project_it = res_labels.find (" project_id" );
244- if (project_it != res_labels.end ()) {
245- EXPECT_THAT (project_it->second , Eq (project_id ()));
246- }
247- auto instance_it = res_labels.find (" instance" );
248- if (instance_it != res_labels.end ()) {
249- EXPECT_THAT (instance_it->second , Eq (instance_id ()));
250- }
251-
252- auto const & metric_labels = ts.metric ().labels ();
253- auto lb_policy_it = metric_labels.find (" channel_pool_lb_policy" );
254- ASSERT_THAT (lb_policy_it, Not (Eq (metric_labels.end ())));
255- EXPECT_THAT (lb_policy_it->second , Eq (expected_lb_policy));
256-
257- auto transport_it = metric_labels.find (" transport_type" );
258- ASSERT_THAT (transport_it, Not (Eq (metric_labels.end ())));
259- EXPECT_THAT (transport_it->second , Not (IsEmpty ()));
260-
261- auto streaming_it = metric_labels.find (" streaming" );
262- ASSERT_THAT (streaming_it, Not (Eq (metric_labels.end ())));
263- EXPECT_THAT (streaming_it->second , Not (IsEmpty ()));
264- }
265- }
266- }
267- EXPECT_TRUE (found_outstanding_rpcs)
268- << " Failed to find outstanding_rpcs metric for " << expected_lb_policy;
269- };
246+ std::string const row_key = " observability-rpc-" + expected_lb_policy;
247+ std::vector<Cell> expected{{row_key, " family4" , " c0" , 1000 , " v1000" },
248+ {row_key, " family4" , " c1" , 2000 , " v2000" }};
249+
250+ // Perform mutations and read calls
251+ Apply (table, row_key, expected);
252+ auto actual = ReadRows (table, Filter::RowKeysRegex (row_key));
253+ CheckEqualUnordered (expected, actual);
254+
255+ // Wait for the periodic 5-second exporter background thread to flush
256+ // metrics while conn is active
257+ std::this_thread::sleep_for (std::chrono::seconds (6 ));
258+ }
270259
271- verify_pool_metric (/* dynamic_pool=*/ true , " RANDOM_TWO_LEAST_USED" );
272- verify_pool_metric (/* dynamic_pool=*/ false , " ROUND_ROBIN" );
260+ auto recorded = collector_service_.recorded_metrics ();
261+ ASSERT_THAT (recorded, Not (IsEmpty ()));
262+ EXPECT_THAT (
263+ recorded,
264+ Each (Property (&google::monitoring::v3::CreateTimeSeriesRequest::name,
265+ Eq (absl::StrCat (" projects/" , project_id ())))));
266+
267+ EXPECT_THAT (recorded,
268+ Contains (HasTimeSeries (AllOf (
269+ MetricType (HasSubstr (" connection_pool/outstanding_rpcs" )),
270+ ResourceType (" bigtable.googleapis.com/Client" ),
271+ HasResourceLabel (" project_id" , project_id ()),
272+ HasResourceLabel (" instance" , instance_id ()),
273+ HasMetricLabel (" channel_pool_lb_policy" , expected_lb_policy),
274+ HasMetricLabel (" transport_type" , Not (IsEmpty ())),
275+ HasMetricLabel (" streaming" , Not (IsEmpty ()))))));
273276}
274277
275278} // namespace
276- } // namespace testing
279+ GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
277280} // namespace bigtable
278281} // namespace cloud
279282} // namespace google
0 commit comments