diff --git a/google/cloud/bigtable/CMakeLists.txt b/google/cloud/bigtable/CMakeLists.txt index 47fc356283896..54fb6f3129cea 100644 --- a/google/cloud/bigtable/CMakeLists.txt +++ b/google/cloud/bigtable/CMakeLists.txt @@ -222,6 +222,8 @@ add_library( internal/rpc_policy_parameters.inc internal/stub_manager.cc internal/stub_manager.h + internal/table_schema_metrics.cc + internal/table_schema_metrics.h internal/traced_row_reader.cc internal/traced_row_reader.h internal/tuple_utils.h @@ -485,6 +487,7 @@ if (BUILD_TESTING) internal/rate_limiter_test.cc internal/retry_traits_test.cc internal/stub_manager_test.cc + internal/table_schema_metrics_test.cc internal/traced_row_reader_test.cc internal/tuple_utils_test.cc mocks/mock_row_reader_test.cc diff --git a/google/cloud/bigtable/bigtable_client_unit_tests.bzl b/google/cloud/bigtable/bigtable_client_unit_tests.bzl index acb6542878348..8199423cbb6a8 100644 --- a/google/cloud/bigtable/bigtable_client_unit_tests.bzl +++ b/google/cloud/bigtable/bigtable_client_unit_tests.bzl @@ -68,6 +68,7 @@ bigtable_client_unit_tests = [ "internal/rate_limiter_test.cc", "internal/retry_traits_test.cc", "internal/stub_manager_test.cc", + "internal/table_schema_metrics_test.cc", "internal/traced_row_reader_test.cc", "internal/tuple_utils_test.cc", "mocks/mock_row_reader_test.cc", diff --git a/google/cloud/bigtable/google_cloud_cpp_bigtable.bzl b/google/cloud/bigtable/google_cloud_cpp_bigtable.bzl index 054d50d102fe4..c4f6776856180 100644 --- a/google/cloud/bigtable/google_cloud_cpp_bigtable.bzl +++ b/google/cloud/bigtable/google_cloud_cpp_bigtable.bzl @@ -112,6 +112,7 @@ google_cloud_cpp_bigtable_hdrs = [ "internal/rpc_policy_parameters.h", "internal/rpc_policy_parameters.inc", "internal/stub_manager.h", + "internal/table_schema_metrics.h", "internal/traced_row_reader.h", "internal/tuple_utils.h", "internal/unary_client_utils.h", @@ -221,6 +222,7 @@ google_cloud_cpp_bigtable_srcs = [ "internal/readrowsparser.cc", "internal/retry_traits.cc", "internal/stub_manager.cc", + "internal/table_schema_metrics.cc", "internal/traced_row_reader.cc", "mutation_batcher.cc", "mutations.cc", diff --git a/google/cloud/bigtable/internal/async_bulk_apply_test.cc b/google/cloud/bigtable/internal/async_bulk_apply_test.cc index 6abc774e651a4..4f7e7eb71c794 100644 --- a/google/cloud/bigtable/internal/async_bulk_apply_test.cc +++ b/google/cloud/bigtable/internal/async_bulk_apply_test.cc @@ -21,6 +21,7 @@ #include "google/cloud/internal/background_threads_impl.h" #ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS #include "google/cloud/bigtable/internal/metrics.h" +#include "google/cloud/bigtable/internal/table_schema_metrics.h" #include "google/cloud/testing_util/fake_clock.h" #endif #include "google/cloud/internal/opentelemetry.h" @@ -142,7 +143,7 @@ TEST_F(AsyncBulkApplyTest, NoMutations) { #ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS -class MockMetric : public Metric { +class MockMetric : public TableSchemaMetric { public: MOCK_METHOD(void, PreCall, (opentelemetry::context::Context const&, PreCallParams const&), @@ -162,7 +163,7 @@ class MockMetric : public Metric { (opentelemetry::context::Context const&, ElementDeliveryParams const&), (override)); - MOCK_METHOD(std::unique_ptr, clone, + MOCK_METHOD(std::unique_ptr, clone, (TableResourceLabels const& resource_labels, TableDataLabels const& data_labels), (const, override)); diff --git a/google/cloud/bigtable/internal/async_row_reader_test.cc b/google/cloud/bigtable/internal/async_row_reader_test.cc index f68a14c79d344..bb27eef4a7cbf 100644 --- a/google/cloud/bigtable/internal/async_row_reader_test.cc +++ b/google/cloud/bigtable/internal/async_row_reader_test.cc @@ -21,6 +21,7 @@ #include "google/cloud/internal/opentelemetry.h" #ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS #include "google/cloud/bigtable/internal/metrics.h" +#include "google/cloud/bigtable/internal/table_schema_metrics.h" #include "google/cloud/testing_util/fake_clock.h" #endif #include "google/cloud/testing_util/mock_backoff_policy.h" @@ -94,7 +95,7 @@ class AsyncRowReaderTest : public ::testing::Test { #ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS -class MockMetric : public Metric { +class MockMetric : public TableSchemaMetric { public: MOCK_METHOD(void, PreCall, (opentelemetry::context::Context const&, PreCallParams const&), @@ -114,7 +115,7 @@ class MockMetric : public Metric { (opentelemetry::context::Context const&, ElementDeliveryParams const&), (override)); - MOCK_METHOD(std::unique_ptr, clone, + MOCK_METHOD(std::unique_ptr, clone, (TableResourceLabels const& resource_labels, TableDataLabels const& data_labels), (const, override)); diff --git a/google/cloud/bigtable/internal/async_row_sampler_test.cc b/google/cloud/bigtable/internal/async_row_sampler_test.cc index 72081f9da0ca6..e00b0c1b43b6f 100644 --- a/google/cloud/bigtable/internal/async_row_sampler_test.cc +++ b/google/cloud/bigtable/internal/async_row_sampler_test.cc @@ -20,6 +20,7 @@ #include "google/cloud/internal/make_status.h" #ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS #include "google/cloud/bigtable/internal/metrics.h" +#include "google/cloud/bigtable/internal/table_schema_metrics.h" #include "google/cloud/testing_util/fake_clock.h" #endif #include "google/cloud/internal/opentelemetry.h" @@ -82,7 +83,7 @@ std::optional MakeResponse(std::string row_key, #ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS -class MockMetric : public Metric { +class MockMetric : public TableSchemaMetric { public: MOCK_METHOD(void, PreCall, (opentelemetry::context::Context const&, PreCallParams const&), @@ -102,7 +103,7 @@ class MockMetric : public Metric { (opentelemetry::context::Context const&, ElementDeliveryParams const&), (override)); - MOCK_METHOD(std::unique_ptr, clone, + MOCK_METHOD(std::unique_ptr, clone, (TableResourceLabels const& resource_labels, TableDataLabels const& data_labels), (const, override)); diff --git a/google/cloud/bigtable/internal/bulk_mutator_test.cc b/google/cloud/bigtable/internal/bulk_mutator_test.cc index 24cf199ee6f69..af80aff4f4ee8 100644 --- a/google/cloud/bigtable/internal/bulk_mutator_test.cc +++ b/google/cloud/bigtable/internal/bulk_mutator_test.cc @@ -17,6 +17,7 @@ #include "google/cloud/grpc_options.h" #ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS #include "google/cloud/bigtable/internal/metrics.h" +#include "google/cloud/bigtable/internal/table_schema_metrics.h" #include "google/cloud/testing_util/fake_clock.h" #endif #include "google/cloud/bigtable/testing/mock_bigtable_stub.h" @@ -83,7 +84,7 @@ v2::MutateRowsResponse MakeResponse( #ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS -class MockMetric : public bigtable_internal::Metric { +class MockMetric : public bigtable_internal::TableSchemaMetric { public: MOCK_METHOD(void, PreCall, (opentelemetry::context::Context const&, @@ -106,7 +107,7 @@ class MockMetric : public bigtable_internal::Metric { (opentelemetry::context::Context const&, bigtable_internal::ElementDeliveryParams const&), (override)); - MOCK_METHOD(std::unique_ptr, clone, + MOCK_METHOD(std::unique_ptr, clone, (bigtable_internal::TableResourceLabels const& resource_labels, bigtable_internal::TableDataLabels const& data_labels), (const, override)); diff --git a/google/cloud/bigtable/internal/data_connection_impl_test.cc b/google/cloud/bigtable/internal/data_connection_impl_test.cc index 287e3b1db0aaf..a4d5fb0e8fe91 100644 --- a/google/cloud/bigtable/internal/data_connection_impl_test.cc +++ b/google/cloud/bigtable/internal/data_connection_impl_test.cc @@ -20,6 +20,8 @@ #include "google/cloud/bigtable/internal/query_plan.h" #ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS #include "google/cloud/bigtable/internal/metrics.h" +#include "google/cloud/bigtable/internal/operation_context_factory.h" +#include "google/cloud/bigtable/internal/table_schema_metrics.h" #include "google/cloud/testing_util/fake_clock.h" #endif #include "google/cloud/bigtable/bound_query.h" @@ -340,7 +342,7 @@ class MockBackgroundThreads : public BackgroundThreads { #ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS -class MockMetric : public Metric { +class MockMetric : public TableSchemaMetric { public: MOCK_METHOD(void, PreCall, (opentelemetry::context::Context const&, PreCallParams const&), @@ -360,14 +362,14 @@ class MockMetric : public Metric { (opentelemetry::context::Context const&, ElementDeliveryParams const&), (override)); - MOCK_METHOD(std::unique_ptr, clone, + MOCK_METHOD(std::unique_ptr, clone, (TableResourceLabels const& resource_labels, TableDataLabels const& data_labels), (const, override)); }; // This class is a vehicle to get a MockMetric into the OperationContext object. -class CloningMetric : public Metric { +class CloningMetric : public TableSchemaMetric { public: explicit CloningMetric(std::unique_ptr metric) { metrics_.push_back(std::move(metric)); @@ -377,8 +379,8 @@ class CloningMetric : public Metric { std::reverse(metrics_.begin(), metrics_.end()); } - std::unique_ptr clone(TableResourceLabels const&, - TableDataLabels const&) const override { + std::unique_ptr clone( + TableResourceLabels const&, TableDataLabels const&) const override { auto m = std::move(metrics_.back()); metrics_.pop_back(); return m; @@ -512,7 +514,8 @@ TEST_F(DataConnectionTest, ApplySuccess) { auto mock = std::make_shared(); EXPECT_CALL(*mock, MutateRow) .WillOnce([](grpc::ClientContext&, Options const&, - v2::MutateRowRequest const& request, auto const&) { + v2::MutateRowRequest const& request, + bigtable_internal::OperationContext&) { EXPECT_EQ(kAppProfile, request.app_profile_id()); EXPECT_EQ(kTableName, request.table_name()); EXPECT_EQ("row", request.row_key()); @@ -544,7 +547,8 @@ TEST_F(DataConnectionTest, ApplyPermanentFailure) { auto mock = std::make_shared(); EXPECT_CALL(*mock, MutateRow) .WillOnce([](grpc::ClientContext&, Options const&, - v2::MutateRowRequest const& request, auto const&) { + v2::MutateRowRequest const& request, + bigtable_internal::OperationContext&) { EXPECT_EQ(kAppProfile, request.app_profile_id()); EXPECT_EQ(kTableName, request.table_name()); EXPECT_EQ("row", request.row_key()); @@ -576,14 +580,16 @@ TEST_F(DataConnectionTest, ApplyRetryThenSuccess) { auto mock = std::make_shared(); EXPECT_CALL(*mock, MutateRow) .WillOnce([](grpc::ClientContext&, Options const&, - v2::MutateRowRequest const& request, auto const&) { + v2::MutateRowRequest const& request, + bigtable_internal::OperationContext&) { EXPECT_EQ(kAppProfile, request.app_profile_id()); EXPECT_EQ(kTableName, request.table_name()); EXPECT_EQ("row", request.row_key()); return TransientError(); }) .WillOnce([](grpc::ClientContext&, Options const&, - v2::MutateRowRequest const& request, auto const&) { + v2::MutateRowRequest const& request, + bigtable_internal::OperationContext&) { EXPECT_EQ(kAppProfile, request.app_profile_id()); EXPECT_EQ(kTableName, request.table_name()); EXPECT_EQ("row", request.row_key()); @@ -616,7 +622,8 @@ TEST_F(DataConnectionTest, ApplyRetryExhausted) { EXPECT_CALL(*mock, MutateRow) .Times(kNumRetries + 1) .WillRepeatedly([](grpc::ClientContext&, Options const&, - v2::MutateRowRequest const& request, auto const&) { + v2::MutateRowRequest const& request, + bigtable_internal::OperationContext&) { EXPECT_EQ(kAppProfile, request.app_profile_id()); EXPECT_EQ(kTableName, request.table_name()); EXPECT_EQ("row", request.row_key()); @@ -641,7 +648,8 @@ TEST_F(DataConnectionTest, ApplyRetryIdempotency) { auto mock = std::make_shared(); EXPECT_CALL(*mock, MutateRow) .WillOnce([](grpc::ClientContext&, Options const&, - v2::MutateRowRequest const& request, auto const&) { + v2::MutateRowRequest const& request, + bigtable_internal::OperationContext&) { EXPECT_EQ(kAppProfile, request.app_profile_id()); EXPECT_EQ(kTableName, request.table_name()); EXPECT_EQ("row", request.row_key()); @@ -682,14 +690,16 @@ TEST_F(DataConnectionTest, ApplyBigtableCookie) { auto mock = std::make_shared(); EXPECT_CALL(*mock, MutateRow) .WillOnce([this](grpc::ClientContext& context, Options const&, - v2::MutateRowRequest const&, auto const&) { + v2::MutateRowRequest const&, + bigtable_internal::OperationContext&) { // Return a bigtable cookie in the first request. metadata_fixture_.SetServerMetadata( context, {{}, {{"x-goog-cbt-cookie-routing", "routing"}}}); return TransientError(); }) .WillOnce([this](grpc::ClientContext& context, Options const&, - v2::MutateRowRequest const&, auto const&) { + v2::MutateRowRequest const&, + bigtable_internal::OperationContext&) { // Verify that the next request includes the bigtable cookie from // above. auto headers = metadata_fixture_.GetMetadata(context); @@ -731,13 +741,15 @@ TEST_F(DataConnectionTest, AsyncApplySuccess) { auto mock = std::make_shared(); EXPECT_CALL(*mock, AsyncMutateRow) - .WillOnce([](google::cloud::CompletionQueue&, auto, auto, - v2::MutateRowRequest const& request, auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - EXPECT_EQ("row", request.row_key()); - return make_ready_future(make_status_or(v2::MutateRowResponse{})); - }); + .WillOnce( + [](google::cloud::CompletionQueue&, auto, auto, + v2::MutateRowRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + EXPECT_EQ("row", request.row_key()); + return make_ready_future(make_status_or(v2::MutateRowResponse{})); + }); auto conn = TestConnection(std::move(mock), std::move(factory)); internal::OptionsSpan span(CallOptions()); @@ -748,14 +760,16 @@ TEST_F(DataConnectionTest, AsyncApplySuccess) { TEST_F(DataConnectionTest, AsyncApplyPermanentFailure) { auto mock = std::make_shared(); EXPECT_CALL(*mock, AsyncMutateRow) - .WillOnce([](google::cloud::CompletionQueue&, auto, auto, - v2::MutateRowRequest const& request, auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - EXPECT_EQ("row", request.row_key()); - return make_ready_future>( - PermanentError()); - }); + .WillOnce( + [](google::cloud::CompletionQueue&, auto, auto, + v2::MutateRowRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + EXPECT_EQ("row", request.row_key()); + return make_ready_future>( + PermanentError()); + }); auto conn = TestConnection(std::move(mock)); internal::OptionsSpan span(CallOptions()); @@ -782,14 +796,16 @@ TEST_F(DataConnectionTest, AsyncApplyRetryExhausted) { auto mock = std::make_shared(); EXPECT_CALL(*mock, AsyncMutateRow) .Times(kNumRetries + 1) - .WillRepeatedly([](google::cloud::CompletionQueue&, auto, auto, - v2::MutateRowRequest const& request, auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - EXPECT_EQ("row", request.row_key()); - return make_ready_future>( - TransientError()); - }); + .WillRepeatedly( + [](google::cloud::CompletionQueue&, auto, auto, + v2::MutateRowRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + EXPECT_EQ("row", request.row_key()); + return make_ready_future>( + TransientError()); + }); auto mock_b = std::make_unique(); EXPECT_CALL(*mock_b, clone).WillOnce([]() { @@ -808,14 +824,16 @@ TEST_F(DataConnectionTest, AsyncApplyRetryExhausted) { TEST_F(DataConnectionTest, AsyncApplyRetryIdempotency) { auto mock = std::make_shared(); EXPECT_CALL(*mock, AsyncMutateRow) - .WillOnce([](google::cloud::CompletionQueue&, auto, auto, - v2::MutateRowRequest const& request, auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - EXPECT_EQ("row", request.row_key()); - return make_ready_future>( - TransientError()); - }); + .WillOnce( + [](google::cloud::CompletionQueue&, auto, auto, + v2::MutateRowRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + EXPECT_EQ("row", request.row_key()); + return make_ready_future>( + TransientError()); + }); auto mock_i = std::make_unique(); EXPECT_CALL(*mock_i, clone).WillOnce([]() { @@ -835,25 +853,30 @@ TEST_F(DataConnectionTest, AsyncApplyRetryIdempotency) { TEST_F(DataConnectionTest, AsyncApplyBigtableCookie) { auto mock = std::make_shared(); EXPECT_CALL(*mock, AsyncMutateRow) - .WillOnce([this](CompletionQueue&, - std::shared_ptr const& context, - auto, v2::MutateRowRequest const&, auto const&) { - // Return a bigtable cookie in the first request. - metadata_fixture_.SetServerMetadata( - *context, {{}, {{"x-goog-cbt-cookie-routing", "routing"}}}); - return make_ready_future>( - TransientError()); - }) - .WillOnce([this](CompletionQueue&, - std::shared_ptr const& context, - auto, v2::MutateRowRequest const&, auto const&) { - // Verify that the next request includes the bigtable cookie from above. - auto headers = metadata_fixture_.GetMetadata(*context); - EXPECT_THAT(headers, - Contains(Pair("x-goog-cbt-cookie-routing", "routing"))); - return make_ready_future>( - PermanentError()); - }); + .WillOnce( + [this](CompletionQueue&, + std::shared_ptr const& context, auto, + v2::MutateRowRequest const&, + std::shared_ptr const&) { + // Return a bigtable cookie in the first request. + metadata_fixture_.SetServerMetadata( + *context, {{}, {{"x-goog-cbt-cookie-routing", "routing"}}}); + return make_ready_future>( + TransientError()); + }) + .WillOnce( + [this](CompletionQueue&, + std::shared_ptr const& context, auto, + v2::MutateRowRequest const&, + std::shared_ptr const&) { + // Verify that the next request includes the bigtable cookie from + // above. + auto headers = metadata_fixture_.GetMetadata(*context); + EXPECT_THAT(headers, + Contains(Pair("x-goog-cbt-cookie-routing", "routing"))); + return make_ready_future>( + PermanentError()); + }); auto mock_b = std::make_unique(); EXPECT_CALL(*mock_b, clone).WillOnce([]() { @@ -913,22 +936,24 @@ TEST_F(DataConnectionTest, BulkApplySuccess) { auto mock = std::make_shared(); EXPECT_CALL(*mock, MutateRows) - .WillOnce([](auto, auto const&, - google::bigtable::v2::MutateRowsRequest const& request, - auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - EXPECT_THAT(request.entries(), ElementsAre(Entry("r0"), Entry("r1"))); - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Read) - .WillOnce([](google::bigtable::v2::MutateRowsResponse* r) { - *r = MakeBulkApplyResponse( - {{0, grpc::StatusCode::OK}, {1, grpc::StatusCode::OK}}); - return std::nullopt; - }) - .WillOnce(Return(Status())); - return stream; - }); + .WillOnce( + [](auto, auto const&, + google::bigtable::v2::MutateRowsRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + EXPECT_THAT(request.entries(), + ElementsAre(Entry("r0"), Entry("r1"))); + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Read) + .WillOnce([](google::bigtable::v2::MutateRowsResponse* r) { + *r = MakeBulkApplyResponse( + {{0, grpc::StatusCode::OK}, {1, grpc::StatusCode::OK}}); + return std::nullopt; + }) + .WillOnce(Return(Status())); + return stream; + }); auto conn = TestConnection(std::move(mock), std::move(factory)); internal::OptionsSpan span(CallOptions()); @@ -962,40 +987,42 @@ TEST_F(DataConnectionTest, BulkApplyRetryMutationPolicy) { auto mock = std::make_shared(); EXPECT_CALL(*mock, MutateRows) - .WillOnce([](auto, auto const&, - google::bigtable::v2::MutateRowsRequest const& request, - auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Read) - .WillOnce([](google::bigtable::v2::MutateRowsResponse* r) { - *r = MakeBulkApplyResponse( - {{0, grpc::StatusCode::OK}, - {1, grpc::StatusCode::UNAVAILABLE}, - {2, grpc::StatusCode::PERMISSION_DENIED}, - {3, grpc::StatusCode::UNAVAILABLE}}); - return std::nullopt; - }) - .WillOnce(Return(Status())); - return stream; - }) - .WillOnce([](auto, auto const&, - google::bigtable::v2::MutateRowsRequest const& request, - auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - EXPECT_THAT(request.entries(), - ElementsAre(Entry("retries-transient-error"))); - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Read) - .WillOnce([](google::bigtable::v2::MutateRowsResponse* r) { - *r = MakeBulkApplyResponse({{0, grpc::StatusCode::OK}}); - return std::nullopt; - }) - .WillOnce(Return(Status())); - return stream; - }); + .WillOnce( + [](auto, auto const&, + google::bigtable::v2::MutateRowsRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Read) + .WillOnce([](google::bigtable::v2::MutateRowsResponse* r) { + *r = MakeBulkApplyResponse( + {{0, grpc::StatusCode::OK}, + {1, grpc::StatusCode::UNAVAILABLE}, + {2, grpc::StatusCode::PERMISSION_DENIED}, + {3, grpc::StatusCode::UNAVAILABLE}}); + return std::nullopt; + }) + .WillOnce(Return(Status())); + return stream; + }) + .WillOnce( + [](auto, auto const&, + google::bigtable::v2::MutateRowsRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + EXPECT_THAT(request.entries(), + ElementsAre(Entry("retries-transient-error"))); + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Read) + .WillOnce([](google::bigtable::v2::MutateRowsResponse* r) { + *r = MakeBulkApplyResponse({{0, grpc::StatusCode::OK}}); + return std::nullopt; + }) + .WillOnce(Return(Status())); + return stream; + }); auto conn = TestConnection(std::move(mock), std::move(factory)); internal::OptionsSpan span(CallOptions()); @@ -1024,35 +1051,37 @@ TEST_F(DataConnectionTest, BulkApplyIncompleteStreamRetried) { auto mock = std::make_shared(); EXPECT_CALL(*mock, MutateRows) - .WillOnce([](auto, auto const&, - google::bigtable::v2::MutateRowsRequest const& request, - auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Read) - .WillOnce([](google::bigtable::v2::MutateRowsResponse* r) { - *r = MakeBulkApplyResponse({{0, grpc::StatusCode::OK}}); - return std::nullopt; - }) - .WillOnce(Return(Status())); - return stream; - }) - .WillOnce([](auto, auto const&, - google::bigtable::v2::MutateRowsRequest const& request, - auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - EXPECT_THAT(request.entries(), ElementsAre(Entry("forgotten"))); - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Read) - .WillOnce([](google::bigtable::v2::MutateRowsResponse* r) { - *r = MakeBulkApplyResponse({{0, grpc::StatusCode::OK}}); - return std::nullopt; - }) - .WillOnce(Return(Status())); - return stream; - }); + .WillOnce( + [](auto, auto const&, + google::bigtable::v2::MutateRowsRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Read) + .WillOnce([](google::bigtable::v2::MutateRowsResponse* r) { + *r = MakeBulkApplyResponse({{0, grpc::StatusCode::OK}}); + return std::nullopt; + }) + .WillOnce(Return(Status())); + return stream; + }) + .WillOnce( + [](auto, auto const&, + google::bigtable::v2::MutateRowsRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + EXPECT_THAT(request.entries(), ElementsAre(Entry("forgotten"))); + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Read) + .WillOnce([](google::bigtable::v2::MutateRowsResponse* r) { + *r = MakeBulkApplyResponse({{0, grpc::StatusCode::OK}}); + return std::nullopt; + }) + .WillOnce(Return(Status())); + return stream; + }); auto conn = TestConnection(std::move(mock), std::move(factory)); internal::OptionsSpan span(CallOptions()); @@ -1081,15 +1110,16 @@ TEST_F(DataConnectionTest, BulkApplyStreamRetryExhausted) { auto mock = std::make_shared(); EXPECT_CALL(*mock, MutateRows) .Times(kNumRetries + 1) - .WillRepeatedly([](auto, auto const&, - google::bigtable::v2::MutateRowsRequest const& request, - auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Read).WillOnce(Return(TransientError())); - return stream; - }); + .WillRepeatedly( + [](auto, auto const&, + google::bigtable::v2::MutateRowsRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Read).WillOnce(Return(TransientError())); + return stream; + }); auto mock_b = std::make_unique(); EXPECT_CALL(*mock_b, clone).WillOnce([]() { @@ -1126,15 +1156,16 @@ TEST_F(DataConnectionTest, BulkApplyStreamPermanentError) { auto mock = std::make_shared(); EXPECT_CALL(*mock, MutateRows) - .WillOnce([](auto, auto const&, - google::bigtable::v2::MutateRowsRequest const& request, - auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Read).WillOnce(Return(PermanentError())); - return stream; - }); + .WillOnce( + [](auto, auto const&, + google::bigtable::v2::MutateRowsRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Read).WillOnce(Return(PermanentError())); + return stream; + }); auto conn = TestConnection(std::move(mock), std::move(factory)); internal::OptionsSpan span(CallOptions()); @@ -1148,22 +1179,23 @@ TEST_F(DataConnectionTest, BulkApplyNoSleepIfNoPendingMutations) { auto mock = std::make_shared(); EXPECT_CALL(*mock, MutateRows) - .WillOnce([](auto, auto const&, - google::bigtable::v2::MutateRowsRequest const& request, - auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Read) - .WillOnce([](google::bigtable::v2::MutateRowsResponse* r) { - *r = MakeBulkApplyResponse( - {{0, grpc::StatusCode::OK}, - {1, grpc::StatusCode::PERMISSION_DENIED}}); - return std::nullopt; - }) - .WillOnce(Return(Status())); - return stream; - }); + .WillOnce( + [](auto, auto const&, + google::bigtable::v2::MutateRowsRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Read) + .WillOnce([](google::bigtable::v2::MutateRowsResponse* r) { + *r = MakeBulkApplyResponse( + {{0, grpc::StatusCode::OK}, + {1, grpc::StatusCode::PERMISSION_DENIED}}); + return std::nullopt; + }) + .WillOnce(Return(Status())); + return stream; + }); auto mock_b = std::make_unique(); EXPECT_CALL(*mock_b, clone).Times(0); @@ -1182,22 +1214,23 @@ TEST_F(DataConnectionTest, BulkApplyRetriesOkStreamWithFailedMutations) { auto mock = std::make_shared(); EXPECT_CALL(*mock, MutateRows) .Times(kNumRetries + 1) - .WillRepeatedly([](auto, auto const&, - google::bigtable::v2::MutateRowsRequest const& request, - auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - auto stream = std::make_unique(); - // The overall stream succeeds, but it contains failed mutations. - // Our retry and backoff policies should take effect. - EXPECT_CALL(*stream, Read) - .WillOnce([](google::bigtable::v2::MutateRowsResponse* r) { - *r = MakeBulkApplyResponse({{0, grpc::StatusCode::UNAVAILABLE}}); - return std::nullopt; - }) - .WillOnce(Return(Status())); - return stream; - }); + .WillRepeatedly( + [](auto, auto const&, + google::bigtable::v2::MutateRowsRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + auto stream = std::make_unique(); + // Our retry and backoff policies should take effect. + EXPECT_CALL(*stream, Read) + .WillOnce([](google::bigtable::v2::MutateRowsResponse* r) { + *r = MakeBulkApplyResponse( + {{0, grpc::StatusCode::UNAVAILABLE}}); + return std::nullopt; + }) + .WillOnce(Return(Status())); + return stream; + }); auto mock_b = std::make_unique(); EXPECT_CALL(*mock_b, clone).WillOnce([]() { @@ -1219,7 +1252,8 @@ TEST_F(DataConnectionTest, BulkApplyRetryInfoHeeded) { auto mock = std::make_shared(); EXPECT_CALL(*mock, MutateRows) .WillOnce( - [](auto, auto const&, v2::MutateRowsRequest const&, auto const&) { + [](auto, auto const&, v2::MutateRowsRequest const&, + std::shared_ptr const&) { auto status = PermanentError(); internal::SetRetryInfo(status, internal::RetryInfo{ms(0)}); auto stream = std::make_unique(); @@ -1227,7 +1261,8 @@ TEST_F(DataConnectionTest, BulkApplyRetryInfoHeeded) { return stream; }) .WillOnce( - [](auto, auto const&, v2::MutateRowsRequest const&, auto const&) { + [](auto, auto const&, v2::MutateRowsRequest const&, + std::shared_ptr const&) { auto stream = std::make_unique(); EXPECT_CALL(*stream, Read) .WillOnce([](google::bigtable::v2::MutateRowsResponse* r) { @@ -1252,7 +1287,8 @@ TEST_F(DataConnectionTest, BulkApplyRetryInfoIgnored) { auto mock = std::make_shared(); EXPECT_CALL(*mock, MutateRows) .WillOnce( - [](auto, auto const&, v2::MutateRowsRequest const&, auto const&) { + [](auto, auto const&, v2::MutateRowsRequest const&, + std::shared_ptr const&) { auto status = PermanentError(); internal::SetRetryInfo(status, internal::RetryInfo{ms(0)}); auto stream = std::make_unique(); @@ -1302,14 +1338,16 @@ TEST_F(DataConnectionTest, AsyncBulkApply) { EXPECT_CALL(*mock_limiter, AsyncAcquire) .WillOnce(Return(ByMove(make_ready_future()))); EXPECT_CALL(*mock_stub, AsyncMutateRows) - .WillOnce([](CompletionQueue const&, auto, auto, - v2::MutateRowsRequest const& request, auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - using ErrorStream = - internal::AsyncStreamingReadRpcError; - return std::make_unique(PermanentError()); - }); + .WillOnce( + [](CompletionQueue const&, auto, auto, + v2::MutateRowsRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + using ErrorStream = + internal::AsyncStreamingReadRpcError; + return std::make_unique(PermanentError()); + }); } auto conn = TestConnection(std::move(mock_stub), std::move(mock_limiter)); @@ -1323,20 +1361,21 @@ TEST_F(DataConnectionTest, AsyncBulkApply) { TEST_F(DataConnectionTest, ReadRows) { auto mock = std::make_shared(); EXPECT_CALL(*mock, ReadRows) - .WillOnce([](auto, auto const&, - google::bigtable::v2::ReadRowsRequest const& request, - auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - EXPECT_EQ(42, request.rows_limit()); - EXPECT_THAT(request, HasTestRowSet()); - EXPECT_THAT(request.filter(), IsTestFilter()); - EXPECT_FALSE(request.reversed()); - - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Read).WillOnce(Return(Status())); - return stream; - }); + .WillOnce( + [](auto, auto const&, + google::bigtable::v2::ReadRowsRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + EXPECT_EQ(42, request.rows_limit()); + EXPECT_THAT(request, HasTestRowSet()); + EXPECT_THAT(request.filter(), IsTestFilter()); + EXPECT_FALSE(request.reversed()); + + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Read).WillOnce(Return(Status())); + return stream; + }); auto conn = TestConnection(std::move(mock)); internal::OptionsSpan span(CallOptions()); @@ -1347,15 +1386,16 @@ TEST_F(DataConnectionTest, ReadRows) { TEST_F(DataConnectionTest, ReadRowsReverseScan) { auto mock = std::make_shared(); EXPECT_CALL(*mock, ReadRows) - .WillOnce([](auto, auto const&, - google::bigtable::v2::ReadRowsRequest const& request, - auto const&) { - EXPECT_TRUE(request.reversed()); - - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Read).WillOnce(Return(Status())); - return stream; - }); + .WillOnce( + [](auto, auto const&, + google::bigtable::v2::ReadRowsRequest const& request, + std::shared_ptr const&) { + EXPECT_TRUE(request.reversed()); + + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Read).WillOnce(Return(Status())); + return stream; + }); auto conn = TestConnection(std::move(mock)); internal::OptionsSpan span(CallOptions().set(true)); @@ -1368,20 +1408,21 @@ TEST_F(DataConnectionTest, ReadRowsReverseScan) { TEST_F(DataConnectionTest, ReadRowsFull) { auto mock = std::make_shared(); EXPECT_CALL(*mock, ReadRows) - .WillOnce([](auto, auto const&, - google::bigtable::v2::ReadRowsRequest const& request, - auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - EXPECT_EQ(42, request.rows_limit()); - EXPECT_THAT(request, HasTestRowSet()); - EXPECT_THAT(request.filter(), IsTestFilter()); - EXPECT_TRUE(request.reversed()); - - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Read).WillOnce(Return(Status())); - return stream; - }); + .WillOnce( + [](auto, auto const&, + google::bigtable::v2::ReadRowsRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + EXPECT_EQ(42, request.rows_limit()); + EXPECT_THAT(request, HasTestRowSet()); + EXPECT_THAT(request.filter(), IsTestFilter()); + EXPECT_TRUE(request.reversed()); + + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Read).WillOnce(Return(Status())); + return stream; + }); auto conn = TestConnection(std::move(mock)); internal::OptionsSpan span(CallOptions()); @@ -1393,20 +1434,22 @@ TEST_F(DataConnectionTest, ReadRowsFull) { TEST_F(DataConnectionTest, ReadRowsRetryInfoHeeded) { auto mock = std::make_shared(); EXPECT_CALL(*mock, ReadRows) - .WillOnce([](auto, auto const&, - google::bigtable::v2::ReadRowsRequest const&, auto const&) { - auto status = PermanentError(); - internal::SetRetryInfo(status, internal::RetryInfo{ms(0)}); - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Read).WillOnce(Return(status)); - return stream; - }) - .WillOnce([](auto, auto const&, - google::bigtable::v2::ReadRowsRequest const&, auto const&) { - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Read).WillOnce(Return(Status())); - return stream; - }); + .WillOnce( + [](auto, auto const&, google::bigtable::v2::ReadRowsRequest const&, + std::shared_ptr const&) { + auto status = PermanentError(); + internal::SetRetryInfo(status, internal::RetryInfo{ms(0)}); + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Read).WillOnce(Return(status)); + return stream; + }) + .WillOnce( + [](auto, auto const&, google::bigtable::v2::ReadRowsRequest const&, + std::shared_ptr const&) { + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Read).WillOnce(Return(Status())); + return stream; + }); auto conn = TestConnection(std::move(mock)); internal::OptionsSpan span( @@ -1419,14 +1462,15 @@ TEST_F(DataConnectionTest, ReadRowsRetryInfoHeeded) { TEST_F(DataConnectionTest, ReadRowsRetryInfoIgnored) { auto mock = std::make_shared(); EXPECT_CALL(*mock, ReadRows) - .WillOnce([](auto, auto const&, - google::bigtable::v2::ReadRowsRequest const&, auto const&) { - auto status = PermanentError(); - internal::SetRetryInfo(status, internal::RetryInfo{ms(0)}); - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Read).WillOnce(Return(status)); - return stream; - }); + .WillOnce( + [](auto, auto const&, google::bigtable::v2::ReadRowsRequest const&, + std::shared_ptr const&) { + auto status = PermanentError(); + internal::SetRetryInfo(status, internal::RetryInfo{ms(0)}); + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Read).WillOnce(Return(status)); + return stream; + }); auto conn = TestConnection(std::move(mock)); internal::OptionsSpan span( @@ -1456,19 +1500,20 @@ TEST_F(DataConnectionTest, ReadRowEmpty) { auto mock = std::make_shared(); EXPECT_CALL(*mock, ReadRows) - .WillOnce([](auto, auto const&, - google::bigtable::v2::ReadRowsRequest const& request, - auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - EXPECT_EQ(1, request.rows_limit()); - EXPECT_THAT(request.rows().row_keys(), ElementsAre("row")); - EXPECT_THAT(request.filter(), IsTestFilter()); - - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Read).WillOnce(Return(Status())); - return stream; - }); + .WillOnce( + [](auto, auto const&, + google::bigtable::v2::ReadRowsRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + EXPECT_EQ(1, request.rows_limit()); + EXPECT_THAT(request.rows().row_keys(), ElementsAre("row")); + EXPECT_THAT(request.filter(), IsTestFilter()); + + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Read).WillOnce(Return(Status())); + return stream; + }); auto conn = TestConnection(std::move(mock), std::move(factory)); internal::OptionsSpan span(CallOptions()); @@ -1495,30 +1540,31 @@ TEST_F(DataConnectionTest, ReadRowSuccess) { auto mock = std::make_shared(); EXPECT_CALL(*mock, ReadRows) - .WillOnce([](auto, auto const&, - google::bigtable::v2::ReadRowsRequest const& request, - auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - EXPECT_EQ(1, request.rows_limit()); - EXPECT_THAT(request.rows().row_keys(), ElementsAre("row")); - EXPECT_THAT(request.filter(), IsTestFilter()); - - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Read) - .WillOnce([](google::bigtable::v2::ReadRowsResponse* r) { - v2::ReadRowsResponse resp; - auto& chunk = *resp.add_chunks(); - *chunk.mutable_row_key() = "row"; - chunk.mutable_family_name()->set_value("cf"); - chunk.mutable_qualifier()->set_value("cq"); - chunk.set_commit_row(true); - *r = resp; - return std::nullopt; - }) - .WillOnce(Return(Status())); - return stream; - }); + .WillOnce( + [](auto, auto const&, + google::bigtable::v2::ReadRowsRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + EXPECT_EQ(1, request.rows_limit()); + EXPECT_THAT(request.rows().row_keys(), ElementsAre("row")); + EXPECT_THAT(request.filter(), IsTestFilter()); + + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Read) + .WillOnce([](google::bigtable::v2::ReadRowsResponse* r) { + v2::ReadRowsResponse resp; + auto& chunk = *resp.add_chunks(); + *chunk.mutable_row_key() = "row"; + chunk.mutable_family_name()->set_value("cf"); + chunk.mutable_qualifier()->set_value("cq"); + chunk.set_commit_row(true); + *r = resp; + return std::nullopt; + }) + .WillOnce(Return(Status())); + return stream; + }); auto conn = TestConnection(std::move(mock), std::move(factory)); internal::OptionsSpan span(CallOptions()); @@ -1546,19 +1592,20 @@ TEST_F(DataConnectionTest, ReadRowFailure) { auto mock = std::make_shared(); EXPECT_CALL(*mock, ReadRows) - .WillOnce([](auto, auto const&, - google::bigtable::v2::ReadRowsRequest const& request, - auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - EXPECT_EQ(1, request.rows_limit()); - EXPECT_THAT(request.rows().row_keys(), ElementsAre("row")); - EXPECT_THAT(request.filter(), IsTestFilter()); - - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Read).WillOnce(Return(PermanentError())); - return stream; - }); + .WillOnce( + [](auto, auto const&, + google::bigtable::v2::ReadRowsRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + EXPECT_EQ(1, request.rows_limit()); + EXPECT_THAT(request.rows().row_keys(), ElementsAre("row")); + EXPECT_THAT(request.filter(), IsTestFilter()); + + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Read).WillOnce(Return(PermanentError())); + return stream; + }); auto conn = TestConnection(std::move(mock), std::move(factory)); internal::OptionsSpan span(CallOptions()); @@ -1598,7 +1645,8 @@ TEST_F(DataConnectionTest, CheckAndMutateRowSuccess) { auto mock = std::make_shared(); EXPECT_CALL(*mock, CheckAndMutateRow) .WillOnce([&](grpc::ClientContext&, Options const&, - v2::CheckAndMutateRowRequest const& request, auto const&) { + v2::CheckAndMutateRowRequest const& request, + bigtable_internal::OperationContext&) { EXPECT_EQ(kAppProfile, request.app_profile_id()); EXPECT_EQ(kTableName, request.table_name()); EXPECT_EQ("row", request.row_key()); @@ -1613,7 +1661,8 @@ TEST_F(DataConnectionTest, CheckAndMutateRowSuccess) { return resp; }) .WillOnce([&](grpc::ClientContext&, Options const&, - v2::CheckAndMutateRowRequest const& request, auto const&) { + v2::CheckAndMutateRowRequest const& request, + bigtable_internal::OperationContext&) { EXPECT_EQ(kAppProfile, request.app_profile_id()); EXPECT_EQ(kTableName, request.table_name()); EXPECT_EQ("row", request.row_key()); @@ -1650,7 +1699,8 @@ TEST_F(DataConnectionTest, CheckAndMutateRowIdempotency) { auto mock = std::make_shared(); EXPECT_CALL(*mock, CheckAndMutateRow) .WillOnce([&](grpc::ClientContext&, Options const&, - v2::CheckAndMutateRowRequest const& request, auto const&) { + v2::CheckAndMutateRowRequest const& request, + bigtable_internal::OperationContext&) { EXPECT_EQ(kAppProfile, request.app_profile_id()); EXPECT_EQ(kTableName, request.table_name()); EXPECT_EQ("row", request.row_key()); @@ -1703,7 +1753,8 @@ TEST_F(DataConnectionTest, CheckAndMutateRowPermanentError) { auto mock = std::make_shared(); EXPECT_CALL(*mock, CheckAndMutateRow) .WillOnce([&](grpc::ClientContext&, Options const&, - v2::CheckAndMutateRowRequest const& request, auto const&) { + v2::CheckAndMutateRowRequest const& request, + bigtable_internal::OperationContext&) { EXPECT_EQ(kAppProfile, request.app_profile_id()); EXPECT_EQ(kTableName, request.table_name()); EXPECT_EQ("row", request.row_key()); @@ -1748,7 +1799,7 @@ TEST_F(DataConnectionTest, CheckAndMutateRowRetryExhausted) { .Times(kNumRetries + 1) .WillRepeatedly([&](grpc::ClientContext&, Options const&, v2::CheckAndMutateRowRequest const& request, - auto const&) { + bigtable_internal::OperationContext&) { EXPECT_EQ(kAppProfile, request.app_profile_id()); EXPECT_EQ(kTableName, request.table_name()); EXPECT_EQ("row", request.row_key()); @@ -1787,14 +1838,16 @@ TEST_F(DataConnectionTest, CheckAndMutateRowBigtableCookie) { auto mock = std::make_shared(); EXPECT_CALL(*mock, CheckAndMutateRow) .WillOnce([this](grpc::ClientContext& context, Options const&, - v2::CheckAndMutateRowRequest const&, auto const&) { + v2::CheckAndMutateRowRequest const&, + bigtable_internal::OperationContext&) { // Return a bigtable cookie in the first request. metadata_fixture_.SetServerMetadata( context, {{}, {{"x-goog-cbt-cookie-routing", "routing"}}}); return TransientError(); }) .WillOnce([this](grpc::ClientContext& context, Options const&, - v2::CheckAndMutateRowRequest const&, auto const&) { + v2::CheckAndMutateRowRequest const&, + bigtable_internal::OperationContext&) { // Verify that the next request includes the bigtable cookie from above. auto headers = metadata_fixture_.GetMetadata(context); EXPECT_THAT(headers, @@ -1852,36 +1905,40 @@ TEST_F(DataConnectionTest, AsyncCheckAndMutateRowSuccess) { auto mock = std::make_shared(); EXPECT_CALL(*mock, AsyncCheckAndMutateRow) - .WillOnce([&](google::cloud::CompletionQueue&, auto, auto, - v2::CheckAndMutateRowRequest const& request, auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - EXPECT_EQ("row", request.row_key()); - EXPECT_THAT(request.predicate_filter(), IsTestFilter()); - EXPECT_THAT(request.true_mutations(), - ElementsAre(MatchMutation(t1), MatchMutation(t2))); - EXPECT_THAT(request.false_mutations(), - ElementsAre(MatchMutation(f1), MatchMutation(f2))); - - v2::CheckAndMutateRowResponse resp; - resp.set_predicate_matched(true); - return make_ready_future(make_status_or(resp)); - }) - .WillOnce([&](google::cloud::CompletionQueue&, auto, auto, - v2::CheckAndMutateRowRequest const& request, auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - EXPECT_EQ("row", request.row_key()); - EXPECT_THAT(request.predicate_filter(), IsTestFilter()); - EXPECT_THAT(request.true_mutations(), - ElementsAre(MatchMutation(t1), MatchMutation(t2))); - EXPECT_THAT(request.false_mutations(), - ElementsAre(MatchMutation(f1), MatchMutation(f2))); - - v2::CheckAndMutateRowResponse resp; - resp.set_predicate_matched(false); - return make_ready_future(make_status_or(resp)); - }); + .WillOnce( + [&](google::cloud::CompletionQueue&, auto, auto, + v2::CheckAndMutateRowRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + EXPECT_EQ("row", request.row_key()); + EXPECT_THAT(request.predicate_filter(), IsTestFilter()); + EXPECT_THAT(request.true_mutations(), + ElementsAre(MatchMutation(t1), MatchMutation(t2))); + EXPECT_THAT(request.false_mutations(), + ElementsAre(MatchMutation(f1), MatchMutation(f2))); + + v2::CheckAndMutateRowResponse resp; + resp.set_predicate_matched(true); + return make_ready_future(make_status_or(resp)); + }) + .WillOnce( + [&](google::cloud::CompletionQueue&, auto, auto, + v2::CheckAndMutateRowRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + EXPECT_EQ("row", request.row_key()); + EXPECT_THAT(request.predicate_filter(), IsTestFilter()); + EXPECT_THAT(request.true_mutations(), + ElementsAre(MatchMutation(t1), MatchMutation(t2))); + EXPECT_THAT(request.false_mutations(), + ElementsAre(MatchMutation(f1), MatchMutation(f2))); + + v2::CheckAndMutateRowResponse resp; + resp.set_predicate_matched(false); + return make_ready_future(make_status_or(resp)); + }); auto conn = TestConnection(std::move(mock), std::move(factory)); internal::OptionsSpan span(CallOptions()); @@ -1906,19 +1963,21 @@ TEST_F(DataConnectionTest, AsyncCheckAndMutateRowIdempotency) { auto mock = std::make_shared(); EXPECT_CALL(*mock, AsyncCheckAndMutateRow) - .WillOnce([&](google::cloud::CompletionQueue&, auto, auto, - v2::CheckAndMutateRowRequest const& request, auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - EXPECT_EQ("row", request.row_key()); - EXPECT_THAT(request.predicate_filter(), IsTestFilter()); - EXPECT_THAT(request.true_mutations(), - ElementsAre(MatchMutation(t1), MatchMutation(t2))); - EXPECT_THAT(request.false_mutations(), - ElementsAre(MatchMutation(f1), MatchMutation(f2))); - return make_ready_future>( - TransientError()); - }); + .WillOnce( + [&](google::cloud::CompletionQueue&, auto, auto, + v2::CheckAndMutateRowRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + EXPECT_EQ("row", request.row_key()); + EXPECT_THAT(request.predicate_filter(), IsTestFilter()); + EXPECT_THAT(request.true_mutations(), + ElementsAre(MatchMutation(t1), MatchMutation(t2))); + EXPECT_THAT(request.false_mutations(), + ElementsAre(MatchMutation(f1), MatchMutation(f2))); + return make_ready_future>( + TransientError()); + }); auto mock_i = std::make_unique(); EXPECT_CALL(*mock_i, clone).WillOnce([]() { @@ -1960,19 +2019,21 @@ TEST_F(DataConnectionTest, AsyncCheckAndMutateRowPermanentError) { auto mock = std::make_shared(); EXPECT_CALL(*mock, AsyncCheckAndMutateRow) - .WillOnce([&](google::cloud::CompletionQueue&, auto, auto, - v2::CheckAndMutateRowRequest const& request, auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - EXPECT_EQ("row", request.row_key()); - EXPECT_THAT(request.predicate_filter(), IsTestFilter()); - EXPECT_THAT(request.true_mutations(), - ElementsAre(MatchMutation(t1), MatchMutation(t2))); - EXPECT_THAT(request.false_mutations(), - ElementsAre(MatchMutation(f1), MatchMutation(f2))); - return make_ready_future>( - PermanentError()); - }); + .WillOnce( + [&](google::cloud::CompletionQueue&, auto, auto, + v2::CheckAndMutateRowRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + EXPECT_EQ("row", request.row_key()); + EXPECT_THAT(request.predicate_filter(), IsTestFilter()); + EXPECT_THAT(request.true_mutations(), + ElementsAre(MatchMutation(t1), MatchMutation(t2))); + EXPECT_THAT(request.false_mutations(), + ElementsAre(MatchMutation(f1), MatchMutation(f2))); + return make_ready_future>( + PermanentError()); + }); auto conn = TestConnection(std::move(mock), std::move(factory)); internal::OptionsSpan span(CallOptions()); @@ -2005,20 +2066,21 @@ TEST_F(DataConnectionTest, AsyncCheckAndMutateRowRetryExhausted) { auto mock = std::make_shared(); EXPECT_CALL(*mock, AsyncCheckAndMutateRow) .Times(kNumRetries + 1) - .WillRepeatedly([&](google::cloud::CompletionQueue&, auto, auto, - v2::CheckAndMutateRowRequest const& request, - auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - EXPECT_EQ("row", request.row_key()); - EXPECT_THAT(request.predicate_filter(), IsTestFilter()); - EXPECT_THAT(request.true_mutations(), - ElementsAre(MatchMutation(t1), MatchMutation(t2))); - EXPECT_THAT(request.false_mutations(), - ElementsAre(MatchMutation(f1), MatchMutation(f2))); - return make_ready_future>( - TransientError()); - }); + .WillRepeatedly( + [&](google::cloud::CompletionQueue&, auto, auto, + v2::CheckAndMutateRowRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + EXPECT_EQ("row", request.row_key()); + EXPECT_THAT(request.predicate_filter(), IsTestFilter()); + EXPECT_THAT(request.true_mutations(), + ElementsAre(MatchMutation(t1), MatchMutation(t2))); + EXPECT_THAT(request.false_mutations(), + ElementsAre(MatchMutation(f1), MatchMutation(f2))); + return make_ready_future>( + TransientError()); + }); auto mock_b = std::make_unique(); EXPECT_CALL(*mock_b, clone).WillOnce([]() { @@ -2046,25 +2108,30 @@ TEST_F(DataConnectionTest, AsyncCheckAndMutateRowBigtableCookie) { auto mock = std::make_shared(); EXPECT_CALL(*mock, AsyncCheckAndMutateRow) - .WillOnce([this](CompletionQueue&, - std::shared_ptr const& context, - auto, v2::CheckAndMutateRowRequest const&, auto const&) { - // Return a bigtable cookie in the first request. - metadata_fixture_.SetServerMetadata( - *context, {{}, {{"x-goog-cbt-cookie-routing", "routing"}}}); - return make_ready_future>( - TransientError()); - }) - .WillOnce([this](CompletionQueue&, - std::shared_ptr const& context, - auto, v2::CheckAndMutateRowRequest const&, auto const&) { - // Verify that the next request includes the bigtable cookie from above. - auto headers = metadata_fixture_.GetMetadata(*context); - EXPECT_THAT(headers, - Contains(Pair("x-goog-cbt-cookie-routing", "routing"))); - return make_ready_future>( - PermanentError()); - }); + .WillOnce( + [this](CompletionQueue&, + std::shared_ptr const& context, auto, + v2::CheckAndMutateRowRequest const&, + std::shared_ptr const&) { + // Return a bigtable cookie in the first request. + metadata_fixture_.SetServerMetadata( + *context, {{}, {{"x-goog-cbt-cookie-routing", "routing"}}}); + return make_ready_future>( + TransientError()); + }) + .WillOnce( + [this](CompletionQueue&, + std::shared_ptr const& context, auto, + v2::CheckAndMutateRowRequest const&, + std::shared_ptr const&) { + // Verify that the next request includes the bigtable cookie from + // above. + auto headers = metadata_fixture_.GetMetadata(*context); + EXPECT_THAT(headers, + Contains(Pair("x-goog-cbt-cookie-routing", "routing"))); + return make_ready_future>( + PermanentError()); + }); auto mock_b = std::make_unique(); EXPECT_CALL(*mock_b, clone).WillOnce([]() { @@ -2102,24 +2169,26 @@ TEST_F(DataConnectionTest, SampleRowsSuccess) { auto mock = std::make_shared(); EXPECT_CALL(*mock, SampleRowKeys) - .WillOnce([this](auto client_context, auto const&, - v2::SampleRowKeysRequest const& request, auto const&) { - metadata_fixture_.SetServerMetadata(*client_context, {}); - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Read) - .WillOnce([](google::bigtable::v2::SampleRowKeysResponse* r) { - *r = MakeSampleRowsResponse("test1", 11); - return std::nullopt; - }) - .WillOnce([](google::bigtable::v2::SampleRowKeysResponse* r) { - *r = MakeSampleRowsResponse("test2", 22); - return std::nullopt; - }) - .WillOnce(Return(Status{})); - return stream; - }); + .WillOnce( + [this](auto client_context, auto const&, + v2::SampleRowKeysRequest const& request, + std::shared_ptr const&) { + metadata_fixture_.SetServerMetadata(*client_context, {}); + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Read) + .WillOnce([](google::bigtable::v2::SampleRowKeysResponse* r) { + *r = MakeSampleRowsResponse("test1", 11); + return std::nullopt; + }) + .WillOnce([](google::bigtable::v2::SampleRowKeysResponse* r) { + *r = MakeSampleRowsResponse("test2", 22); + return std::nullopt; + }) + .WillOnce(Return(Status{})); + return stream; + }); MockFunction mock_setup; EXPECT_CALL(mock_setup, Call).Times(1); @@ -2152,32 +2221,34 @@ TEST_F(DataConnectionTest, SampleRowsRetryResetsSamples) { auto mock = std::make_shared(); EXPECT_CALL(*mock, SampleRowKeys) - .WillOnce([](auto, auto const&, v2::SampleRowKeysRequest const& request, - auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Read) - .WillOnce([](google::bigtable::v2::SampleRowKeysResponse* r) { - *r = MakeSampleRowsResponse("discarded", 11); - return std::nullopt; - }) - .WillOnce(Return(TransientError())); - return stream; - }) - .WillOnce([](auto, auto const&, v2::SampleRowKeysRequest const& request, - auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Read) - .WillOnce([](google::bigtable::v2::SampleRowKeysResponse* r) { - *r = MakeSampleRowsResponse("returned", 22); - return std::nullopt; - }) - .WillOnce(Return(Status{})); - return stream; - }); + .WillOnce( + [](auto, auto const&, v2::SampleRowKeysRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Read) + .WillOnce([](google::bigtable::v2::SampleRowKeysResponse* r) { + *r = MakeSampleRowsResponse("discarded", 11); + return std::nullopt; + }) + .WillOnce(Return(TransientError())); + return stream; + }) + .WillOnce( + [](auto, auto const&, v2::SampleRowKeysRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Read) + .WillOnce([](google::bigtable::v2::SampleRowKeysResponse* r) { + *r = MakeSampleRowsResponse("returned", 22); + return std::nullopt; + }) + .WillOnce(Return(Status{})); + return stream; + }); auto conn = TestConnection(std::move(mock), std::move(factory)); internal::OptionsSpan span(CallOptions()); @@ -2207,16 +2278,17 @@ TEST_F(DataConnectionTest, SampleRowsRetryExhausted) { auto mock = std::make_shared(); EXPECT_CALL(*mock, SampleRowKeys) .Times(kNumRetries + 1) - .WillRepeatedly([this](auto context, auto const&, - v2::SampleRowKeysRequest const& request, - auto const&) { - metadata_fixture_.SetServerMetadata(*context, {}); - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Read).WillOnce(Return(TransientError())); - return stream; - }); + .WillRepeatedly( + [this](auto context, auto const&, + v2::SampleRowKeysRequest const& request, + std::shared_ptr const&) { + metadata_fixture_.SetServerMetadata(*context, {}); + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Read).WillOnce(Return(TransientError())); + return stream; + }); auto mock_b = std::make_unique(); EXPECT_CALL(*mock_b, clone).WillOnce([]() { @@ -2254,15 +2326,17 @@ TEST_F(DataConnectionTest, SampleRowsPermanentError) { auto mock = std::make_shared(); EXPECT_CALL(*mock, SampleRowKeys) - .WillOnce([this](auto client_context, auto const&, - v2::SampleRowKeysRequest const& request, auto const&) { - metadata_fixture_.SetServerMetadata(*client_context, {}); - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Read).WillOnce(Return(PermanentError())); - return stream; - }); + .WillOnce( + [this](auto client_context, auto const&, + v2::SampleRowKeysRequest const& request, + std::shared_ptr const&) { + metadata_fixture_.SetServerMetadata(*client_context, {}); + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Read).WillOnce(Return(PermanentError())); + return stream; + }); MockFunction mock_setup; EXPECT_CALL(mock_setup, Call).Times(1); @@ -2277,26 +2351,28 @@ TEST_F(DataConnectionTest, SampleRowsPermanentError) { TEST_F(DataConnectionTest, SampleRowsBigtableCookie) { auto mock = std::make_shared(); EXPECT_CALL(*mock, SampleRowKeys) - .WillOnce([this](auto context, auto const&, - v2::SampleRowKeysRequest const&, auto const&) { - // Return a bigtable cookie in the first request. - metadata_fixture_.SetServerMetadata( - *context, {{}, {{"x-goog-cbt-cookie-routing", "routing"}}}); - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Read).WillOnce(Return(TransientError())); - return stream; - }) - .WillOnce([this](auto context, auto const&, - v2::SampleRowKeysRequest const&, auto const&) { - // Verify that the next request includes the bigtable cookie from - // above. - auto headers = metadata_fixture_.GetMetadata(*context); - EXPECT_THAT(headers, - Contains(Pair("x-goog-cbt-cookie-routing", "routing"))); - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Read).WillOnce(Return(PermanentError())); - return stream; - }); + .WillOnce( + [this](auto context, auto const&, v2::SampleRowKeysRequest const&, + std::shared_ptr const&) { + // Return a bigtable cookie in the first request. + metadata_fixture_.SetServerMetadata( + *context, {{}, {{"x-goog-cbt-cookie-routing", "routing"}}}); + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Read).WillOnce(Return(TransientError())); + return stream; + }) + .WillOnce( + [this](auto context, auto const&, v2::SampleRowKeysRequest const&, + std::shared_ptr const&) { + // Verify that the next request includes the bigtable cookie from + // above. + auto headers = metadata_fixture_.GetMetadata(*context); + EXPECT_THAT(headers, + Contains(Pair("x-goog-cbt-cookie-routing", "routing"))); + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Read).WillOnce(Return(PermanentError())); + return stream; + }); auto mock_b = std::make_unique(); EXPECT_CALL(*mock_b, clone).WillOnce([]() { @@ -2317,7 +2393,8 @@ TEST_F(DataConnectionTest, SampleRowsRetryInfoHeeded) { auto mock = std::make_shared(); EXPECT_CALL(*mock, SampleRowKeys) .WillOnce( - [](auto, auto const&, v2::SampleRowKeysRequest const&, auto const&) { + [](auto, auto const&, v2::SampleRowKeysRequest const&, + std::shared_ptr const&) { auto status = PermanentError(); internal::SetRetryInfo(status, internal::RetryInfo{ms(0)}); auto stream = std::make_unique(); @@ -2325,7 +2402,8 @@ TEST_F(DataConnectionTest, SampleRowsRetryInfoHeeded) { return stream; }) .WillOnce( - [](auto, auto const&, v2::SampleRowKeysRequest const&, auto const&) { + [](auto, auto const&, v2::SampleRowKeysRequest const&, + std::shared_ptr const&) { auto stream = std::make_unique(); EXPECT_CALL(*stream, Read).WillOnce(Return(Status())); return stream; @@ -2342,7 +2420,8 @@ TEST_F(DataConnectionTest, SampleRowsRetryInfoIgnored) { auto mock = std::make_shared(); EXPECT_CALL(*mock, SampleRowKeys) .WillOnce( - [](auto, auto const&, v2::SampleRowKeysRequest const&, auto const&) { + [](auto, auto const&, v2::SampleRowKeysRequest const&, + std::shared_ptr const&) { auto status = PermanentError(); internal::SetRetryInfo(status, internal::RetryInfo{ms(0)}); auto stream = std::make_unique(); @@ -2362,14 +2441,16 @@ TEST_F(DataConnectionTest, SampleRowsRetryInfoIgnored) { TEST_F(DataConnectionTest, AsyncSampleRows) { auto mock = std::make_shared(); EXPECT_CALL(*mock, AsyncSampleRowKeys) - .WillOnce([](CompletionQueue const&, auto, auto, - v2::SampleRowKeysRequest const& request, auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - using ErrorStream = - internal::AsyncStreamingReadRpcError; - return std::make_unique(PermanentError()); - }); + .WillOnce( + [](CompletionQueue const&, auto, auto, + v2::SampleRowKeysRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + using ErrorStream = + internal::AsyncStreamingReadRpcError; + return std::make_unique(PermanentError()); + }); auto conn = TestConnection(std::move(mock)); internal::OptionsSpan span(CallOptions()); @@ -2411,7 +2492,7 @@ TEST_F(DataConnectionTest, ReadModifyWriteRowSuccess) { EXPECT_CALL(*mock, ReadModifyWriteRow) .WillOnce([&response](grpc::ClientContext&, Options const&, v2::ReadModifyWriteRowRequest const& request, - auto const&) { + bigtable_internal::OperationContext&) { EXPECT_EQ(kAppProfile, request.app_profile_id()); EXPECT_EQ(kTableName, request.table_name()); EXPECT_EQ("row", request.row_key()); @@ -2452,7 +2533,8 @@ TEST_F(DataConnectionTest, ReadModifyWriteRowPermanentError) { auto mock = std::make_shared(); EXPECT_CALL(*mock, ReadModifyWriteRow) .WillOnce([](grpc::ClientContext&, Options const&, - v2::ReadModifyWriteRowRequest const& request, auto const&) { + v2::ReadModifyWriteRowRequest const& request, + bigtable_internal::OperationContext&) { EXPECT_EQ(kAppProfile, request.app_profile_id()); EXPECT_EQ(kTableName, request.table_name()); EXPECT_EQ("row", request.row_key()); @@ -2489,7 +2571,8 @@ TEST_F(DataConnectionTest, ReadModifyWriteRowTransientErrorNotRetried) { auto mock = std::make_shared(); EXPECT_CALL(*mock, ReadModifyWriteRow) .WillOnce([](grpc::ClientContext&, Options const&, - v2::ReadModifyWriteRowRequest const& request, auto const&) { + v2::ReadModifyWriteRowRequest const& request, + bigtable_internal::OperationContext&) { EXPECT_EQ(kAppProfile, request.app_profile_id()); EXPECT_EQ(kTableName, request.table_name()); EXPECT_EQ("row", request.row_key()); @@ -2547,15 +2630,17 @@ TEST_F(DataConnectionTest, AsyncReadModifyWriteRowSuccess) { auto mock = std::make_shared(); EXPECT_CALL(*mock, AsyncReadModifyWriteRow) - .WillOnce([&response, this]( - google::cloud::CompletionQueue&, auto client_context, auto, - v2::ReadModifyWriteRowRequest const& request, auto const&) { - metadata_fixture_.SetServerMetadata(*client_context, {}); - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - EXPECT_EQ("row", request.row_key()); - return make_ready_future(make_status_or(response)); - }); + .WillOnce( + [&response, this]( + google::cloud::CompletionQueue&, auto client_context, auto, + v2::ReadModifyWriteRowRequest const& request, + std::shared_ptr const&) { + metadata_fixture_.SetServerMetadata(*client_context, {}); + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + EXPECT_EQ("row", request.row_key()); + return make_ready_future(make_status_or(response)); + }); v2::ReadModifyWriteRowRequest req; req.set_app_profile_id(kAppProfile); @@ -2594,16 +2679,17 @@ TEST_F(DataConnectionTest, AsyncReadModifyWriteRowPermanentError) { auto mock = std::make_shared(); EXPECT_CALL(*mock, AsyncReadModifyWriteRow) - .WillOnce([this](google::cloud::CompletionQueue&, auto client_context, - auto, v2::ReadModifyWriteRowRequest const& request, - auto const&) { - metadata_fixture_.SetServerMetadata(*client_context, {}); - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - EXPECT_EQ("row", request.row_key()); - return make_ready_future>( - PermanentError()); - }); + .WillOnce( + [this](google::cloud::CompletionQueue&, auto client_context, auto, + v2::ReadModifyWriteRowRequest const& request, + std::shared_ptr const&) { + metadata_fixture_.SetServerMetadata(*client_context, {}); + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + EXPECT_EQ("row", request.row_key()); + return make_ready_future>( + PermanentError()); + }); v2::ReadModifyWriteRowRequest req; req.set_app_profile_id(kAppProfile); @@ -2623,16 +2709,17 @@ TEST_F(DataConnectionTest, AsyncReadModifyWriteRowPermanentError) { TEST_F(DataConnectionTest, AsyncReadModifyWriteRowTransientErrorNotRetried) { auto mock = std::make_shared(); EXPECT_CALL(*mock, AsyncReadModifyWriteRow) - .WillOnce([this](google::cloud::CompletionQueue&, auto client_context, - auto, v2::ReadModifyWriteRowRequest const& request, - auto const&) { - metadata_fixture_.SetServerMetadata(*client_context, {}); - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - EXPECT_EQ("row", request.row_key()); - return make_ready_future>( - TransientError()); - }); + .WillOnce( + [this](google::cloud::CompletionQueue&, auto client_context, auto, + v2::ReadModifyWriteRowRequest const& request, + std::shared_ptr const&) { + metadata_fixture_.SetServerMetadata(*client_context, {}); + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + EXPECT_EQ("row", request.row_key()); + return make_ready_future>( + TransientError()); + }); v2::ReadModifyWriteRowRequest req; req.set_app_profile_id(kAppProfile); @@ -2663,17 +2750,19 @@ TEST_F(DataConnectionTest, AsyncReadModifyWriteRowTransientErrorNotRetried) { TEST_F(DataConnectionTest, AsyncReadRows) { auto mock = std::make_shared(); EXPECT_CALL(*mock, AsyncReadRows) - .WillOnce([](CompletionQueue const&, auto, auto, - v2::ReadRowsRequest const& request, auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - EXPECT_EQ(42, request.rows_limit()); - EXPECT_THAT(request, HasTestRowSet()); - EXPECT_THAT(request.filter(), IsTestFilter()); - using ErrorStream = - internal::AsyncStreamingReadRpcError; - return std::make_unique(PermanentError()); - }); + .WillOnce( + [](CompletionQueue const&, auto, auto, + v2::ReadRowsRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + EXPECT_EQ(42, request.rows_limit()); + EXPECT_THAT(request, HasTestRowSet()); + EXPECT_THAT(request.filter(), IsTestFilter()); + using ErrorStream = + internal::AsyncStreamingReadRpcError; + return std::make_unique(PermanentError()); + }); MockFunction(bigtable::Row const&)> on_row; EXPECT_CALL(on_row, Call).Times(0); @@ -2693,13 +2782,15 @@ TEST_F(DataConnectionTest, AsyncReadRows) { TEST_F(DataConnectionTest, AsyncReadRowsReverseScan) { auto mock = std::make_shared(); EXPECT_CALL(*mock, AsyncReadRows) - .WillOnce([](CompletionQueue const&, auto, auto, - v2::ReadRowsRequest const& request, auto const&) { - EXPECT_TRUE(request.reversed()); - using ErrorStream = - internal::AsyncStreamingReadRpcError; - return std::make_unique(PermanentError()); - }); + .WillOnce( + [](CompletionQueue const&, auto, auto, + v2::ReadRowsRequest const& request, + std::shared_ptr const&) { + EXPECT_TRUE(request.reversed()); + using ErrorStream = + internal::AsyncStreamingReadRpcError; + return std::make_unique(PermanentError()); + }); MockFunction(bigtable::Row const&)> on_row; EXPECT_CALL(on_row, Call).Times(0); @@ -2734,26 +2825,28 @@ TEST_F(DataConnectionTest, AsyncReadRowEmpty) { auto mock = std::make_shared(); EXPECT_CALL(*mock, AsyncReadRows) - .WillOnce([](CompletionQueue const&, auto, auto, - v2::ReadRowsRequest const& request, auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - EXPECT_EQ(1, request.rows_limit()); - EXPECT_THAT(request.rows().row_keys(), ElementsAre("row")); - EXPECT_THAT(request.filter(), IsTestFilter()); - - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Start).WillOnce([] { - return make_ready_future(true); - }); - EXPECT_CALL(*stream, Read).WillOnce([] { - return make_ready_future>({}); - }); - EXPECT_CALL(*stream, Finish).WillOnce([] { - return make_ready_future(Status{}); - }); - return stream; - }); + .WillOnce( + [](CompletionQueue const&, auto, auto, + v2::ReadRowsRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + EXPECT_EQ(1, request.rows_limit()); + EXPECT_THAT(request.rows().row_keys(), ElementsAre("row")); + EXPECT_THAT(request.filter(), IsTestFilter()); + + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Start).WillOnce([] { + return make_ready_future(true); + }); + EXPECT_CALL(*stream, Read).WillOnce([] { + return make_ready_future>({}); + }); + EXPECT_CALL(*stream, Finish).WillOnce([] { + return make_ready_future(Status{}); + }); + return stream; + }); auto conn = TestConnection(std::move(mock), std::move(factory)); internal::OptionsSpan span(CallOptions()); @@ -2780,36 +2873,39 @@ TEST_F(DataConnectionTest, AsyncReadRowSuccess) { auto mock = std::make_shared(); EXPECT_CALL(*mock, AsyncReadRows) - .WillOnce([](CompletionQueue const&, auto, auto, - v2::ReadRowsRequest const& request, auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - EXPECT_EQ(1, request.rows_limit()); - EXPECT_THAT(request.rows().row_keys(), ElementsAre("row")); - EXPECT_THAT(request.filter(), IsTestFilter()); - - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Start).WillOnce([] { - return make_ready_future(true); - }); - EXPECT_CALL(*stream, Read) - .WillOnce([] { - v2::ReadRowsResponse r; - auto& c = *r.add_chunks(); - c.set_row_key("row"); - c.mutable_family_name()->set_value("cf"); - c.mutable_qualifier()->set_value("cq"); - c.set_commit_row(true); - return make_ready_future(std::make_optional(r)); - }) - .WillOnce([] { - return make_ready_future>({}); + .WillOnce( + [](CompletionQueue const&, auto, auto, + v2::ReadRowsRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + EXPECT_EQ(1, request.rows_limit()); + EXPECT_THAT(request.rows().row_keys(), ElementsAre("row")); + EXPECT_THAT(request.filter(), IsTestFilter()); + + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Start).WillOnce([] { + return make_ready_future(true); }); - EXPECT_CALL(*stream, Finish).WillOnce([] { - return make_ready_future(Status{}); - }); - return stream; - }); + EXPECT_CALL(*stream, Read) + .WillOnce([] { + v2::ReadRowsResponse r; + auto& c = *r.add_chunks(); + c.set_row_key("row"); + c.mutable_family_name()->set_value("cf"); + c.mutable_qualifier()->set_value("cq"); + c.set_commit_row(true); + return make_ready_future(std::make_optional(r)); + }) + .WillOnce([] { + return make_ready_future>( + {}); + }); + EXPECT_CALL(*stream, Finish).WillOnce([] { + return make_ready_future(Status{}); + }); + return stream; + }); auto conn = TestConnection(std::move(mock), std::move(factory)); internal::OptionsSpan span(CallOptions()); @@ -2837,18 +2933,20 @@ TEST_F(DataConnectionTest, AsyncReadRowFailure) { auto mock = std::make_shared(); EXPECT_CALL(*mock, AsyncReadRows) - .WillOnce([](CompletionQueue const&, auto, auto, - v2::ReadRowsRequest const& request, auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ(kTableName, request.table_name()); - EXPECT_EQ(1, request.rows_limit()); - EXPECT_THAT(request.rows().row_keys(), ElementsAre("row")); - EXPECT_THAT(request.filter(), IsTestFilter()); - - using ErrorStream = - internal::AsyncStreamingReadRpcError; - return std::make_unique(PermanentError()); - }); + .WillOnce( + [](CompletionQueue const&, auto, auto, + v2::ReadRowsRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ(kTableName, request.table_name()); + EXPECT_EQ(1, request.rows_limit()); + EXPECT_THAT(request.rows().row_keys(), ElementsAre("row")); + EXPECT_THAT(request.filter(), IsTestFilter()); + + using ErrorStream = + internal::AsyncStreamingReadRpcError; + return std::make_unique(PermanentError()); + }); auto conn = TestConnection(std::move(mock), std::move(factory)); internal::OptionsSpan span(CallOptions()); @@ -2892,7 +2990,8 @@ TEST_F(DataConnectionTest, PrepareQuerySuccess) { kResultMetadataText, response.mutable_metadata())); EXPECT_CALL(*mock, PrepareQuery) .WillOnce([&](grpc::ClientContext&, Options const&, - v2::PrepareQueryRequest const& request, auto const&) { + v2::PrepareQueryRequest const& request, + bigtable_internal::OperationContext&) { EXPECT_EQ(kAppProfile, request.app_profile_id()); EXPECT_EQ("projects/the-project/instances/the-instance", request.instance_name()); @@ -2941,7 +3040,9 @@ TEST_F(DataConnectionTest, PrepareQueryPermanentError) { EXPECT_CALL(*mock, PrepareQuery) .WillOnce([](grpc::ClientContext&, Options const&, v2::PrepareQueryRequest const&, - auto const&) { return PermanentError(); }); + bigtable_internal::OperationContext&) { + return PermanentError(); + }); auto conn = TestConnection(std::move(mock), std::move(factory)); internal::OptionsSpan span(CallOptions()); @@ -2984,14 +3085,16 @@ TEST_F(DataConnectionTest, AsyncPrepareQuerySuccess) { ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( kResultMetadataText, response.mutable_metadata())); EXPECT_CALL(*mock, AsyncPrepareQuery) - .WillOnce([&](CompletionQueue const&, auto, auto, - v2::PrepareQueryRequest const& request, auto const&) { - EXPECT_EQ(kAppProfile, request.app_profile_id()); - EXPECT_EQ("projects/the-project/instances/the-instance", - request.instance_name()); - EXPECT_EQ("SELECT * FROM the-table", request.query()); - return make_ready_future(make_status_or(response)); - }); + .WillOnce( + [&](CompletionQueue const&, auto, auto, + v2::PrepareQueryRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(kAppProfile, request.app_profile_id()); + EXPECT_EQ("projects/the-project/instances/the-instance", + request.instance_name()); + EXPECT_EQ("SELECT * FROM the-table", request.query()); + return make_ready_future(make_status_or(response)); + }); auto fake_cq_impl = std::make_shared(); auto mock_bg = std::make_unique(); @@ -3031,11 +3134,12 @@ TEST_F(DataConnectionTest, AsyncPrepareQueryPermanentError) { #endif auto mock = std::make_shared(); EXPECT_CALL(*mock, AsyncPrepareQuery) - .WillOnce([](CompletionQueue&, auto, auto, v2::PrepareQueryRequest const&, - auto const&) { - return make_ready_future>( - PermanentError()); - }); + .WillOnce( + [](CompletionQueue&, auto, auto, v2::PrepareQueryRequest const&, + std::shared_ptr const&) { + return make_ready_future>( + PermanentError()); + }); auto conn = TestConnection(std::move(mock), std::move(factory)); internal::OptionsSpan span(CallOptions()); @@ -3095,44 +3199,47 @@ TEST_F(DataConnectionTest, ExecuteQuerySuccessWithTransientErrors) { Status{StatusCode::kUnimplemented, "not implemented"})); }; EXPECT_CALL(*mock, ExecuteQuery) - .WillOnce([&](auto, auto const&, - google::bigtable::v2::ExecuteQueryRequest const&, - auto const&) { - auto error_stream = std::make_unique(); - EXPECT_CALL(*error_stream, Read).WillOnce(Return(TransientError())); - return error_stream; - }) - .WillOnce([&](auto, auto const&, - google::bigtable::v2::ExecuteQueryRequest const&, - auto const&) { - auto error_stream = std::make_unique(); - EXPECT_CALL(*error_stream, Read).WillOnce(Return(TransientError())); - return error_stream; - }) - .WillOnce([&](auto, auto const&, - google::bigtable::v2::ExecuteQueryRequest const& request, - auto const&) { - EXPECT_EQ(request.app_profile_id(), kAppProfile); - EXPECT_EQ(request.instance_name(), - "projects/test-project/instances/test-instance"); - - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Read) - .WillOnce([&](google::bigtable::v2::ExecuteQueryResponse* r) { - MakeResponse(*r->mutable_results(), {"r1", "v1"}, std::nullopt, - false); - return std::nullopt; - }) - .WillOnce([&](google::bigtable::v2::ExecuteQueryResponse* r) { - MakeResponse(*r->mutable_results(), {"r2", "v2"}, - "sentinel-token", false); - return std::nullopt; - }) - // End of stream - .WillOnce(Return(google::cloud::Status())); - - return stream; - }); + .WillOnce( + [&](auto, auto const&, + google::bigtable::v2::ExecuteQueryRequest const&, + std::shared_ptr const&) { + auto error_stream = std::make_unique(); + EXPECT_CALL(*error_stream, Read).WillOnce(Return(TransientError())); + return error_stream; + }) + .WillOnce( + [&](auto, auto const&, + google::bigtable::v2::ExecuteQueryRequest const&, + std::shared_ptr const&) { + auto error_stream = std::make_unique(); + EXPECT_CALL(*error_stream, Read).WillOnce(Return(TransientError())); + return error_stream; + }) + .WillOnce( + [&](auto, auto const&, + google::bigtable::v2::ExecuteQueryRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(request.app_profile_id(), kAppProfile); + EXPECT_EQ(request.instance_name(), + "projects/test-project/instances/test-instance"); + + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Read) + .WillOnce([&](google::bigtable::v2::ExecuteQueryResponse* r) { + MakeResponse(*r->mutable_results(), {"r1", "v1"}, + std::nullopt, false); + return std::nullopt; + }) + .WillOnce([&](google::bigtable::v2::ExecuteQueryResponse* r) { + MakeResponse(*r->mutable_results(), {"r2", "v2"}, + "sentinel-token", false); + return std::nullopt; + }) + // End of stream + .WillOnce(Return(google::cloud::Status())); + + return stream; + }); auto conn = TestConnection(std::move(mock), std::move(factory)); internal::OptionsSpan span(CallOptions()); @@ -3199,11 +3306,13 @@ TEST_F(DataConnectionTest, ExecuteQueryFailure) { std::move(refresh_fn)); EXPECT_CALL(*mock, ExecuteQuery) - .WillOnce([&](auto, auto const&, auto const&, auto const&) { - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Read).WillOnce(Return(PermanentError())); - return stream; - }); + .WillOnce( + [&](auto, auto const&, auto const&, + std::shared_ptr const&) { + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Read).WillOnce(Return(PermanentError())); + return stream; + }); auto conn = TestConnection(std::move(mock), std::move(factory)); internal::OptionsSpan span(CallOptions()); @@ -3254,11 +3363,13 @@ TEST_F(DataConnectionTest, ExecuteQueryOperationRetryExhausted) { EXPECT_CALL(*mock, ExecuteQuery) .Times(3) - .WillRepeatedly([&](auto, auto const&, auto const&, auto const&) { - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Read).WillOnce(Return(TransientError())); - return stream; - }); + .WillRepeatedly( + [&](auto, auto const&, auto const&, + std::shared_ptr const&) { + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Read).WillOnce(Return(TransientError())); + return stream; + }); auto conn = TestConnection(std::move(mock), std::move(factory)); internal::OptionsSpan span(CallOptions()); @@ -3352,39 +3463,41 @@ TEST_F(DataConnectionTest, ExecuteQuerySuccessWithQueryPlanRefresh) { }); EXPECT_CALL(*mock, ExecuteQuery) - .WillOnce([&](auto, auto const&, - google::bigtable::v2::ExecuteQueryRequest const& request, - auto const&) { - EXPECT_EQ(request.prepared_query(), "test-pq-id-initial"); - auto error_stream = std::make_unique(); - EXPECT_CALL(*error_stream, Read).WillOnce(Return(QueryPlanError())); - return error_stream; - }) - .WillOnce([&](auto, auto const&, - google::bigtable::v2::ExecuteQueryRequest const& request, - auto const&) { - EXPECT_EQ(request.app_profile_id(), kAppProfile); - EXPECT_EQ(request.instance_name(), - "projects/test-project/instances/test-instance"); - EXPECT_EQ(request.prepared_query(), "test-pq-id-refresh"); - - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Read) - .WillOnce([&](google::bigtable::v2::ExecuteQueryResponse* r) { - MakeResponse(*r->mutable_results(), {"r1", "v1"}, std::nullopt, - false); - return std::nullopt; - }) - .WillOnce([&](google::bigtable::v2::ExecuteQueryResponse* r) { - MakeResponse(*r->mutable_results(), {"r2", "v2"}, - "sentinel-token", false); - return std::nullopt; - }) - // End of stream - .WillOnce(Return(google::cloud::Status())); - - return stream; - }); + .WillOnce( + [&](auto, auto const&, + google::bigtable::v2::ExecuteQueryRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(request.prepared_query(), "test-pq-id-initial"); + auto error_stream = std::make_unique(); + EXPECT_CALL(*error_stream, Read).WillOnce(Return(QueryPlanError())); + return error_stream; + }) + .WillOnce( + [&](auto, auto const&, + google::bigtable::v2::ExecuteQueryRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(request.app_profile_id(), kAppProfile); + EXPECT_EQ(request.instance_name(), + "projects/test-project/instances/test-instance"); + EXPECT_EQ(request.prepared_query(), "test-pq-id-refresh"); + + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Read) + .WillOnce([&](google::bigtable::v2::ExecuteQueryResponse* r) { + MakeResponse(*r->mutable_results(), {"r1", "v1"}, + std::nullopt, false); + return std::nullopt; + }) + .WillOnce([&](google::bigtable::v2::ExecuteQueryResponse* r) { + MakeResponse(*r->mutable_results(), {"r2", "v2"}, + "sentinel-token", false); + return std::nullopt; + }) + // End of stream + .WillOnce(Return(google::cloud::Status())); + + return stream; + }); auto conn = TestConnection(std::move(mock), std::move(factory)); internal::OptionsSpan span(CallOptions()); @@ -3518,44 +3631,50 @@ TEST_F(DataConnectionTest, PrepareAndExecuteQuerySuccessWithQueryPlanRefresh) { EXPECT_CALL(*mock, PrepareQuery) .WillOnce([&](grpc::ClientContext&, Options const&, PrepareQueryRequest const&, - auto const&) { return initial_pq_response; }); - EXPECT_CALL(*mock, AsyncPrepareQuery) - .WillOnce([&](CompletionQueue const&, auto, auto, - v2::PrepareQueryRequest const&, auto const&) { - return make_ready_future(make_status_or(refresh_pq_response)); + bigtable_internal::OperationContext&) { + return initial_pq_response; }); + EXPECT_CALL(*mock, AsyncPrepareQuery) + .WillOnce( + [&](CompletionQueue const&, auto, auto, + v2::PrepareQueryRequest const&, + std::shared_ptr const&) { + return make_ready_future(make_status_or(refresh_pq_response)); + }); EXPECT_CALL(*mock, ExecuteQuery) - .WillOnce([&](auto, auto const&, ExecuteQueryRequest const& request, - auto const&) { - EXPECT_EQ(request.prepared_query(), "test-pq-id-initial"); - auto error_stream = std::make_unique(); - EXPECT_CALL(*error_stream, Read).WillOnce(Return(QueryPlanError())); - return error_stream; - }) - .WillOnce([&](auto, auto const&, ExecuteQueryRequest const& request, - auto const&) { - EXPECT_EQ(request.app_profile_id(), kAppProfile); - EXPECT_EQ(request.instance_name(), - "projects/test-project/instances/test-instance"); - EXPECT_EQ(request.prepared_query(), "test-pq-id-refresh"); - - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Read) - .WillOnce([&](ExecuteQueryResponse* r) { - MakeResponse(*r->mutable_results(), {"r1", "v1"}, std::nullopt, - false); - return std::nullopt; - }) - .WillOnce([&](ExecuteQueryResponse* r) { - MakeResponse(*r->mutable_results(), {"r2", "v2"}, - "sentinel-token", false); - return std::nullopt; - }) - // End of stream - .WillOnce(Return(google::cloud::Status())); - - return stream; - }); + .WillOnce( + [&](auto, auto const&, ExecuteQueryRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(request.prepared_query(), "test-pq-id-initial"); + auto error_stream = std::make_unique(); + EXPECT_CALL(*error_stream, Read).WillOnce(Return(QueryPlanError())); + return error_stream; + }) + .WillOnce( + [&](auto, auto const&, ExecuteQueryRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(request.app_profile_id(), kAppProfile); + EXPECT_EQ(request.instance_name(), + "projects/test-project/instances/test-instance"); + EXPECT_EQ(request.prepared_query(), "test-pq-id-refresh"); + + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Read) + .WillOnce([&](ExecuteQueryResponse* r) { + MakeResponse(*r->mutable_results(), {"r1", "v1"}, + std::nullopt, false); + return std::nullopt; + }) + .WillOnce([&](ExecuteQueryResponse* r) { + MakeResponse(*r->mutable_results(), {"r2", "v2"}, + "sentinel-token", false); + return std::nullopt; + }) + // End of stream + .WillOnce(Return(google::cloud::Status())); + + return stream; + }); auto conn = TestConnection(std::move(mock), std::move(factory)); internal::OptionsSpan span(CallOptions()); @@ -3686,46 +3805,50 @@ TEST_F(DataConnectionTest, auto mock = std::make_shared(); EXPECT_CALL(*mock, AsyncPrepareQuery) - .WillOnce([&](CompletionQueue const&, auto, auto, - PrepareQueryRequest const&, auto const&) { - return make_ready_future(make_status_or(initial_pq_response)); - }) - .WillOnce([&](CompletionQueue const&, auto, auto, - PrepareQueryRequest const&, auto const&) { - return make_ready_future(make_status_or(refresh_pq_response)); - }); + .WillOnce( + [&](CompletionQueue const&, auto, auto, PrepareQueryRequest const&, + std::shared_ptr const&) { + return make_ready_future(make_status_or(initial_pq_response)); + }) + .WillOnce( + [&](CompletionQueue const&, auto, auto, PrepareQueryRequest const&, + std::shared_ptr const&) { + return make_ready_future(make_status_or(refresh_pq_response)); + }); EXPECT_CALL(*mock, ExecuteQuery) - .WillOnce([&](auto, auto const&, ExecuteQueryRequest const& request, - auto const&) { - EXPECT_EQ(request.prepared_query(), "test-pq-id-initial"); - auto error_stream = std::make_unique(); - EXPECT_CALL(*error_stream, Read).WillOnce(Return(QueryPlanError())); - return error_stream; - }) - .WillOnce([&](auto, auto const&, ExecuteQueryRequest const& request, - auto const&) { - EXPECT_EQ(request.app_profile_id(), kAppProfile); - EXPECT_EQ(request.instance_name(), - "projects/test-project/instances/test-instance"); - EXPECT_EQ(request.prepared_query(), "test-pq-id-refresh"); - - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Read) - .WillOnce([&](ExecuteQueryResponse* r) { - MakeResponse(*r->mutable_results(), {"r1", "v1"}, std::nullopt, - false); - return std::nullopt; - }) - .WillOnce([&](ExecuteQueryResponse* r) { - MakeResponse(*r->mutable_results(), {"r2", "v2"}, - "sentinel-token", false); - return std::nullopt; - }) - // End of stream - .WillOnce(Return(google::cloud::Status())); - - return stream; - }); + .WillOnce( + [&](auto, auto const&, ExecuteQueryRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(request.prepared_query(), "test-pq-id-initial"); + auto error_stream = std::make_unique(); + EXPECT_CALL(*error_stream, Read).WillOnce(Return(QueryPlanError())); + return error_stream; + }) + .WillOnce( + [&](auto, auto const&, ExecuteQueryRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(request.app_profile_id(), kAppProfile); + EXPECT_EQ(request.instance_name(), + "projects/test-project/instances/test-instance"); + EXPECT_EQ(request.prepared_query(), "test-pq-id-refresh"); + + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Read) + .WillOnce([&](ExecuteQueryResponse* r) { + MakeResponse(*r->mutable_results(), {"r1", "v1"}, + std::nullopt, false); + return std::nullopt; + }) + .WillOnce([&](ExecuteQueryResponse* r) { + MakeResponse(*r->mutable_results(), {"r2", "v2"}, + "sentinel-token", false); + return std::nullopt; + }) + // End of stream + .WillOnce(Return(google::cloud::Status())); + + return stream; + }); auto conn = TestConnection(std::move(mock), std::move(factory)); internal::OptionsSpan span(CallOptions()); @@ -3794,20 +3917,21 @@ TEST_F(DataConnectionTest, ExecuteQueryFailureWithSchemaChange) { Status{StatusCode::kUnimplemented, "not implemented"})); }; EXPECT_CALL(*mock, ExecuteQuery) - .WillOnce([&](auto, auto const&, - google::bigtable::v2::ExecuteQueryRequest const& request, - auto const&) { - EXPECT_EQ(request.app_profile_id(), kAppProfile); - EXPECT_EQ(request.instance_name(), - "projects/test-project/instances/test-instance"); - auto stream = std::make_unique(); - EXPECT_CALL(*stream, Read) - .WillOnce([&](google::bigtable::v2::ExecuteQueryResponse* r) { - *r = eq_response; - return std::nullopt; - }); - return stream; - }); + .WillOnce( + [&](auto, auto const&, + google::bigtable::v2::ExecuteQueryRequest const& request, + std::shared_ptr const&) { + EXPECT_EQ(request.app_profile_id(), kAppProfile); + EXPECT_EQ(request.instance_name(), + "projects/test-project/instances/test-instance"); + auto stream = std::make_unique(); + EXPECT_CALL(*stream, Read) + .WillOnce([&](google::bigtable::v2::ExecuteQueryResponse* r) { + *r = eq_response; + return std::nullopt; + }); + return stream; + }); auto conn = TestConnection(std::move(mock), std::move(factory)); internal::OptionsSpan span(CallOptions()); @@ -3860,9 +3984,10 @@ TEST_F(DataConnectionTest, PrepareQueryFailsOnInvalidType) { std::chrono::system_clock::now() + std::chrono::seconds(3600)); EXPECT_CALL(*mock, PrepareQuery) - .WillOnce([&](grpc::ClientContext&, Options const&, - v2::PrepareQueryRequest const&, - auto const&) { return pq_response; }); + .WillOnce( + [&](grpc::ClientContext&, Options const&, + v2::PrepareQueryRequest const&, + bigtable_internal::OperationContext&) { return pq_response; }); auto conn = TestConnection(std::move(mock)); internal::OptionsSpan span(CallOptions()); @@ -3905,10 +4030,12 @@ TEST_F(DataConnectionTest, AsyncPrepareQueryFailsOnInvalidType) { std::chrono::system_clock::now() + std::chrono::seconds(3600)); EXPECT_CALL(*mock, AsyncPrepareQuery) - .WillOnce([&](CompletionQueue const&, auto, auto, - v2::PrepareQueryRequest const&, auto const&) { - return make_ready_future(make_status_or(pq_response)); - }); + .WillOnce( + [&](CompletionQueue const&, auto, auto, + v2::PrepareQueryRequest const&, + std::shared_ptr const&) { + return make_ready_future(make_status_or(pq_response)); + }); auto conn = TestConnection(std::move(mock)); internal::OptionsSpan span(CallOptions()); diff --git a/google/cloud/bigtable/internal/default_row_reader_test.cc b/google/cloud/bigtable/internal/default_row_reader_test.cc index c69a55ef257d0..87829dfd4670f 100644 --- a/google/cloud/bigtable/internal/default_row_reader_test.cc +++ b/google/cloud/bigtable/internal/default_row_reader_test.cc @@ -20,6 +20,7 @@ #include "google/cloud/internal/make_status.h" #ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS #include "google/cloud/bigtable/internal/metrics.h" +#include "google/cloud/bigtable/internal/table_schema_metrics.h" #include "google/cloud/testing_util/fake_clock.h" #endif #include "google/cloud/testing_util/mock_backoff_policy.h" @@ -105,7 +106,7 @@ std::vector> StatusOrRowKeys( #ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS -class MockMetric : public Metric { +class MockMetric : public TableSchemaMetric { public: MOCK_METHOD(void, PreCall, (opentelemetry::context::Context const&, PreCallParams const&), @@ -125,7 +126,7 @@ class MockMetric : public Metric { (opentelemetry::context::Context const&, ElementDeliveryParams const&), (override)); - MOCK_METHOD(std::unique_ptr, clone, + MOCK_METHOD(std::unique_ptr, clone, (TableResourceLabels const& resource_labels, TableDataLabels const& data_labels), (const, override)); diff --git a/google/cloud/bigtable/internal/metrics.cc b/google/cloud/bigtable/internal/metrics.cc index b4b10b37e5c74..1f2c20496c73a 100644 --- a/google/cloud/bigtable/internal/metrics.cc +++ b/google/cloud/bigtable/internal/metrics.cc @@ -15,550 +15,14 @@ #ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS #include "google/cloud/bigtable/internal/metrics.h" -#include "google/cloud/bigtable/version.h" -#include "absl/strings/charconv.h" -#include "absl/strings/escaping.h" -#include "absl/strings/match.h" -#include "absl/strings/numbers.h" -#include "absl/strings/str_split.h" -#include "absl/strings/strip.h" -#include -#include -#include namespace google { namespace cloud { namespace bigtable_internal { GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN -namespace { -auto constexpr kMeterInstrumentationScopeVersion = "v1"; -} // namespace - -// TODO(#15329): Refactor how we're handling different data labels for -// the various RPCs. Adding a function to each metric type to add its -// TableDataLabels to the map should be more performant than performing a -// set_difference every time. -LabelMap IntoLabelMap(TableResourceLabels const& r, TableDataLabels const& d, - std::set const& filtered_data_labels, - std::optional const& peer_info_labels) { - LabelMap labels = { - {"project_id", r.project_id}, - {"instance", r.instance}, - {"table", r.table}, - {"cluster", r.cluster.empty() ? "" : r.cluster}, - {"zone", r.zone.empty() ? "global" : r.zone}}; - - if (peer_info_labels) { - labels.insert({ - {"transport_type", peer_info_labels->transport_type}, - {"transport_region", peer_info_labels->transport_region}, - {"transport_subzone", peer_info_labels->transport_subzone}, - }); - } - - std::map data = {{ - {"method", d.method}, - {"streaming", d.streaming}, - {"client_name", d.client_name}, - {"client_uid", d.client_uid}, - {"app_profile", d.app_profile}, - {"status", d.status}, - }}; - - if (filtered_data_labels.empty()) { - labels.insert(data.begin(), data.end()); - return labels; - } - - struct Compare { - bool operator()(std::pair const& a, - std::string const& b) { - return a.first < b; - } - - bool operator()(std::string const& a, - std::pair const& b) { - return a < b.first; - } - }; - - std::set_difference(data.begin(), data.end(), filtered_data_labels.begin(), - filtered_data_labels.end(), - std::inserter(labels, labels.begin()), Compare()); - - return labels; -} - -bool HasServerTiming(grpc::ClientContext const& client_context) { - auto const& initial_metadata = client_context.GetServerInitialMetadata(); - auto it = initial_metadata.find("server-timing"); - return it != initial_metadata.end(); -} - -bool IsConnectivityError(google::cloud::Status const& status, - grpc::ClientContext const& client_context) { - return status.code() != google::cloud::StatusCode::kDeadlineExceeded && - !HasServerTiming(client_context); -} - -std::optional -GetResponseParamsFromTrailingMetadata( - grpc::ClientContext const& client_context) { - auto metadata = client_context.GetServerTrailingMetadata(); - auto iter = metadata.find("x-goog-ext-425905942-bin"); - if (iter == metadata.end()) return std::nullopt; - google::bigtable::v2::ResponseParams p; - // The value for this key should always be the same in a response, so we - // return the first value we find. - std::string value{iter->second.data(), iter->second.size()}; - if (p.ParseFromString(value)) return p; - return std::nullopt; -} - -std::optional GetPeerInfoFromServerMetadata( - grpc::ClientContext const& client_context) { - // The peer info is sent in the initial metadata and encoded in WebSafeBase64. - std::string decoded; - auto const& init_metadata = client_context.GetServerInitialMetadata(); - auto iter_init = init_metadata.find("bigtable-peer-info"); - if (iter_init == init_metadata.end() || - !absl::WebSafeBase64Unescape( - absl::string_view{iter_init->second.data(), iter_init->second.size()}, - &decoded)) { - // Find it in trailing metadata if not found in initial metadata or failed - // to decode. - auto const& trailing_metadata = client_context.GetServerTrailingMetadata(); - auto iter_trailing = trailing_metadata.find("bigtable-peer-info"); - if (iter_trailing == trailing_metadata.end() || - !absl::WebSafeBase64Unescape( - absl::string_view{iter_trailing->second.data(), - iter_trailing->second.size()}, - &decoded)) { - return std::nullopt; - } - } - google::bigtable::v2::PeerInfo p; - if (p.ParseFromString(decoded)) return p; - return std::nullopt; -} - -std::optional GetServerLatencyFromInitialMetadata( - grpc::ClientContext const& client_context) { - auto const& initial_metadata = client_context.GetServerInitialMetadata(); - auto it = initial_metadata.find("server-timing"); - if (it == initial_metadata.end()) { - return std::nullopt; - } - - absl::string_view value(it->second.data(), it->second.length()); - - for (absl::string_view entry : absl::StrSplit(value, ',')) { - entry = absl::StripAsciiWhitespace(entry); - std::vector parts = absl::StrSplit(entry, ';'); - if (parts.empty()) { - continue; - } - - absl::string_view metric_name = absl::StripAsciiWhitespace(parts[0]); - if (metric_name == "gfet4t7") { - // Look for the "dur" parameter within its parts. - for (size_t i = 1; i < parts.size(); ++i) { - absl::string_view param = absl::StripAsciiWhitespace(parts[i]); - if (absl::ConsumePrefix(¶m, "dur=")) { - double dur_value; - auto result = absl::from_chars( - param.data(), param.data() + param.size(), dur_value); - if (result.ec == std::errc()) { - return dur_value; - } - return std::nullopt; - } - } - } - } - - return std::nullopt; -} Metric::~Metric() = default; -OperationLatency::OperationLatency( - std::string const& instrumentation_scope, - opentelemetry::nostd::shared_ptr< - opentelemetry::metrics::MeterProvider> const& provider) - : operation_latencies_(provider - ->GetMeter(instrumentation_scope, - kMeterInstrumentationScopeVersion) - ->CreateDoubleHistogram("operation_latencies") - .release()) {} - -void OperationLatency::PreCall(opentelemetry::context::Context const&, - PreCallParams const& p) { - if (p.first_attempt) { - operation_start_ = p.attempt_start; - } -} - -void OperationLatency::PostCall(opentelemetry::context::Context const&, - grpc::ClientContext const& client_context, - PostCallParams const&) { - auto response_params = GetResponseParamsFromTrailingMetadata(client_context); - if (response_params) { - resource_labels_.cluster = response_params->cluster_id(); - resource_labels_.zone = response_params->zone_id(); - } -} - -void OperationLatency::OnDone(opentelemetry::context::Context const& context, - OnDoneParams const& p) { - data_labels_.status = StatusCodeToString(p.operation_status.code()); - auto operation_elapsed = std::chrono::duration_cast( - p.operation_end - operation_start_); - operation_latencies_->Record(operation_elapsed.count(), - IntoLabelMap(resource_labels_, data_labels_), - context); -} - -std::unique_ptr OperationLatency::clone( - TableResourceLabels const& resource_labels, - TableDataLabels const& data_labels) const { - auto m = std::make_unique(*this); - m->resource_labels_ = resource_labels; - m->data_labels_ = data_labels; - return m; -} - -AttemptLatency::AttemptLatency( - std::string const& instrumentation_scope, - opentelemetry::nostd::shared_ptr< - opentelemetry::metrics::MeterProvider> const& provider) - : attempt_latencies_(provider - ->GetMeter(instrumentation_scope, - kMeterInstrumentationScopeVersion) - ->CreateDoubleHistogram("attempt_latencies")) {} - -void AttemptLatency::PreCall(opentelemetry::context::Context const&, - PreCallParams const& p) { - attempt_start_ = p.attempt_start; -} - -void AttemptLatency::PostCall(opentelemetry::context::Context const& context, - grpc::ClientContext const& client_context, - PostCallParams const& p) { - auto response_params = GetResponseParamsFromTrailingMetadata(client_context); - if (response_params) { - resource_labels_.cluster = response_params->cluster_id(); - resource_labels_.zone = response_params->zone_id(); - } - data_labels_.status = StatusCodeToString(p.attempt_status.code()); - auto attempt_elapsed = std::chrono::duration_cast( - p.attempt_end - attempt_start_); - auto m = IntoLabelMap(resource_labels_, data_labels_); - attempt_latencies_->Record(attempt_elapsed.count(), std::move(m), context); -} - -std::unique_ptr AttemptLatency::clone( - TableResourceLabels const& resource_labels, - TableDataLabels const& data_labels) const { - auto m = std::make_unique(*this); - m->resource_labels_ = resource_labels; - m->data_labels_ = data_labels; - return m; -} - -AttemptLatency2::AttemptLatency2( - std::string const& instrumentation_scope, - opentelemetry::nostd::shared_ptr< - opentelemetry::metrics::MeterProvider> const& provider) - : attempt_latencies2_(provider - ->GetMeter(instrumentation_scope, - kMeterInstrumentationScopeVersion) - ->CreateDoubleHistogram("attempt_latencies2")) {} - -void AttemptLatency2::PreCall(opentelemetry::context::Context const&, - PreCallParams const& p) { - attempt_start_ = p.attempt_start; -} - -void AttemptLatency2::PostCall(opentelemetry::context::Context const& context, - grpc::ClientContext const& client_context, - PostCallParams const& p) { - auto response_params = GetResponseParamsFromTrailingMetadata(client_context); - if (response_params) { - resource_labels_.cluster = response_params->cluster_id(); - resource_labels_.zone = response_params->zone_id(); - } - - auto peer_info = GetPeerInfoFromServerMetadata(client_context); - peer_info_labels_.transport_type = - absl::AsciiStrToLower(google::bigtable::v2::PeerInfo::TransportType_Name( - peer_info ? peer_info->transport_type() - : google::bigtable::v2::PeerInfo::TRANSPORT_TYPE_UNKNOWN)); - if (peer_info) { - peer_info_labels_.transport_region = - peer_info->application_frontend_region(); - peer_info_labels_.transport_subzone = - peer_info->application_frontend_subzone(); - } - - data_labels_.status = StatusCodeToString(p.attempt_status.code()); - auto attempt_elapsed = std::chrono::duration_cast( - p.attempt_end - attempt_start_); - auto m = IntoLabelMap(resource_labels_, data_labels_, {}, peer_info_labels_); - attempt_latencies2_->Record(attempt_elapsed.count(), std::move(m), context); -} - -std::unique_ptr AttemptLatency2::clone( - TableResourceLabels const& resource_labels, - TableDataLabels const& data_labels) const { - auto m = std::make_unique(*this); - m->resource_labels_ = resource_labels; - m->data_labels_ = data_labels; - return m; -} - -RetryCount::RetryCount( - std::string const& instrumentation_scope, - opentelemetry::nostd::shared_ptr< - opentelemetry::metrics::MeterProvider> const& provider) - : retry_count_(provider - ->GetMeter(instrumentation_scope, - kMeterInstrumentationScopeVersion) - ->CreateUInt64Counter("retry_count") - .release()) {} - -void RetryCount::PreCall(opentelemetry::context::Context const&, - PreCallParams const& p) { - if (!p.first_attempt) { - ++num_retries_; - } -} - -void RetryCount::PostCall(opentelemetry::context::Context const&, - grpc::ClientContext const& client_context, - PostCallParams const&) { - auto response_params = GetResponseParamsFromTrailingMetadata(client_context); - if (response_params) { - resource_labels_.cluster = response_params->cluster_id(); - resource_labels_.zone = response_params->zone_id(); - } -} - -void RetryCount::OnDone(opentelemetry::context::Context const& context, - OnDoneParams const& p) { - data_labels_.status = StatusCodeToString(p.operation_status.code()); - retry_count_->Add(num_retries_, - IntoLabelMap(resource_labels_, data_labels_, - std::set{"streaming"}), - context); -} - -std::unique_ptr RetryCount::clone( - TableResourceLabels const& resource_labels, - TableDataLabels const& data_labels) const { - auto m = std::make_unique(*this); - m->resource_labels_ = resource_labels; - m->data_labels_ = data_labels; - return m; -} - -FirstResponseLatency::FirstResponseLatency( - std::string const& instrumentation_scope, - opentelemetry::nostd::shared_ptr< - opentelemetry::metrics::MeterProvider> const& provider) - : first_response_latencies_( - provider - ->GetMeter(instrumentation_scope, - kMeterInstrumentationScopeVersion) - ->CreateDoubleHistogram("first_response_latencies")) {} - -void FirstResponseLatency::PreCall(opentelemetry::context::Context const&, - PreCallParams const& p) { - if (p.first_attempt) { - operation_start_ = p.attempt_start; - } -} - -void FirstResponseLatency::PostCall(opentelemetry::context::Context const&, - grpc::ClientContext const& client_context, - PostCallParams const&) { - auto response_params = GetResponseParamsFromTrailingMetadata(client_context); - if (response_params) { - resource_labels_.cluster = response_params->cluster_id(); - resource_labels_.zone = response_params->zone_id(); - } -} - -void FirstResponseLatency::ElementDelivery( - opentelemetry::context::Context const&, ElementDeliveryParams const& p) { - if (p.first_response) { - first_response_latency_ = std::chrono::duration_cast( - p.element_delivery - operation_start_); - } -} - -void FirstResponseLatency::OnDone( - opentelemetry::context::Context const& context, OnDoneParams const& p) { - if (first_response_latency_) { - data_labels_.status = StatusCodeToString(p.operation_status.code()); - auto m = IntoLabelMap(resource_labels_, data_labels_, - std::set{"streaming"}); - first_response_latencies_->Record(first_response_latency_->count(), - std::move(m), context); - } -} - -std::unique_ptr FirstResponseLatency::clone( - TableResourceLabels const& resource_labels, - TableDataLabels const& data_labels) const { - auto m = std::make_unique(*this); - m->resource_labels_ = resource_labels; - m->data_labels_ = data_labels; - return m; -} - -ServerLatency::ServerLatency( - std::string const& instrumentation_scope, - opentelemetry::nostd::shared_ptr< - opentelemetry::metrics::MeterProvider> const& provider) - : server_latencies_(provider - ->GetMeter(instrumentation_scope, - kMeterInstrumentationScopeVersion) - ->CreateDoubleHistogram("server_latencies")) {} - -void ServerLatency::PostCall(opentelemetry::context::Context const& context, - grpc::ClientContext const& client_context, - PostCallParams const& p) { - auto response_params = GetResponseParamsFromTrailingMetadata(client_context); - if (response_params) { - resource_labels_.cluster = response_params->cluster_id(); - resource_labels_.zone = response_params->zone_id(); - } - data_labels_.status = StatusCodeToString(p.attempt_status.code()); - auto server_latency = GetServerLatencyFromInitialMetadata(client_context); - if (server_latency) { - auto m = IntoLabelMap(resource_labels_, data_labels_); - server_latencies_->Record(*server_latency, std::move(m), context); - } -} - -std::unique_ptr ServerLatency::clone( - TableResourceLabels const& resource_labels, - TableDataLabels const& data_labels) const { - auto m = std::make_unique(*this); - m->resource_labels_ = resource_labels; - m->data_labels_ = data_labels; - return m; -} - -ConnectivityErrorCount::ConnectivityErrorCount( - std::string const& instrumentation_scope, - opentelemetry::nostd::shared_ptr< - opentelemetry::metrics::MeterProvider> const& provider) - : connectivity_error_count_( - provider - ->GetMeter(instrumentation_scope, - kMeterInstrumentationScopeVersion) - ->CreateUInt64Counter("connectivity_error_count") - .release()) {} - -void ConnectivityErrorCount::PostCall(opentelemetry::context::Context const&, - grpc::ClientContext const& client_context, - PostCallParams const& p) { - auto response_params = GetResponseParamsFromTrailingMetadata(client_context); - if (response_params) { - resource_labels_.cluster = response_params->cluster_id(); - resource_labels_.zone = response_params->zone_id(); - } - auto const& status = p.attempt_status; - data_labels_.status = StatusCodeToString(status.code()); - if (resource_labels_.cluster.empty() || resource_labels_.zone.empty() || - IsConnectivityError(status, client_context)) { - ++num_errors_; - } -} - -void ConnectivityErrorCount::OnDone( - opentelemetry::context::Context const& context, OnDoneParams const&) { - auto m = IntoLabelMap(resource_labels_, data_labels_, - std::set{"streaming"}); - connectivity_error_count_->Add(num_errors_, std::move(m), context); -} - -std::unique_ptr ConnectivityErrorCount::clone( - TableResourceLabels const& resource_labels, - TableDataLabels const& data_labels) const { - auto m = std::make_unique(*this); - m->resource_labels_ = resource_labels; - m->data_labels_ = data_labels; - return m; -} - -ApplicationBlockingLatency::ApplicationBlockingLatency( - std::string const& instrumentation_scope, - opentelemetry::nostd::shared_ptr< - opentelemetry::metrics::MeterProvider> const& provider) - : application_blocking_latencies_( - provider - ->GetMeter(instrumentation_scope, - kMeterInstrumentationScopeVersion) - ->CreateDoubleHistogram("application_latencies")) {} - -void ApplicationBlockingLatency::ElementDelivery( - opentelemetry::context::Context const&, ElementDeliveryParams const& p) { - element_delivery_time_ = p.element_delivery; -} - -void ApplicationBlockingLatency::ElementRequest( - opentelemetry::context::Context const&, ElementRequestParams const& p) { - auto application_blocking_latency = - std::chrono::duration_cast(p.element_request - - element_delivery_time_); - pending_latencies_.push_back(application_blocking_latency); -} - -void ApplicationBlockingLatency::PostCall( - opentelemetry::context::Context const&, - grpc::ClientContext const& client_context, PostCallParams const&) { - auto response_params = GetResponseParamsFromTrailingMetadata(client_context); - if (response_params) { - resource_labels_.cluster = response_params->cluster_id(); - resource_labels_.zone = response_params->zone_id(); - } -} - -void ApplicationBlockingLatency::OnDone( - opentelemetry::context::Context const& context, OnDoneParams const&) { - auto m = IntoLabelMap(resource_labels_, data_labels_, - std::set{"streaming", "status"}); - for (auto const& latency : pending_latencies_) { - application_blocking_latencies_->Record(latency.count(), m, context); - } -} - -std::unique_ptr ApplicationBlockingLatency::clone( - TableResourceLabels const& resource_labels, - TableDataLabels const& data_labels) const { - auto m = std::make_unique(*this); - m->resource_labels_ = resource_labels; - m->data_labels_ = data_labels; - return m; -} - -std::vector> CloneMetrics( - TableResourceLabels const& resource_labels, - TableDataLabels const& data_labels, - std::vector> const& metrics) { - std::vector> v; - v.reserve(metrics.size()); - for (auto const& m : metrics) { - // We should never add a nullptr Metric to the list. - if (m == nullptr) continue; - v.push_back(m->clone(resource_labels, data_labels)); - } - return v; -} - GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END } // namespace bigtable_internal } // namespace cloud diff --git a/google/cloud/bigtable/internal/metrics.h b/google/cloud/bigtable/internal/metrics.h index 60991e403ad8f..2dda76df9d0c3 100644 --- a/google/cloud/bigtable/internal/metrics.h +++ b/google/cloud/bigtable/internal/metrics.h @@ -20,66 +20,19 @@ #include "google/cloud/bigtable/internal/operation_context.h" #include "google/cloud/bigtable/version.h" #include "google/cloud/status.h" -#include "google/bigtable/v2/peer_info.pb.h" -#include "google/bigtable/v2/response_params.pb.h" #include #include -#include -#include -#include +#include +#include #include #include -#include +#include namespace google { namespace cloud { namespace bigtable_internal { GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN -struct TableResourceLabels { - std::string project_id; - std::string instance; - std::string table; - std::string cluster; - std::string zone; -}; - -struct TableDataLabels { - std::string method; - std::string streaming; - std::string client_name; - std::string client_uid; - std::string app_profile; - std::string status; -}; - -// Labels populated from the peer info metadata. -struct PeerInfoLabels { - std::string transport_type; - std::string transport_region; - std::string transport_subzone; -}; - -using LabelMap = std::unordered_map; -// `peer_info_labels` is optional because only AttemptLatency2 populates it. -LabelMap IntoLabelMap( - TableResourceLabels const& r, TableDataLabels const& d, - std::set const& filtered_data_labels = {}, - std::optional const& peer_info_labels = std::nullopt); - -bool HasServerTiming(grpc::ClientContext const& client_context); -bool IsConnectivityError(google::cloud::Status const& status, - grpc::ClientContext const& client_context); -std::optional -GetResponseParamsFromTrailingMetadata( - grpc::ClientContext const& client_context); -// Retrieve the peer info from server headers or trailers. Returns nullopt if -// not found or decoding or parsing fails. -std::optional GetPeerInfoFromServerMetadata( - grpc::ClientContext const& client_context); -std::optional GetServerLatencyFromInitialMetadata( - grpc::ClientContext const& client_context); - struct PreCallParams { OperationContext::Clock::time_point attempt_start; bool first_attempt; @@ -104,11 +57,17 @@ struct ElementDeliveryParams { bool first_response; }; +enum class MetricSchema { + kTable, + kClient, +}; + class Metric { public: using LatencyDuration = std::chrono::duration; virtual ~Metric() = 0; + virtual MetricSchema schema() const = 0; virtual void PreCall(opentelemetry::context::Context const&, PreCallParams const&) {} virtual void PostCall(opentelemetry::context::Context const&, @@ -119,212 +78,8 @@ class Metric { ElementRequestParams const&) {} virtual void ElementDelivery(opentelemetry::context::Context const&, ElementDeliveryParams const&) {} - virtual std::unique_ptr clone(TableResourceLabels const&, - TableDataLabels const&) const = 0; -}; - -std::vector> CloneMetrics( - TableResourceLabels const& resource_labels, - TableDataLabels const& data_labels, - std::vector> const& metrics); - -class OperationLatency : public Metric { - public: - explicit OperationLatency( - std::string const& instrumentation_scope, - opentelemetry::nostd::shared_ptr< - opentelemetry::metrics::MeterProvider> const& provider); - void PreCall(opentelemetry::context::Context const&, - PreCallParams const& p) override; - void PostCall(opentelemetry::context::Context const& context, - grpc::ClientContext const& client_context, - PostCallParams const& p) override; - void OnDone(opentelemetry::context::Context const& context, - OnDoneParams const& p) override; - std::unique_ptr clone( - TableResourceLabels const& resource_labels, - TableDataLabels const& data_labels) const override; - - private: - TableResourceLabels resource_labels_; - TableDataLabels data_labels_; - opentelemetry::nostd::shared_ptr> - operation_latencies_; - OperationContext::Clock::time_point operation_start_; -}; - -class AttemptLatency : public Metric { - public: - AttemptLatency(std::string const& instrumentation_scope, - opentelemetry::nostd::shared_ptr< - opentelemetry::metrics::MeterProvider> const& provider); - void PreCall(opentelemetry::context::Context const&, - PreCallParams const& p) override; - void PostCall(opentelemetry::context::Context const& context, - grpc::ClientContext const& client_context, - PostCallParams const& p) override; - std::unique_ptr clone( - TableResourceLabels const& resource_labels, - TableDataLabels const& data_labels) const override; - - private: - TableResourceLabels resource_labels_; - TableDataLabels data_labels_; - opentelemetry::nostd::shared_ptr> - attempt_latencies_; - OperationContext::Clock::time_point attempt_start_; -}; - -// Similar to AttemptLatency and also populates the peer info. -class AttemptLatency2 : public Metric { - public: - AttemptLatency2(std::string const& instrumentation_scope, - opentelemetry::nostd::shared_ptr< - opentelemetry::metrics::MeterProvider> const& provider); - void PreCall(opentelemetry::context::Context const&, - PreCallParams const& p) override; - void PostCall(opentelemetry::context::Context const& context, - grpc::ClientContext const& client_context, - PostCallParams const& p) override; - std::unique_ptr clone( - TableResourceLabels const& resource_labels, - TableDataLabels const& data_labels) const override; - - private: - TableResourceLabels resource_labels_; - TableDataLabels data_labels_; - PeerInfoLabels peer_info_labels_; - opentelemetry::nostd::shared_ptr> - attempt_latencies2_; - OperationContext::Clock::time_point attempt_start_; }; -class RetryCount : public Metric { - public: - RetryCount(std::string const& instrumentation_scope, - opentelemetry::nostd::shared_ptr< - opentelemetry::metrics::MeterProvider> const& provider); - void PreCall(opentelemetry::context::Context const&, - PreCallParams const&) override; - void PostCall(opentelemetry::context::Context const& context, - grpc::ClientContext const& client_context, - PostCallParams const& p) override; - void OnDone(opentelemetry::context::Context const& context, - OnDoneParams const& p) override; - std::unique_ptr clone( - TableResourceLabels const& resource_labels, - TableDataLabels const& data_labels) const override; - - private: - TableResourceLabels resource_labels_; - TableDataLabels data_labels_; - std::uint64_t num_retries_ = 0; - opentelemetry::nostd::shared_ptr< - opentelemetry::metrics::Counter> - retry_count_; -}; - -class FirstResponseLatency : public Metric { - public: - FirstResponseLatency( - std::string const& instrumentation_scope, - opentelemetry::nostd::shared_ptr< - opentelemetry::metrics::MeterProvider> const& provider); - void PreCall(opentelemetry::context::Context const&, - PreCallParams const& p) override; - void PostCall(opentelemetry::context::Context const& context, - grpc::ClientContext const& client_context, - PostCallParams const& p) override; - void ElementDelivery(opentelemetry::context::Context const&, - ElementDeliveryParams const&) override; - void OnDone(opentelemetry::context::Context const& context, - OnDoneParams const& p) override; - - std::unique_ptr clone( - TableResourceLabels const& resource_labels, - TableDataLabels const& data_labels) const override; - - private: - TableResourceLabels resource_labels_; - TableDataLabels data_labels_; - opentelemetry::nostd::shared_ptr> - first_response_latencies_; - OperationContext::Clock::time_point operation_start_; - std::optional first_response_latency_; -}; - -class ServerLatency : public Metric { - public: - ServerLatency(std::string const& instrumentation_scope, - opentelemetry::nostd::shared_ptr< - opentelemetry::metrics::MeterProvider> const& provider); - void PostCall(opentelemetry::context::Context const& context, - grpc::ClientContext const& client_context, - PostCallParams const& p) override; - - std::unique_ptr clone( - TableResourceLabels const& resource_labels, - TableDataLabels const& data_labels) const override; - - private: - TableResourceLabels resource_labels_; - TableDataLabels data_labels_; - opentelemetry::nostd::shared_ptr> - server_latencies_; -}; - -class ApplicationBlockingLatency : public Metric { - public: - ApplicationBlockingLatency( - std::string const& instrumentation_scope, - opentelemetry::nostd::shared_ptr< - opentelemetry::metrics::MeterProvider> const& provider); - void PostCall(opentelemetry::context::Context const& context, - grpc::ClientContext const& client_context, - PostCallParams const& p) override; - void ElementDelivery(opentelemetry::context::Context const&, - ElementDeliveryParams const&) override; - void ElementRequest(opentelemetry::context::Context const&, - ElementRequestParams const&) override; - void OnDone(opentelemetry::context::Context const& context, - OnDoneParams const&) override; - - std::unique_ptr clone( - TableResourceLabels const& resource_labels, - TableDataLabels const& data_labels) const override; - - private: - TableResourceLabels resource_labels_; - TableDataLabels data_labels_; - opentelemetry::nostd::shared_ptr> - application_blocking_latencies_; - OperationContext::Clock::time_point element_delivery_time_; - std::vector pending_latencies_; -}; - -class ConnectivityErrorCount : public Metric { - public: - ConnectivityErrorCount( - std::string const& instrumentation_scope, - opentelemetry::nostd::shared_ptr< - opentelemetry::metrics::MeterProvider> const& provider); - void PostCall(opentelemetry::context::Context const&, - grpc::ClientContext const& client_context, - PostCallParams const& p) override; - void OnDone(opentelemetry::context::Context const& context, - OnDoneParams const&) override; - std::unique_ptr clone( - TableResourceLabels const& resource_labels, - TableDataLabels const& data_labels) const override; - - private: - TableResourceLabels resource_labels_; - TableDataLabels data_labels_; - std::uint64_t num_errors_ = 0; - opentelemetry::nostd::shared_ptr< - opentelemetry::metrics::Counter> - connectivity_error_count_; -}; GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END } // namespace bigtable_internal } // namespace cloud diff --git a/google/cloud/bigtable/internal/metrics_test.cc b/google/cloud/bigtable/internal/metrics_test.cc index 049708ba343da..518fef20d1d9f 100644 --- a/google/cloud/bigtable/internal/metrics_test.cc +++ b/google/cloud/bigtable/internal/metrics_test.cc @@ -12,2497 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Getting vcpkg, cmake, and opentelemetry to actually build with the STL -// enabled is more trouble than it's worth for these unit tests. #if !defined(_WIN32) && !defined(__MACH__) #ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS #include "google/cloud/bigtable/internal/metrics.h" -#include "google/cloud/bigtable/version.h" -#include "google/cloud/testing_util/fake_clock.h" -#include "google/cloud/testing_util/validate_metadata.h" -#include "absl/strings/escaping.h" -#include "google/bigtable/v2/peer_info.pb.h" #include -#include namespace google { namespace cloud { namespace bigtable_internal { GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN -namespace { - -using ::google::cloud::testing_util::FakeSteadyClock; -using ::google::cloud::testing_util::SetServerMetadata; - -using ::testing::A; -using ::testing::Eq; -using ::testing::Pair; -using ::testing::SizeIs; -using ::testing::UnorderedElementsAre; - -using ::opentelemetry::metrics::Counter; -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 -using ::opentelemetry::metrics::Gauge; -#endif -using ::opentelemetry::metrics::Histogram; -using ::opentelemetry::metrics::ObservableInstrument; -using ::opentelemetry::metrics::UpDownCounter; - -template -class MockHistogram : public opentelemetry::metrics::Histogram { - public: -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 - MOCK_METHOD(void, Record, // NOLINT(bugprone-exception-escape) - (T value), (noexcept, override)); - MOCK_METHOD(void, Record, // NOLINT(bugprone-exception-escape) - (T, opentelemetry::common::KeyValueIterable const&), - (noexcept, override)); - -#endif - MOCK_METHOD(void, Record, // NOLINT(bugprone-exception-escape) - (T value, opentelemetry::context::Context const& context), - (noexcept, override)); - MOCK_METHOD(void, Record, // NOLINT(bugprone-exception-escape) - (T value, - opentelemetry::common::KeyValueIterable const& attributes, - opentelemetry::context::Context const& context), - (noexcept, override)); -}; - -template -class MockCounter : public opentelemetry::metrics::Counter { - public: - MOCK_METHOD(void, Add, // NOLINT(bugprone-exception-escape) - (T value), (noexcept, override)); - MOCK_METHOD(void, Add, // NOLINT(bugprone-exception-escape) - (T value, opentelemetry::context::Context const&), - (noexcept, override)); - MOCK_METHOD(void, Add, // NOLINT(bugprone-exception-escape) - (T value, opentelemetry::common::KeyValueIterable const&), - (noexcept, override)); - MOCK_METHOD(void, Add, // NOLINT(bugprone-exception-escape) - (T value, opentelemetry::common::KeyValueIterable const&, - opentelemetry::context::Context const&), - (noexcept, override)); -}; - -class MockMeter : public opentelemetry::metrics::Meter { - public: - MOCK_METHOD(opentelemetry::nostd::unique_ptr>, - CreateUInt64Counter, // NOLINT(bugprone-exception-escape) - (opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view), - (noexcept, override)); - - MOCK_METHOD(opentelemetry::nostd::unique_ptr>, - CreateDoubleCounter, // NOLINT(bugprone-exception-escape) - (opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view), - (noexcept, override)); - - MOCK_METHOD( - opentelemetry::nostd::shared_ptr, - CreateInt64ObservableCounter, // NOLINT(bugprone-exception-escape) - (opentelemetry::nostd::string_view, opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view), - (noexcept, override)); - - MOCK_METHOD( - opentelemetry::nostd::shared_ptr, - CreateDoubleObservableCounter, // NOLINT(bugprone-exception-escape) - (opentelemetry::nostd::string_view, opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view), - (noexcept, override)); - - MOCK_METHOD(opentelemetry::nostd::unique_ptr>, - CreateUInt64Histogram, // NOLINT(bugprone-exception-escape) - (opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view), - (noexcept, override)); - - MOCK_METHOD(opentelemetry::nostd::unique_ptr>, - CreateDoubleHistogram, // NOLINT(bugprone-exception-escape) - (opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view), - (noexcept, override)); - -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 - MOCK_METHOD(opentelemetry::nostd::unique_ptr>, - CreateInt64Gauge, // NOLINT(bugprone-exception-escape) - (opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view), - (noexcept, override)); - - MOCK_METHOD(opentelemetry::nostd::unique_ptr>, - CreateDoubleGauge, // NOLINT(bugprone-exception-escape) - (opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view), - (noexcept, override)); - - MOCK_METHOD(uintptr_t, - RegisterCallback, // NOLINT(bugprone-exception-escape) - (opentelemetry::metrics::MultiObservableCallbackPtr, void*, - opentelemetry::nostd::span< - opentelemetry::metrics::ObservableInstrument*>), - (noexcept, override)); - - MOCK_METHOD(void, - DeregisterCallback, // NOLINT(bugprone-exception-escape) - (uintptr_t), (noexcept, override)); -#endif - - MOCK_METHOD(opentelemetry::nostd::shared_ptr, - CreateInt64ObservableGauge, // NOLINT(bugprone-exception-escape) - (opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view), - (noexcept, override)); - - MOCK_METHOD(opentelemetry::nostd::shared_ptr, - CreateDoubleObservableGauge, // NOLINT(bugprone-exception-escape) - (opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view), - (noexcept, override)); - - MOCK_METHOD(opentelemetry::nostd::unique_ptr>, - CreateInt64UpDownCounter, // NOLINT(bugprone-exception-escape) - (opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view), - (noexcept, override)); - - MOCK_METHOD(opentelemetry::nostd::unique_ptr>, - CreateDoubleUpDownCounter, // NOLINT(bugprone-exception-escape) - (opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view), - (noexcept, override)); - - MOCK_METHOD( - opentelemetry::nostd::shared_ptr, - CreateInt64ObservableUpDownCounter, // NOLINT(bugprone-exception-escape) - (opentelemetry::nostd::string_view, opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view), - (noexcept, override)); - - MOCK_METHOD( - opentelemetry::nostd::shared_ptr, - CreateDoubleObservableUpDownCounter, // NOLINT(bugprone-exception-escape) - (opentelemetry::nostd::string_view, opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view), - (noexcept, override)); -}; - -class MockMeterProvider : public opentelemetry::metrics::MeterProvider { - public: -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 - MOCK_METHOD(opentelemetry::nostd::shared_ptr, - GetMeter, // NOLINT(bugprone-exception-escape) - (opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view, - opentelemetry::common::KeyValueIterable const*), - (noexcept, override)); - - MOCK_METHOD(void, RemoveMeter, // NOLINT(bugprone-exception-escape) - (opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view), - (noexcept, override)); - -#else - MOCK_METHOD(opentelemetry::nostd::shared_ptr, - GetMeter, // NOLINT(bugprone-exception-escape) - (opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view), - (noexcept, override)); -#endif -}; - -TEST(LabelMap, IntoLabelMap) { - TableResourceLabels r{"my-project", "my-instance", "my-table", "my-cluster", - "my-zone"}; - TableDataLabels d{"my-method", "my-streaming", "my-client-name", - "my-client-uid", "my-app-profile", "my-status"}; - auto label_map = IntoLabelMap(r, d); - EXPECT_THAT( - label_map, - UnorderedElementsAre( - Pair("project_id", "my-project"), Pair("instance", "my-instance"), - Pair("table", "my-table"), Pair("cluster", "my-cluster"), - Pair("zone", "my-zone"), Pair("method", "my-method"), - Pair("streaming", "my-streaming"), - Pair("client_name", "my-client-name"), - Pair("client_uid", "my-client-uid"), - Pair("app_profile", "my-app-profile"), Pair("status", "my-status"))); -} - -TEST(LabelMap, IntoLabelMapWithDefaults) { - TableResourceLabels r{"my-project", "my-instance", "my-table", "", ""}; - TableDataLabels d{"my-method", "my-streaming", "my-client-name", - "my-client-uid", "my-app-profile", "my-status"}; - auto label_map = IntoLabelMap(r, d); - EXPECT_THAT( - label_map, - UnorderedElementsAre( - Pair("project_id", "my-project"), Pair("instance", "my-instance"), - Pair("table", "my-table"), Pair("cluster", ""), - Pair("zone", "global"), Pair("method", "my-method"), - Pair("streaming", "my-streaming"), - Pair("client_name", "my-client-name"), - Pair("client_uid", "my-client-uid"), - Pair("app_profile", "my-app-profile"), Pair("status", "my-status"))); -} - -TEST(GetResponseParamsFromMetadata, NonEmptyHeader) { - google::bigtable::v2::ResponseParams expected_response_params; - expected_response_params.set_cluster_id("my-cluster"); - expected_response_params.set_zone_id("my-zone"); - grpc::ClientContext client_context; - RpcMetadata server_metadata; - server_metadata.trailers.emplace( - "x-goog-ext-425905942-bin", expected_response_params.SerializeAsString()); - SetServerMetadata(client_context, server_metadata); - - auto result = GetResponseParamsFromTrailingMetadata(client_context); - ASSERT_TRUE(result); - EXPECT_THAT(result->cluster_id(), Eq("my-cluster")); - EXPECT_THAT(result->zone_id(), Eq("my-zone")); -} - -TEST(GetResponseParamsFromMetadata, EmptyHeader) { - grpc::ClientContext client_context; - RpcMetadata server_metadata; - SetServerMetadata(client_context, server_metadata); - - auto result = GetResponseParamsFromTrailingMetadata(client_context); - EXPECT_FALSE(result); -} - -struct CreateServerMetadataOptions { - bool include_peer_info = true; - bool include_response_params = true; -}; - -// Helper function to create server metadata by populating the initial and -// trailers based on the options. -void CreateServerMetadata(grpc::ClientContext& client_context, - CreateServerMetadataOptions const& options = - CreateServerMetadataOptions{}) { - RpcMetadata server_metadata; - if (options.include_peer_info) { - google::bigtable::v2::PeerInfo peer_info; - peer_info.set_transport_type( - google::bigtable::v2::PeerInfo::TRANSPORT_TYPE_DIRECT_ACCESS); - peer_info.set_application_frontend_region("my-region"); - peer_info.set_application_frontend_subzone("my-subzone"); - server_metadata.headers.emplace( - "bigtable-peer-info", - absl::WebSafeBase64Escape(peer_info.SerializeAsString())); - } - if (options.include_response_params) { - google::bigtable::v2::ResponseParams expected_response_params; - expected_response_params.set_cluster_id("my-cluster"); - expected_response_params.set_zone_id("my-zone"); - server_metadata.trailers.emplace( - "x-goog-ext-425905942-bin", - expected_response_params.SerializeAsString()); - } - - SetServerMetadata(client_context, server_metadata); -} - -TEST(GetPeerInfoFromMetadata, NonEmptyHeaderInitial) { - grpc::ClientContext client_context; - CreateServerMetadata(client_context, - CreateServerMetadataOptions{true, false}); - - auto result = GetPeerInfoFromServerMetadata(client_context); - ASSERT_TRUE(result); - EXPECT_THAT(result->transport_type(), - Eq(google::bigtable::v2::PeerInfo::TRANSPORT_TYPE_DIRECT_ACCESS)); - EXPECT_THAT(result->application_frontend_region(), Eq("my-region")); - EXPECT_THAT(result->application_frontend_subzone(), Eq("my-subzone")); -} - -TEST(GetPeerInfoFromMetadata, NonEmptyHeaderTrailers) { - grpc::ClientContext client_context; - google::bigtable::v2::PeerInfo peer_info; - peer_info.set_transport_type( - google::bigtable::v2::PeerInfo::TRANSPORT_TYPE_DIRECT_ACCESS); - peer_info.set_application_frontend_region("my-region"); - peer_info.set_application_frontend_subzone("my-subzone"); - RpcMetadata server_metadata; - // Set the trailers instead of headers to test that the function handles - // both. - server_metadata.trailers.emplace( - "bigtable-peer-info", - absl::WebSafeBase64Escape(peer_info.SerializeAsString())); - SetServerMetadata(client_context, server_metadata); - - auto result = GetPeerInfoFromServerMetadata(client_context); - ASSERT_TRUE(result); - EXPECT_THAT(result->transport_type(), - Eq(google::bigtable::v2::PeerInfo::TRANSPORT_TYPE_DIRECT_ACCESS)); - EXPECT_THAT(result->application_frontend_region(), Eq("my-region")); - EXPECT_THAT(result->application_frontend_subzone(), Eq("my-subzone")); -} - -TEST(GetPeerInfoFromMetadata, EmptyHeader) { - grpc::ClientContext client_context; - // The server metadata is empty when both options are false. - CreateServerMetadata(client_context, - CreateServerMetadataOptions{false, false}); - - EXPECT_THAT(GetPeerInfoFromServerMetadata(client_context), Eq(std::nullopt)); -} - -TEST(GetPeerInfoFromMetadata, EmptyStringInitial) { - grpc::ClientContext client_context; - RpcMetadata server_metadata; - server_metadata.headers.emplace("bigtable-peer-info", ""); - SetServerMetadata(client_context, server_metadata); - - auto result = GetPeerInfoFromServerMetadata(client_context); - ASSERT_TRUE(result); - EXPECT_THAT(result->transport_type(), - Eq(google::bigtable::v2::PeerInfo::TRANSPORT_TYPE_UNKNOWN)); - EXPECT_THAT(result->application_frontend_region(), Eq("")); - EXPECT_THAT(result->application_frontend_subzone(), Eq("")); -} - -TEST(GetPeerInfoFromMetadata, EmptyStringTrailers) { - grpc::ClientContext client_context; - RpcMetadata server_metadata; - server_metadata.trailers.emplace("bigtable-peer-info", ""); - SetServerMetadata(client_context, server_metadata); - - auto result = GetPeerInfoFromServerMetadata(client_context); - ASSERT_TRUE(result); - EXPECT_THAT(result->transport_type(), - Eq(google::bigtable::v2::PeerInfo::TRANSPORT_TYPE_UNKNOWN)); - EXPECT_THAT(result->application_frontend_region(), Eq("")); - EXPECT_THAT(result->application_frontend_subzone(), Eq("")); -} - -TEST(GetPeerInfoFromMetadata, InvalidBase64Initial) { - { - grpc::ClientContext client_context; - RpcMetadata server_metadata; - server_metadata.headers.emplace("bigtable-peer-info", "invalid-base64!"); - SetServerMetadata(client_context, server_metadata); - - EXPECT_THAT(GetPeerInfoFromServerMetadata(client_context), - Eq(std::nullopt)); - } - { - grpc::ClientContext client_context; - RpcMetadata server_metadata; - server_metadata.headers.emplace("bigtable-peer-info", - "invalid+websafe/base64"); - SetServerMetadata(client_context, server_metadata); - - EXPECT_THAT(GetPeerInfoFromServerMetadata(client_context), - Eq(std::nullopt)); - } -} - -TEST(GetPeerInfoFromMetadata, InvalidBase64Trailers) { - { - grpc::ClientContext client_context; - RpcMetadata server_metadata; - server_metadata.trailers.emplace("bigtable-peer-info", "invalid-base64!"); - SetServerMetadata(client_context, server_metadata); - - EXPECT_THAT(GetPeerInfoFromServerMetadata(client_context), - Eq(std::nullopt)); - } - { - grpc::ClientContext client_context; - RpcMetadata server_metadata; - server_metadata.trailers.emplace("bigtable-peer-info", - "invalid+websafe/base64"); - SetServerMetadata(client_context, server_metadata); - - EXPECT_THAT(GetPeerInfoFromServerMetadata(client_context), - Eq(std::nullopt)); - } -} - -std::unordered_map MakeAttributesMap( - opentelemetry::common::KeyValueIterable const& attributes) { - std::unordered_map m; - attributes.ForEachKeyValue([&](opentelemetry::nostd::string_view k, - opentelemetry::common::AttributeValue v) { - if (opentelemetry::nostd::holds_alternative< - opentelemetry::nostd::string_view>(v)) { - m.emplace( - std::string{k}, - opentelemetry::nostd::get(v)); - } - return true; - }); - return m; -} - -void SetClusterZone(grpc::ClientContext& client_context) { - CreateServerMetadata(client_context, - CreateServerMetadataOptions{false, true}); -} - -TEST(OperationLatencyTest, FirstAttemptSuccess) { - auto mock_histogram = std::make_unique>(); - EXPECT_CALL( - *mock_histogram, - Record(A(), A(), - A())) - .WillOnce([](double value, - opentelemetry::common::KeyValueIterable const& attributes, - opentelemetry::context::Context const&) { - EXPECT_THAT(value, Eq(10.0)); - EXPECT_THAT( - MakeAttributesMap(attributes), - UnorderedElementsAre( - Pair("project_id", "my-project-id"), - Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), - Pair("table", "my-table"), Pair("zone", "my-zone"), - Pair("method", "my-method"), Pair("streaming", "my-streaming"), - Pair("status", "OK"), Pair("client_name", "my-client-name"), - Pair("client_uid", "my-client-uid"), - Pair("app_profile", "my-app-profile"))); - }); - - opentelemetry::nostd::shared_ptr mock_meter = - std::make_shared(); - EXPECT_CALL(*mock_meter, CreateDoubleHistogram) - .WillOnce([mock = std::move(mock_histogram)]( - opentelemetry::nostd::string_view name, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view) mutable { - EXPECT_THAT(name, Eq("operation_latencies")); - return std::move(mock); - }); - - opentelemetry::nostd::shared_ptr mock_provider = - std::make_shared(); - EXPECT_CALL(*mock_provider, GetMeter) -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view, - opentelemetry::common::KeyValueIterable const*) mutable { -#else - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view) mutable { -#endif - EXPECT_THAT(scope, Eq("my-instrument-scope")); - EXPECT_THAT(scope_version, Eq("v1")); - return mock_meter; - }); - - OperationLatency operation_latency("my-instrument-scope", mock_provider); - TableResourceLabels resource_labels{"my-project-id", "my-instance", - "my-table", "", ""}; - TableDataLabels data_labels{"my-method", "my-streaming", - "my-client-name", "my-client-uid", - "my-app-profile", ""}; - auto clone = operation_latency.clone(resource_labels, data_labels); - - grpc::ClientContext client_context; - SetClusterZone(client_context); - - auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); - auto clock = std::make_shared(); - - clock->SetTime(std::chrono::steady_clock::now()); - clone->PreCall(otel_context, {clock->Now(), true}); - clock->AdvanceTime(std::chrono::milliseconds(5)); - clone->PostCall(otel_context, client_context, - {clock->Now(), Status{StatusCode::kOk, "ok"}}); - clock->AdvanceTime(std::chrono::milliseconds(5)); - clone->OnDone(otel_context, {clock->Now(), Status{StatusCode::kOk, "ok"}}); -} - -TEST(OperationLatencyTest, ThirdAttemptSuccess) { - auto mock_histogram = std::make_unique>(); - EXPECT_CALL( - *mock_histogram, - Record(A(), A(), - A())) - .WillOnce([](double value, - opentelemetry::common::KeyValueIterable const& attributes, - opentelemetry::context::Context const&) { - EXPECT_THAT(value, Eq(20.0)); - EXPECT_THAT( - MakeAttributesMap(attributes), - UnorderedElementsAre( - Pair("project_id", "my-project-id"), - Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), - Pair("table", "my-table"), Pair("zone", "my-zone"), - Pair("method", "my-method"), Pair("streaming", "my-streaming"), - Pair("status", "OK"), Pair("client_name", "my-client-name"), - Pair("client_uid", "my-client-uid"), - Pair("app_profile", "my-app-profile"))); - }); - - opentelemetry::nostd::shared_ptr mock_meter = - std::make_shared(); - EXPECT_CALL(*mock_meter, CreateDoubleHistogram) - .WillOnce([mock = std::move(mock_histogram)]( - opentelemetry::nostd::string_view name, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view) mutable { - EXPECT_THAT(name, Eq("operation_latencies")); - return std::move(mock); - }); - - opentelemetry::nostd::shared_ptr mock_provider = - std::make_shared(); - EXPECT_CALL(*mock_provider, GetMeter) -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view, - opentelemetry::common::KeyValueIterable const*) mutable { -#else - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view) mutable { -#endif - EXPECT_THAT(scope, Eq("my-instrument-scope")); - EXPECT_THAT(scope_version, Eq("v1")); - return mock_meter; - }); - - OperationLatency operation_latency("my-instrument-scope", mock_provider); - TableResourceLabels resource_labels{"my-project-id", "my-instance", - "my-table", "", ""}; - TableDataLabels data_labels{"my-method", "my-streaming", - "my-client-name", "my-client-uid", - "my-app-profile", ""}; - auto clone = operation_latency.clone(resource_labels, data_labels); - - grpc::ClientContext client_context; - SetClusterZone(client_context); - - auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); - auto clock = std::make_shared(); - - clock->SetTime(std::chrono::steady_clock::now()); - - clone->PreCall(otel_context, {clock->Now(), true}); - clock->AdvanceTime(std::chrono::milliseconds(5)); - clone->PostCall( - otel_context, client_context, - {clock->Now(), Status{StatusCode::kUnavailable, "unavailable"}}); - - clone->PreCall(otel_context, {clock->Now(), false}); - clock->AdvanceTime(std::chrono::milliseconds(5)); - clone->PostCall( - otel_context, client_context, - {clock->Now(), Status{StatusCode::kUnavailable, "unavailable"}}); - - clone->PreCall(otel_context, {clock->Now(), false}); - clock->AdvanceTime(std::chrono::milliseconds(5)); - clone->PostCall(otel_context, client_context, - {clock->Now(), Status{StatusCode::kOk, "ok"}}); - - clock->AdvanceTime(std::chrono::milliseconds(5)); - clone->OnDone(otel_context, {clock->Now(), Status{StatusCode::kOk, "ok"}}); -} - -TEST(OperationLatencyTest, UsesDefaultClusterAndZone) { - auto mock_histogram = std::make_unique>(); - EXPECT_CALL( - *mock_histogram, - Record(A(), A(), - A())) - .WillOnce([](double value, - opentelemetry::common::KeyValueIterable const& attributes, - opentelemetry::context::Context const&) { - EXPECT_THAT(value, Eq(10.0)); - EXPECT_THAT( - MakeAttributesMap(attributes), - UnorderedElementsAre( - Pair("project_id", "my-project-id"), - Pair("instance", "my-instance"), - Pair("cluster", ""), Pair("table", "my-table"), - Pair("zone", "global"), Pair("method", "my-method"), - Pair("streaming", "my-streaming"), Pair("status", "OK"), - Pair("client_name", "my-client-name"), - Pair("client_uid", "my-client-uid"), - Pair("app_profile", "my-app-profile"))); - }); - - opentelemetry::nostd::shared_ptr mock_meter = - std::make_shared(); - EXPECT_CALL(*mock_meter, CreateDoubleHistogram) - .WillOnce([mock = std::move(mock_histogram)]( - opentelemetry::nostd::string_view name, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view) mutable { - EXPECT_THAT(name, Eq("operation_latencies")); - return std::move(mock); - }); - - opentelemetry::nostd::shared_ptr mock_provider = - std::make_shared(); - EXPECT_CALL(*mock_provider, GetMeter) -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view, - opentelemetry::common::KeyValueIterable const*) mutable { -#else - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view) mutable { -#endif - EXPECT_THAT(scope, Eq("my-instrument-scope")); - EXPECT_THAT(scope_version, Eq("v1")); - return mock_meter; - }); - - OperationLatency operation_latency("my-instrument-scope", mock_provider); - TableResourceLabels resource_labels{"my-project-id", "my-instance", - "my-table", "", ""}; - TableDataLabels data_labels{"my-method", "my-streaming", - "my-client-name", "my-client-uid", - "my-app-profile", ""}; - auto clone = operation_latency.clone(resource_labels, data_labels); - - grpc::ClientContext client_context; - - auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); - auto clock = std::make_shared(); - - clock->SetTime(std::chrono::steady_clock::now()); - clone->PreCall(otel_context, {clock->Now(), true}); - clock->AdvanceTime(std::chrono::milliseconds(5)); - clone->PostCall(otel_context, client_context, - {clock->Now(), Status{StatusCode::kOk, "ok"}}); - clock->AdvanceTime(std::chrono::milliseconds(5)); - clone->OnDone(otel_context, {clock->Now(), Status{StatusCode::kOk, "ok"}}); -} - -TEST(AttemptLatencyTest, NoRetry) { - auto mock_histogram = std::make_unique>(); - EXPECT_CALL( - *mock_histogram, - Record(A(), A(), - A())) - .WillOnce([](double value, - opentelemetry::common::KeyValueIterable const& attributes, - opentelemetry::context::Context const&) { - EXPECT_THAT(value, Eq(1.234)); - EXPECT_THAT( - MakeAttributesMap(attributes), - UnorderedElementsAre( - Pair("project_id", "my-project-id"), - Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), - Pair("table", "my-table"), Pair("zone", "my-zone"), - Pair("method", "my-method"), Pair("streaming", "my-streaming"), - Pair("status", "OK"), Pair("client_name", "my-client-name"), - Pair("client_uid", "my-client-uid"), - Pair("app_profile", "my-app-profile"))); - }); - - opentelemetry::nostd::shared_ptr mock_meter = - std::make_shared(); - EXPECT_CALL(*mock_meter, CreateDoubleHistogram) - .WillOnce([mock = std::move(mock_histogram)]( - opentelemetry::nostd::string_view name, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view) mutable { - EXPECT_THAT(name, Eq("attempt_latencies")); - return std::move(mock); - }); - - opentelemetry::nostd::shared_ptr mock_provider = - std::make_shared(); - EXPECT_CALL(*mock_provider, GetMeter) -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view, - opentelemetry::common::KeyValueIterable const*) mutable { -#else - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view) mutable { -#endif - EXPECT_THAT(scope, Eq("my-instrument-scope")); - EXPECT_THAT(scope_version, Eq("v1")); - return mock_meter; - }); - - AttemptLatency attempt_latency("my-instrument-scope", mock_provider); - TableResourceLabels resource_labels{"my-project-id", "my-instance", - "my-table", "", ""}; - TableDataLabels data_labels{"my-method", "my-streaming", - "my-client-name", "my-client-uid", - "my-app-profile", ""}; - auto clone = attempt_latency.clone(resource_labels, data_labels); - - grpc::ClientContext client_context; - SetClusterZone(client_context); - - auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); - auto clock = std::make_shared(); - - clock->SetTime(std::chrono::steady_clock::now()); - clone->PreCall(otel_context, {clock->Now(), true}); - clock->AdvanceTime(std::chrono::microseconds(1234)); - clone->PostCall(otel_context, client_context, - {clock->Now(), Status{StatusCode::kOk, "ok"}}); - clock->AdvanceTime(std::chrono::milliseconds(100)); - clone->OnDone(otel_context, {clock->Now(), Status{StatusCode::kOk, "ok"}}); -} - -TEST(AttemptLatencyTest, ThreeAttempts) { - auto mock_histogram = std::make_unique>(); - EXPECT_CALL( - *mock_histogram, - Record(A(), A(), - A())) - .WillOnce([](double value, - opentelemetry::common::KeyValueIterable const& attributes, - opentelemetry::context::Context const&) { - EXPECT_THAT(value, Eq(1.0)); - EXPECT_THAT( - MakeAttributesMap(attributes), - UnorderedElementsAre( - Pair("project_id", "my-project-id"), - Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), - Pair("table", "my-table"), Pair("zone", "my-zone"), - Pair("method", "my-method"), Pair("streaming", "my-streaming"), - Pair("status", "OK"), Pair("client_name", "my-client-name"), - Pair("client_uid", "my-client-uid"), - Pair("app_profile", "my-app-profile"))); - }) - .WillOnce([](double value, - opentelemetry::common::KeyValueIterable const& attributes, - opentelemetry::context::Context const&) { - EXPECT_THAT(value, Eq(2.0)); - EXPECT_THAT( - MakeAttributesMap(attributes), - UnorderedElementsAre( - Pair("project_id", "my-project-id"), - Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), - Pair("table", "my-table"), Pair("zone", "my-zone"), - Pair("method", "my-method"), Pair("streaming", "my-streaming"), - Pair("status", "OK"), Pair("client_name", "my-client-name"), - Pair("client_uid", "my-client-uid"), - Pair("app_profile", "my-app-profile"))); - }) - .WillOnce([](double value, - opentelemetry::common::KeyValueIterable const& attributes, - opentelemetry::context::Context const&) { - EXPECT_THAT(value, Eq(3.0)); - EXPECT_THAT( - MakeAttributesMap(attributes), - UnorderedElementsAre( - Pair("project_id", "my-project-id"), - Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), - Pair("table", "my-table"), Pair("zone", "my-zone"), - Pair("method", "my-method"), Pair("streaming", "my-streaming"), - Pair("status", "OK"), Pair("client_name", "my-client-name"), - Pair("client_uid", "my-client-uid"), - Pair("app_profile", "my-app-profile"))); - }); - - opentelemetry::nostd::shared_ptr mock_meter = - std::make_shared(); - EXPECT_CALL(*mock_meter, CreateDoubleHistogram) - .WillOnce([mock = std::move(mock_histogram)]( - opentelemetry::nostd::string_view name, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view) mutable { - EXPECT_THAT(name, Eq("attempt_latencies")); - return std::move(mock); - }); - - opentelemetry::nostd::shared_ptr mock_provider = - std::make_shared(); - EXPECT_CALL(*mock_provider, GetMeter) -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view, - opentelemetry::common::KeyValueIterable const*) mutable { -#else - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view) mutable { -#endif - EXPECT_THAT(scope, Eq("my-instrument-scope")); - EXPECT_THAT(scope_version, Eq("v1")); - return mock_meter; - }); - - AttemptLatency attempt_latency("my-instrument-scope", mock_provider); - TableResourceLabels resource_labels{"my-project-id", "my-instance", - "my-table", "", ""}; - TableDataLabels data_labels{"my-method", "my-streaming", - "my-client-name", "my-client-uid", - "my-app-profile", ""}; - auto clone = attempt_latency.clone(resource_labels, data_labels); - - grpc::ClientContext client_context; - SetClusterZone(client_context); - - auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); - auto clock = std::make_shared(); - - clock->SetTime(std::chrono::steady_clock::now()); - clone->PreCall(otel_context, {clock->Now(), true}); - clock->AdvanceTime(std::chrono::milliseconds(1)); - clone->PostCall(otel_context, client_context, - {clock->Now(), Status{StatusCode::kOk, "ok"}}); - clock->AdvanceTime(std::chrono::milliseconds(100)); - clone->PreCall(otel_context, {clock->Now(), false}); - clock->AdvanceTime(std::chrono::milliseconds(2)); - clone->PostCall(otel_context, client_context, - {clock->Now(), Status{StatusCode::kOk, "ok"}}); - clone->PreCall(otel_context, {clock->Now(), false}); - clock->AdvanceTime(std::chrono::milliseconds(3)); - clone->PostCall(otel_context, client_context, - {clock->Now(), Status{StatusCode::kOk, "ok"}}); - clock->AdvanceTime(std::chrono::milliseconds(100)); - clone->OnDone(otel_context, {clock->Now(), Status{StatusCode::kOk, "ok"}}); -} - -TEST(AttemptLatencyTest, UsesDefaultClusterAndZone) { - auto mock_histogram = std::make_unique>(); - EXPECT_CALL( - *mock_histogram, - Record(A(), A(), - A())) - .WillOnce([](double value, - opentelemetry::common::KeyValueIterable const& attributes, - opentelemetry::context::Context const&) { - EXPECT_THAT(value, Eq(1.234)); - EXPECT_THAT( - MakeAttributesMap(attributes), - UnorderedElementsAre( - Pair("project_id", "my-project-id"), - Pair("instance", "my-instance"), - Pair("cluster", ""), Pair("table", "my-table"), - Pair("zone", "global"), Pair("method", "my-method"), - Pair("streaming", "my-streaming"), Pair("status", "OK"), - Pair("client_name", "my-client-name"), - Pair("client_uid", "my-client-uid"), - Pair("app_profile", "my-app-profile"))); - }); - - opentelemetry::nostd::shared_ptr mock_meter = - std::make_shared(); - EXPECT_CALL(*mock_meter, CreateDoubleHistogram) - .WillOnce([mock = std::move(mock_histogram)]( - opentelemetry::nostd::string_view name, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view) mutable { - EXPECT_THAT(name, Eq("attempt_latencies")); - return std::move(mock); - }); - - opentelemetry::nostd::shared_ptr mock_provider = - std::make_shared(); - EXPECT_CALL(*mock_provider, GetMeter) -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view, - opentelemetry::common::KeyValueIterable const*) mutable { -#else - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view) mutable { -#endif - EXPECT_THAT(scope, Eq("my-instrument-scope")); - EXPECT_THAT(scope_version, Eq("v1")); - return mock_meter; - }); - - AttemptLatency attempt_latency("my-instrument-scope", mock_provider); - TableResourceLabels resource_labels{"my-project-id", "my-instance", - "my-table", "", ""}; - TableDataLabels data_labels{"my-method", "my-streaming", - "my-client-name", "my-client-uid", - "my-app-profile", ""}; - auto clone = attempt_latency.clone(resource_labels, data_labels); - - grpc::ClientContext client_context; - - auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); - auto clock = std::make_shared(); - - clock->SetTime(std::chrono::steady_clock::now()); - clone->PreCall(otel_context, {clock->Now(), true}); - clock->AdvanceTime(std::chrono::microseconds(1234)); - clone->PostCall(otel_context, client_context, - {clock->Now(), Status{StatusCode::kOk, "ok"}}); - clock->AdvanceTime(std::chrono::milliseconds(5)); - clone->OnDone(otel_context, {clock->Now(), Status{StatusCode::kOk, "ok"}}); -} - -TEST(AttemptLatency2Test, NoRetry) { - auto mock_histogram = std::make_unique>(); - EXPECT_CALL( - *mock_histogram, - Record(A(), A(), - A())) - .WillOnce([](double value, - opentelemetry::common::KeyValueIterable const& attributes, - opentelemetry::context::Context const&) { - EXPECT_THAT(value, Eq(1.234)); - EXPECT_THAT( - MakeAttributesMap(attributes), - UnorderedElementsAre( - Pair("project_id", "my-project-id"), - Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), - Pair("table", "my-table"), Pair("zone", "my-zone"), - Pair("method", "my-method"), Pair("streaming", "my-streaming"), - Pair("status", "OK"), Pair("client_name", "my-client-name"), - Pair("client_uid", "my-client-uid"), - Pair("app_profile", "my-app-profile"), - Pair("transport_type", "transport_type_direct_access"), - Pair("transport_region", "my-region"), - Pair("transport_subzone", "my-subzone"))); - }); - - opentelemetry::nostd::shared_ptr mock_meter = - std::make_shared(); - EXPECT_CALL(*mock_meter, CreateDoubleHistogram) - .WillOnce([mock = std::move(mock_histogram)]( - opentelemetry::nostd::string_view name, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view) mutable { - EXPECT_THAT(name, Eq("attempt_latencies2")); - return std::move(mock); - }); - - opentelemetry::nostd::shared_ptr mock_provider = - std::make_shared(); - EXPECT_CALL(*mock_provider, GetMeter) -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view, - opentelemetry::common::KeyValueIterable const*) mutable { -#else - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view) mutable { -#endif - EXPECT_THAT(scope, Eq("my-instrument-scope")); - EXPECT_THAT(scope_version, Eq("v1")); - return mock_meter; - }); - - AttemptLatency2 attempt_latency("my-instrument-scope", mock_provider); - TableResourceLabels resource_labels{"my-project-id", "my-instance", - "my-table", "", ""}; - TableDataLabels data_labels{"my-method", "my-streaming", - "my-client-name", "my-client-uid", - "my-app-profile", ""}; - auto clone = attempt_latency.clone(resource_labels, data_labels); - - grpc::ClientContext client_context; - CreateServerMetadata(client_context); - - auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); - auto clock = std::make_shared(); - - clock->SetTime(std::chrono::steady_clock::now()); - clone->PreCall(otel_context, {clock->Now(), true}); - clock->AdvanceTime(std::chrono::microseconds(1234)); - clone->PostCall(otel_context, client_context, - {clock->Now(), Status{StatusCode::kOk, "ok"}}); -} - -TEST(AttemptLatency2Test, ThreeAttempts) { - auto mock_histogram = std::make_unique>(); - EXPECT_CALL( - *mock_histogram, - Record(A(), A(), - A())) - .WillOnce([](double value, - opentelemetry::common::KeyValueIterable const& attributes, - opentelemetry::context::Context const&) { - EXPECT_THAT(value, Eq(1.0)); - EXPECT_THAT( - MakeAttributesMap(attributes), - UnorderedElementsAre( - Pair("project_id", "my-project-id"), - Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), - Pair("table", "my-table"), Pair("zone", "my-zone"), - Pair("method", "my-method"), Pair("streaming", "my-streaming"), - Pair("status", "OK"), Pair("client_name", "my-client-name"), - Pair("client_uid", "my-client-uid"), - Pair("app_profile", "my-app-profile"), - Pair("transport_type", "transport_type_direct_access"), - Pair("transport_region", "my-region"), - Pair("transport_subzone", "my-subzone"))); - }) - .WillOnce([](double value, - opentelemetry::common::KeyValueIterable const& attributes, - opentelemetry::context::Context const&) { - EXPECT_THAT(value, Eq(2.0)); - EXPECT_THAT( - MakeAttributesMap(attributes), - UnorderedElementsAre( - Pair("project_id", "my-project-id"), - Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), - Pair("table", "my-table"), Pair("zone", "my-zone"), - Pair("method", "my-method"), Pair("streaming", "my-streaming"), - Pair("status", "OK"), Pair("client_name", "my-client-name"), - Pair("client_uid", "my-client-uid"), - Pair("app_profile", "my-app-profile"), - Pair("transport_type", "transport_type_direct_access"), - Pair("transport_region", "my-region"), - Pair("transport_subzone", "my-subzone"))); - }) - .WillOnce([](double value, - opentelemetry::common::KeyValueIterable const& attributes, - opentelemetry::context::Context const&) { - EXPECT_THAT(value, Eq(3.0)); - EXPECT_THAT( - MakeAttributesMap(attributes), - UnorderedElementsAre( - Pair("project_id", "my-project-id"), - Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), - Pair("table", "my-table"), Pair("zone", "my-zone"), - Pair("method", "my-method"), Pair("streaming", "my-streaming"), - Pair("status", "OK"), Pair("client_name", "my-client-name"), - Pair("client_uid", "my-client-uid"), - Pair("app_profile", "my-app-profile"), - Pair("transport_type", "transport_type_direct_access"), - Pair("transport_region", "my-region"), - Pair("transport_subzone", "my-subzone"))); - }); - - opentelemetry::nostd::shared_ptr mock_meter = - std::make_shared(); - EXPECT_CALL(*mock_meter, CreateDoubleHistogram) - .WillOnce([mock = std::move(mock_histogram)]( - opentelemetry::nostd::string_view name, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view) mutable { - EXPECT_THAT(name, Eq("attempt_latencies2")); - return std::move(mock); - }); - - opentelemetry::nostd::shared_ptr mock_provider = - std::make_shared(); - EXPECT_CALL(*mock_provider, GetMeter) -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view, - opentelemetry::common::KeyValueIterable const*) mutable { -#else - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view) mutable { -#endif - EXPECT_THAT(scope, Eq("my-instrument-scope")); - EXPECT_THAT(scope_version, Eq("v1")); - return mock_meter; - }); - - AttemptLatency2 attempt_latency("my-instrument-scope", mock_provider); - TableResourceLabels resource_labels{"my-project-id", "my-instance", - "my-table", "", ""}; - TableDataLabels data_labels{"my-method", "my-streaming", - "my-client-name", "my-client-uid", - "my-app-profile", ""}; - auto clone = attempt_latency.clone(resource_labels, data_labels); - - grpc::ClientContext client_context; - CreateServerMetadata(client_context); - - auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); - auto clock = std::make_shared(); - - clock->SetTime(std::chrono::steady_clock::now()); - clone->PreCall(otel_context, {clock->Now(), true}); - clock->AdvanceTime(std::chrono::milliseconds(1)); - clone->PostCall(otel_context, client_context, - {clock->Now(), Status{StatusCode::kOk, "ok"}}); - clock->AdvanceTime(std::chrono::milliseconds(100)); - clone->PreCall(otel_context, {clock->Now(), false}); - clock->AdvanceTime(std::chrono::milliseconds(2)); - clone->PostCall(otel_context, client_context, - {clock->Now(), Status{StatusCode::kOk, "ok"}}); - clone->PreCall(otel_context, {clock->Now(), false}); - clock->AdvanceTime(std::chrono::milliseconds(3)); - clone->PostCall(otel_context, client_context, - {clock->Now(), Status{StatusCode::kOk, "ok"}}); -} - -TEST(AttemptLatency2Test, UsesDefaultClusterAndZone) { - auto mock_histogram = std::make_unique>(); - EXPECT_CALL( - *mock_histogram, - Record(A(), A(), - A())) - .WillOnce([](double value, - opentelemetry::common::KeyValueIterable const& attributes, - opentelemetry::context::Context const&) { - EXPECT_THAT(value, Eq(1.234)); - EXPECT_THAT( - MakeAttributesMap(attributes), - UnorderedElementsAre( - Pair("project_id", "my-project-id"), - Pair("instance", "my-instance"), - Pair("cluster", ""), Pair("table", "my-table"), - Pair("zone", "global"), Pair("method", "my-method"), - Pair("streaming", "my-streaming"), Pair("status", "OK"), - Pair("client_name", "my-client-name"), - Pair("client_uid", "my-client-uid"), - Pair("app_profile", "my-app-profile"), - Pair("transport_type", "transport_type_direct_access"), - Pair("transport_region", "my-region"), - Pair("transport_subzone", "my-subzone"))); - }); - - opentelemetry::nostd::shared_ptr mock_meter = - std::make_shared(); - EXPECT_CALL(*mock_meter, CreateDoubleHistogram) - .WillOnce([mock = std::move(mock_histogram)]( - opentelemetry::nostd::string_view name, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view) mutable { - EXPECT_THAT(name, Eq("attempt_latencies2")); - return std::move(mock); - }); - - opentelemetry::nostd::shared_ptr mock_provider = - std::make_shared(); - EXPECT_CALL(*mock_provider, GetMeter) -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view, - opentelemetry::common::KeyValueIterable const*) mutable { -#else - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view) mutable { -#endif - EXPECT_THAT(scope, Eq("my-instrument-scope")); - EXPECT_THAT(scope_version, Eq("v1")); - return mock_meter; - }); - - AttemptLatency2 attempt_latency("my-instrument-scope", mock_provider); - TableResourceLabels resource_labels{"my-project-id", "my-instance", - "my-table", "", ""}; - TableDataLabels data_labels{"my-method", "my-streaming", - "my-client-name", "my-client-uid", - "my-app-profile", ""}; - auto clone = attempt_latency.clone(resource_labels, data_labels); - - grpc::ClientContext client_context; - CreateServerMetadata(client_context, - CreateServerMetadataOptions{true, false}); - - auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); - auto clock = std::make_shared(); - - clock->SetTime(std::chrono::steady_clock::now()); - clone->PreCall(otel_context, {clock->Now(), true}); - clock->AdvanceTime(std::chrono::microseconds(1234)); - clone->PostCall(otel_context, client_context, - {clock->Now(), Status{StatusCode::kOk, "ok"}}); -} - -TEST(RetryCountTest, NoRetry) { - auto mock_counter = std::make_unique>(); - EXPECT_CALL(*mock_counter, - Add(A(), - A(), - A())) - .WillOnce([](std::uint64_t value, - opentelemetry::common::KeyValueIterable const& attributes, - opentelemetry::context::Context const&) { - EXPECT_THAT(value, Eq(0)); - EXPECT_THAT( - MakeAttributesMap(attributes), - UnorderedElementsAre( - Pair("project_id", "my-project-id"), - Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), - Pair("table", "my-table"), Pair("zone", "my-zone"), - Pair("method", "my-method"), Pair("status", "OK"), - Pair("client_name", "my-client-name"), - Pair("client_uid", "my-client-uid"), - Pair("app_profile", "my-app-profile"))); - }); - - opentelemetry::nostd::shared_ptr mock_meter = - std::make_shared(); - EXPECT_CALL(*mock_meter, CreateUInt64Counter) - .WillOnce([mock = std::move(mock_counter)]( - opentelemetry::nostd::string_view name, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view) mutable { - EXPECT_THAT(name, Eq("retry_count")); - return std::move(mock); - }); - - opentelemetry::nostd::shared_ptr mock_provider = - std::make_shared(); - EXPECT_CALL(*mock_provider, GetMeter) -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view, - opentelemetry::common::KeyValueIterable const*) mutable { -#else - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view) mutable { -#endif - EXPECT_THAT(scope, Eq("my-instrument-scope")); - EXPECT_THAT(scope_version, Eq("v1")); - return mock_meter; - }); - - RetryCount retry_count("my-instrument-scope", mock_provider); - TableResourceLabels resource_labels{"my-project-id", "my-instance", - "my-table", "", ""}; - TableDataLabels data_labels{"my-method", "my-streaming", - "my-client-name", "my-client-uid", - "my-app-profile", ""}; - auto clone = retry_count.clone(resource_labels, data_labels); - - grpc::ClientContext client_context; - SetClusterZone(client_context); - - auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); - auto clock = std::make_shared(); - - clock->SetTime(std::chrono::steady_clock::now()); - clone->PreCall(otel_context, {clock->Now(), true}); - clock->AdvanceTime(std::chrono::microseconds(1234)); - clone->PostCall(otel_context, client_context, - {clock->Now(), Status{StatusCode::kOk, "ok"}}); - clock->AdvanceTime(std::chrono::milliseconds(100)); - clone->OnDone(otel_context, {clock->Now(), Status{StatusCode::kOk, "ok"}}); -} - -TEST(RetryCountTest, ThreeAttempts) { - auto mock_counter = std::make_unique>(); - EXPECT_CALL(*mock_counter, - Add(A(), - A(), - A())) - .WillOnce([](std::uint64_t value, - opentelemetry::common::KeyValueIterable const& attributes, - opentelemetry::context::Context const&) { - EXPECT_THAT(value, Eq(2)); - EXPECT_THAT( - MakeAttributesMap(attributes), - UnorderedElementsAre( - Pair("project_id", "my-project-id"), - Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), - Pair("table", "my-table"), Pair("zone", "my-zone"), - Pair("method", "my-method"), Pair("status", "OK"), - Pair("client_name", "my-client-name"), - Pair("client_uid", "my-client-uid"), - Pair("app_profile", "my-app-profile"))); - }); - - opentelemetry::nostd::shared_ptr mock_meter = - std::make_shared(); - EXPECT_CALL(*mock_meter, CreateUInt64Counter) - .WillOnce([mock = std::move(mock_counter)]( - opentelemetry::nostd::string_view name, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view) mutable { - EXPECT_THAT(name, Eq("retry_count")); - return std::move(mock); - }); - - opentelemetry::nostd::shared_ptr mock_provider = - std::make_shared(); - EXPECT_CALL(*mock_provider, GetMeter) -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view, - opentelemetry::common::KeyValueIterable const*) mutable { -#else - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view) mutable { -#endif - EXPECT_THAT(scope, Eq("my-instrument-scope")); - EXPECT_THAT(scope_version, Eq("v1")); - return mock_meter; - }); - - RetryCount retry_count("my-instrument-scope", mock_provider); - TableResourceLabels resource_labels{"my-project-id", "my-instance", - "my-table", "", ""}; - TableDataLabels data_labels{"my-method", "my-streaming", - "my-client-name", "my-client-uid", - "my-app-profile", ""}; - auto clone = retry_count.clone(resource_labels, data_labels); - - grpc::ClientContext client_context; - SetClusterZone(client_context); - - auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); - auto clock = std::make_shared(); - - clock->SetTime(std::chrono::steady_clock::now()); - clone->PreCall(otel_context, {clock->Now(), true}); - clock->AdvanceTime(std::chrono::microseconds(1)); - clone->PostCall(otel_context, client_context, - {clock->Now(), Status{StatusCode::kOk, "ok"}}); - clock->AdvanceTime(std::chrono::milliseconds(100)); - clone->PreCall(otel_context, {clock->Now(), false}); - clock->AdvanceTime(std::chrono::microseconds(2)); - clone->PostCall(otel_context, client_context, - {clock->Now(), Status{StatusCode::kOk, "ok"}}); - clock->AdvanceTime(std::chrono::milliseconds(100)); - clone->PreCall(otel_context, {clock->Now(), false}); - clock->AdvanceTime(std::chrono::microseconds(3)); - clone->PostCall(otel_context, client_context, - {clock->Now(), Status{StatusCode::kOk, "ok"}}); - clock->AdvanceTime(std::chrono::milliseconds(100)); - clone->OnDone(otel_context, {clock->Now(), Status{StatusCode::kOk, "ok"}}); -} - -TEST(RetryCountTest, UsesDefaultClusterAndZone) { - auto mock_counter = std::make_unique>(); - EXPECT_CALL(*mock_counter, - Add(A(), - A(), - A())) - .WillOnce([](std::uint64_t value, - opentelemetry::common::KeyValueIterable const& attributes, - opentelemetry::context::Context const&) { - EXPECT_THAT(value, Eq(0)); - EXPECT_THAT( - MakeAttributesMap(attributes), - UnorderedElementsAre( - Pair("project_id", "my-project-id"), - Pair("instance", "my-instance"), - Pair("cluster", ""), Pair("table", "my-table"), - Pair("zone", "global"), Pair("method", "my-method"), - Pair("status", "OK"), Pair("client_name", "my-client-name"), - Pair("client_uid", "my-client-uid"), - Pair("app_profile", "my-app-profile"))); - }); - - opentelemetry::nostd::shared_ptr mock_meter = - std::make_shared(); - EXPECT_CALL(*mock_meter, CreateUInt64Counter) - .WillOnce([mock = std::move(mock_counter)]( - opentelemetry::nostd::string_view name, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view) mutable { - EXPECT_THAT(name, Eq("retry_count")); - return std::move(mock); - }); - - opentelemetry::nostd::shared_ptr mock_provider = - std::make_shared(); - EXPECT_CALL(*mock_provider, GetMeter) -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view, - opentelemetry::common::KeyValueIterable const*) mutable { -#else - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view) mutable { -#endif - EXPECT_THAT(scope, Eq("my-instrument-scope")); - EXPECT_THAT(scope_version, Eq("v1")); - return mock_meter; - }); - - RetryCount retry_count("my-instrument-scope", mock_provider); - TableResourceLabels resource_labels{"my-project-id", "my-instance", - "my-table", "", ""}; - TableDataLabels data_labels{"my-method", "my-streaming", - "my-client-name", "my-client-uid", - "my-app-profile", ""}; - auto clone = retry_count.clone(resource_labels, data_labels); - - grpc::ClientContext client_context; - - auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); - auto clock = std::make_shared(); - - clock->SetTime(std::chrono::steady_clock::now()); - clone->PreCall(otel_context, {clock->Now(), true}); - clock->AdvanceTime(std::chrono::microseconds(1234)); - clone->PostCall(otel_context, client_context, - {clock->Now(), Status{StatusCode::kOk, "ok"}}); - clock->AdvanceTime(std::chrono::milliseconds(100)); - clone->OnDone(otel_context, {clock->Now(), Status{StatusCode::kOk, "ok"}}); -} - -TEST(FirstResponseLatency, Success) { - auto mock_histogram = std::make_unique>(); - EXPECT_CALL( - *mock_histogram, - Record(A(), A(), - A())) - .WillOnce([](double value, - opentelemetry::common::KeyValueIterable const& attributes, - opentelemetry::context::Context const&) { - EXPECT_THAT(value, Eq(2.0)); - EXPECT_THAT( - MakeAttributesMap(attributes), - UnorderedElementsAre( - Pair("project_id", "my-project-id"), - Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), - Pair("table", "my-table"), Pair("zone", "my-zone"), - Pair("method", "my-method"), Pair("status", "OK"), - Pair("client_name", "my-client-name"), - Pair("client_uid", "my-client-uid"), - Pair("app_profile", "my-app-profile"))); - }); - - opentelemetry::nostd::shared_ptr mock_meter = - std::make_shared(); - EXPECT_CALL(*mock_meter, CreateDoubleHistogram) - .WillOnce([mock = std::move(mock_histogram)]( - opentelemetry::nostd::string_view name, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view) mutable { - EXPECT_THAT(name, Eq("first_response_latencies")); - return std::move(mock); - }); - - opentelemetry::nostd::shared_ptr mock_provider = - std::make_shared(); - EXPECT_CALL(*mock_provider, GetMeter) -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view, - opentelemetry::common::KeyValueIterable const*) mutable { -#else - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view) mutable { -#endif - EXPECT_THAT(scope, Eq("my-instrument-scope")); - EXPECT_THAT(scope_version, Eq("v1")); - return mock_meter; - }); - - FirstResponseLatency first_response_latency("my-instrument-scope", - mock_provider); - TableResourceLabels resource_labels{"my-project-id", "my-instance", - "my-table", "", ""}; - TableDataLabels data_labels{"my-method", "my-streaming", - "my-client-name", "my-client-uid", - "my-app-profile", ""}; - auto clone = first_response_latency.clone(resource_labels, data_labels); - - grpc::ClientContext client_context; - SetClusterZone(client_context); - - auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); - auto clock = std::make_shared(); - - // Verify that only the first response is recorded. - clock->SetTime(std::chrono::steady_clock::now()); - clone->PreCall(otel_context, {clock->Now(), true}); - clock->AdvanceTime(std::chrono::milliseconds(2)); - clone->ElementDelivery(otel_context, {clock->Now(), true}); - clock->AdvanceTime(std::chrono::milliseconds(5)); - clone->ElementDelivery(otel_context, {clock->Now(), false}); - clock->AdvanceTime(std::chrono::milliseconds(5)); - clone->PostCall(otel_context, client_context, - {clock->Now(), Status{StatusCode::kOk, "ok"}}); - clock->AdvanceTime(std::chrono::milliseconds(5)); - clone->OnDone(otel_context, {clock->Now(), Status{StatusCode::kOk, "ok"}}); -} - -TEST(FirstResponseLatency, NoDataReceived) { - auto mock_histogram = std::make_unique>(); - EXPECT_CALL( - *mock_histogram, - Record(A(), A(), - A())) - .Times(0); - - opentelemetry::nostd::shared_ptr mock_meter = - std::make_shared(); - EXPECT_CALL(*mock_meter, CreateDoubleHistogram) - .WillOnce([mock = std::move(mock_histogram)]( - opentelemetry::nostd::string_view name, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view) mutable { - EXPECT_THAT(name, Eq("first_response_latencies")); - return std::move(mock); - }); - - opentelemetry::nostd::shared_ptr mock_provider = - std::make_shared(); - EXPECT_CALL(*mock_provider, GetMeter) -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view, - opentelemetry::common::KeyValueIterable const*) mutable { -#else - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view) mutable { -#endif - EXPECT_THAT(scope, Eq("my-instrument-scope")); - EXPECT_THAT(scope_version, Eq("v1")); - return mock_meter; - }); - - FirstResponseLatency first_response_latency("my-instrument-scope", - mock_provider); - TableResourceLabels resource_labels{"my-project-id", "my-instance", - "my-table", "", ""}; - TableDataLabels data_labels{"my-method", "my-streaming", - "my-client-name", "my-client-uid", - "my-app-profile", ""}; - auto clone = first_response_latency.clone(resource_labels, data_labels); - - grpc::ClientContext client_context; - SetClusterZone(client_context); - - auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); - auto clock = std::make_shared(); - - // ElementDelivery is not called. - clock->SetTime(std::chrono::steady_clock::now()); - clone->PreCall(otel_context, {clock->Now(), true}); - clock->AdvanceTime(std::chrono::milliseconds(5)); - clone->PostCall( - otel_context, client_context, - {clock->Now(), Status{StatusCode::kDeadlineExceeded, "timeout"}}); - clock->AdvanceTime(std::chrono::milliseconds(5)); - clone->OnDone( - otel_context, - {clock->Now(), Status{StatusCode::kDeadlineExceeded, "timeout"}}); -} - -TEST(FirstResponseLatency, UsesDefaultClusterAndZone) { - auto mock_histogram = std::make_unique>(); - EXPECT_CALL( - *mock_histogram, - Record(A(), A(), - A())) - .WillOnce([](double value, - opentelemetry::common::KeyValueIterable const& attributes, - opentelemetry::context::Context const&) { - EXPECT_THAT(value, Eq(2.0)); - EXPECT_THAT( - MakeAttributesMap(attributes), - UnorderedElementsAre( - Pair("project_id", "my-project-id"), - Pair("instance", "my-instance"), - Pair("cluster", ""), Pair("table", "my-table"), - Pair("zone", "global"), Pair("method", "my-method"), - Pair("status", "OK"), Pair("client_name", "my-client-name"), - Pair("client_uid", "my-client-uid"), - Pair("app_profile", "my-app-profile"))); - }); - - opentelemetry::nostd::shared_ptr mock_meter = - std::make_shared(); - EXPECT_CALL(*mock_meter, CreateDoubleHistogram) - .WillOnce([mock = std::move(mock_histogram)]( - opentelemetry::nostd::string_view name, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view) mutable { - EXPECT_THAT(name, Eq("first_response_latencies")); - return std::move(mock); - }); - - opentelemetry::nostd::shared_ptr mock_provider = - std::make_shared(); - EXPECT_CALL(*mock_provider, GetMeter) -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view, - opentelemetry::common::KeyValueIterable const*) mutable { -#else - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view) mutable { -#endif - EXPECT_THAT(scope, Eq("my-instrument-scope")); - EXPECT_THAT(scope_version, Eq("v1")); - return mock_meter; - }); - - FirstResponseLatency first_response_latency("my-instrument-scope", - mock_provider); - TableResourceLabels resource_labels{"my-project-id", "my-instance", - "my-table", "", ""}; - TableDataLabels data_labels{"my-method", "my-streaming", - "my-client-name", "my-client-uid", - "my-app-profile", ""}; - auto clone = first_response_latency.clone(resource_labels, data_labels); - - grpc::ClientContext client_context; - - auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); - auto clock = std::make_shared(); - - clock->SetTime(std::chrono::steady_clock::now()); - clone->PreCall(otel_context, {clock->Now(), true}); - clock->AdvanceTime(std::chrono::milliseconds(2)); - clone->ElementDelivery(otel_context, {clock->Now(), true}); - clone->PostCall(otel_context, client_context, - {clock->Now(), Status{StatusCode::kOk, "ok"}}); - clone->OnDone(otel_context, {clock->Now(), Status{StatusCode::kOk, "ok"}}); -} - -TEST(GetServerLatencyFromInitialMetadata, NonEmptyHeader) { - grpc::ClientContext client_context; - RpcMetadata server_metadata; - server_metadata.headers.emplace("server-timing", "gfet4t7; dur=10.5"); - SetServerMetadata(client_context, server_metadata); - - auto result = GetServerLatencyFromInitialMetadata(client_context); - ASSERT_TRUE(result); - EXPECT_THAT(result, Eq(10.5)); -} - -TEST(GetServerLatencyFromInitialMetadata, EmptyHeader) { - grpc::ClientContext client_context; - RpcMetadata server_metadata; - SetServerMetadata(client_context, server_metadata); - - auto result = GetServerLatencyFromInitialMetadata(client_context); - EXPECT_FALSE(result); -} - -TEST(GetServerLatencyFromInitialMetadata, MultipleDurValuesInHeader) { - grpc::ClientContext client_context; - RpcMetadata server_metadata; - server_metadata.headers.emplace("server-timing", - " gfet4t7; dur=2.1, gcp; dur=123"); - SetServerMetadata(client_context, server_metadata); - - auto result = GetServerLatencyFromInitialMetadata(client_context); - ASSERT_TRUE(result); - EXPECT_THAT(result, Eq(2.1)); -} - -TEST(GetServerLatencyFromInitialMetadata, NoGfePresent) { - grpc::ClientContext client_context; - RpcMetadata server_metadata; - server_metadata.headers.emplace("server-timing", "gcp; dur=123"); - SetServerMetadata(client_context, server_metadata); - - auto result = GetServerLatencyFromInitialMetadata(client_context); - EXPECT_FALSE(result); -} - -TEST(ServerLatency, SingleSuccess) { - auto mock_histogram = std::make_unique>(); - EXPECT_CALL( - *mock_histogram, - Record(A(), A(), - A())) - .WillOnce([](double value, - opentelemetry::common::KeyValueIterable const& attributes, - opentelemetry::context::Context const&) { - EXPECT_THAT(value, Eq(15.013)); - EXPECT_THAT( - MakeAttributesMap(attributes), - UnorderedElementsAre( - Pair("project_id", "my-project-id"), - Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), - Pair("table", "my-table"), Pair("zone", "my-zone"), - Pair("method", "my-method"), Pair("streaming", "my-streaming"), - Pair("status", "OK"), Pair("client_name", "my-client-name"), - Pair("client_uid", "my-client-uid"), - Pair("app_profile", "my-app-profile"))); - }); - - opentelemetry::nostd::shared_ptr mock_meter = - std::make_shared(); - EXPECT_CALL(*mock_meter, CreateDoubleHistogram) - .WillOnce([mock = std::move(mock_histogram)]( - opentelemetry::nostd::string_view name, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view) mutable { - EXPECT_THAT(name, Eq("server_latencies")); - return std::move(mock); - }); - - opentelemetry::nostd::shared_ptr mock_provider = - std::make_shared(); - EXPECT_CALL(*mock_provider, GetMeter) -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view, - opentelemetry::common::KeyValueIterable const*) mutable { -#else - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view) mutable { -#endif - EXPECT_THAT(scope, Eq("my-instrument-scope")); - EXPECT_THAT(scope_version, Eq("v1")); - return mock_meter; - }); - - ServerLatency server_latency("my-instrument-scope", mock_provider); - TableResourceLabels resource_labels{"my-project-id", "my-instance", - "my-table", "", ""}; - TableDataLabels data_labels{"my-method", "my-streaming", - "my-client-name", "my-client-uid", - "my-app-profile", ""}; - auto clone = server_latency.clone(resource_labels, data_labels); - - grpc::ClientContext client_context; - google::bigtable::v2::ResponseParams response_params; - response_params.set_cluster_id("my-cluster"); - response_params.set_zone_id("my-zone"); - RpcMetadata server_metadata; - server_metadata.headers.emplace("server-timing", "gfet4t7; dur=15.013"); - server_metadata.trailers.emplace("x-goog-ext-425905942-bin", - response_params.SerializeAsString()); - SetServerMetadata(client_context, server_metadata); - - auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); - auto clock = std::make_shared(); - - clone->PostCall(otel_context, client_context, - {clock->Now(), Status{StatusCode::kOk, "ok"}}); -} - -TEST(ServerLatency, UsesDefaultClusterAndZone) { - auto mock_histogram = std::make_unique>(); - EXPECT_CALL( - *mock_histogram, - Record(A(), A(), - A())) - .WillOnce([](double value, - opentelemetry::common::KeyValueIterable const& attributes, - opentelemetry::context::Context const&) { - EXPECT_THAT(value, Eq(15.013)); - EXPECT_THAT( - MakeAttributesMap(attributes), - UnorderedElementsAre( - Pair("project_id", "my-project-id"), - Pair("instance", "my-instance"), - Pair("cluster", ""), Pair("table", "my-table"), - Pair("zone", "global"), Pair("method", "my-method"), - Pair("streaming", "my-streaming"), Pair("status", "OK"), - Pair("client_name", "my-client-name"), - Pair("client_uid", "my-client-uid"), - Pair("app_profile", "my-app-profile"))); - }); - opentelemetry::nostd::shared_ptr mock_meter = - std::make_shared(); - EXPECT_CALL(*mock_meter, CreateDoubleHistogram) - .WillOnce([mock = std::move(mock_histogram)]( - opentelemetry::nostd::string_view name, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view) mutable { - EXPECT_THAT(name, Eq("server_latencies")); - return std::move(mock); - }); - - opentelemetry::nostd::shared_ptr mock_provider = - std::make_shared(); - EXPECT_CALL(*mock_provider, GetMeter) -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view, - opentelemetry::common::KeyValueIterable const*) mutable { -#else - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view) mutable { -#endif - EXPECT_THAT(scope, Eq("my-instrument-scope")); - EXPECT_THAT(scope_version, Eq("v1")); - return mock_meter; - }); - - ServerLatency server_latency("my-instrument-scope", mock_provider); - TableResourceLabels resource_labels{"my-project-id", "my-instance", - "my-table", "", ""}; - TableDataLabels data_labels{"my-method", "my-streaming", - "my-client-name", "my-client-uid", - "my-app-profile", ""}; - auto clone = server_latency.clone(resource_labels, data_labels); - - grpc::ClientContext client_context; - RpcMetadata server_metadata; - server_metadata.headers.emplace("server-timing", "gfet4t7; dur=15.013"); - SetServerMetadata(client_context, server_metadata); - - auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); - clone->PostCall( - otel_context, client_context, - {std::chrono::steady_clock::now(), Status{StatusCode::kOk, "ok"}}); -} - -TEST(ServerLatency, TwoAttempts) { - auto mock_histogram = std::make_unique>(); - EXPECT_CALL( - *mock_histogram, - Record(A(), A(), - A())) - .WillOnce([](double value, - opentelemetry::common::KeyValueIterable const& attributes, - opentelemetry::context::Context const&) { - EXPECT_THAT(value, Eq(15.0)); - EXPECT_THAT( - MakeAttributesMap(attributes), - UnorderedElementsAre( - Pair("project_id", "my-project-id"), - Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), - Pair("table", "my-table"), Pair("zone", "my-zone"), - Pair("method", "my-method"), Pair("streaming", "my-streaming"), - Pair("status", "OK"), Pair("client_name", "my-client-name"), - Pair("client_uid", "my-client-uid"), - Pair("app_profile", "my-app-profile"))); - }) - .WillOnce([](double value, - opentelemetry::common::KeyValueIterable const& attributes, - opentelemetry::context::Context const&) { - EXPECT_THAT(value, Eq(10.0)); - EXPECT_THAT( - MakeAttributesMap(attributes), - UnorderedElementsAre( - Pair("project_id", "my-project-id"), - Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), - Pair("table", "my-table"), Pair("zone", "my-zone"), - Pair("method", "my-method"), Pair("streaming", "my-streaming"), - Pair("status", "DEADLINE_EXCEEDED"), - Pair("client_name", "my-client-name"), - Pair("client_uid", "my-client-uid"), - Pair("app_profile", "my-app-profile"))); - }); - - opentelemetry::nostd::shared_ptr mock_meter = - std::make_shared(); - EXPECT_CALL(*mock_meter, CreateDoubleHistogram) - .WillOnce([mock = std::move(mock_histogram)]( - opentelemetry::nostd::string_view name, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view) mutable { - EXPECT_THAT(name, Eq("server_latencies")); - return std::move(mock); - }); - - opentelemetry::nostd::shared_ptr mock_provider = - std::make_shared(); - EXPECT_CALL(*mock_provider, GetMeter) -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view, - opentelemetry::common::KeyValueIterable const*) mutable { -#else - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view) mutable { -#endif - EXPECT_THAT(scope, Eq("my-instrument-scope")); - EXPECT_THAT(scope_version, Eq("v1")); - return mock_meter; - }); - - ServerLatency server_latency("my-instrument-scope", mock_provider); - TableResourceLabels resource_labels{"my-project-id", "my-instance", - "my-table", "", ""}; - TableDataLabels data_labels{"my-method", "my-streaming", - "my-client-name", "my-client-uid", - "my-app-profile", ""}; - auto clone = server_latency.clone(resource_labels, data_labels); - - grpc::ClientContext client_context; - google::bigtable::v2::ResponseParams response_params; - response_params.set_cluster_id("my-cluster"); - response_params.set_zone_id("my-zone"); - RpcMetadata server_metadata; - server_metadata.headers.emplace("server-timing", "gfet4t7; dur=15"); - server_metadata.trailers.emplace("x-goog-ext-425905942-bin", - response_params.SerializeAsString()); - SetServerMetadata(client_context, server_metadata); - - auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); - auto clock = std::make_shared(); - - clone->PostCall(otel_context, client_context, - {clock->Now(), Status{StatusCode::kOk, "ok"}}); - grpc::ClientContext client_context_two; - RpcMetadata server_metadata_two; - server_metadata_two.headers.emplace("server-timing", "gfet4t7; dur=10"); - server_metadata_two.trailers.emplace("x-goog-ext-425905942-bin", - response_params.SerializeAsString()); - SetServerMetadata(client_context_two, server_metadata_two); - clone->PostCall( - otel_context, client_context_two, - {clock->Now(), Status{StatusCode::kDeadlineExceeded, "timeout"}}); -} - -TEST(ServerLatency, NoServerTiming) { - auto mock_histogram = std::make_unique>(); - EXPECT_CALL( - *mock_histogram, - Record(A(), A(), - A())) - .Times(0); - - opentelemetry::nostd::shared_ptr mock_meter = - std::make_shared(); - EXPECT_CALL(*mock_meter, CreateDoubleHistogram) - .WillOnce([mock = std::move(mock_histogram)]( - opentelemetry::nostd::string_view name, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view) mutable { - EXPECT_THAT(name, Eq("server_latencies")); - return std::move(mock); - }); - - opentelemetry::nostd::shared_ptr mock_provider = - std::make_shared(); - EXPECT_CALL(*mock_provider, GetMeter) -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view, - opentelemetry::common::KeyValueIterable const*) mutable { -#else - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view) mutable { -#endif - EXPECT_THAT(scope, Eq("my-instrument-scope")); - EXPECT_THAT(scope_version, Eq("v1")); - return mock_meter; - }); - - ServerLatency server_latency("my-instrument-scope", mock_provider); - TableResourceLabels resource_labels{"my-project-id", "my-instance", - "my-table", "", ""}; - TableDataLabels data_labels{"my-method", "my-streaming", - "my-client-name", "my-client-uid", - "my-app-profile", ""}; - auto clone = server_latency.clone(resource_labels, data_labels); - - grpc::ClientContext client_context; - SetClusterZone(client_context); - auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); - auto clock = std::make_shared(); - clone->PostCall(otel_context, client_context, - {clock->Now(), Status{StatusCode::kOk, "ok"}}); -} - -TEST(ConnectivityErrorCount, MissingResourceLabels) { - auto mock_counter = std::make_unique>(); - EXPECT_CALL(*mock_counter, - Add(A(), - A(), - A())) - .WillOnce([](std::uint64_t value, - opentelemetry::common::KeyValueIterable const& attributes, - opentelemetry::context::Context const&) { - EXPECT_THAT(value, Eq(4)); - EXPECT_THAT( - MakeAttributesMap(attributes), - UnorderedElementsAre( - Pair("project_id", "my-project-id"), - Pair("instance", "my-instance"), - Pair("cluster", ""), Pair("table", "my-table"), - Pair("zone", "global"), Pair("method", "my-method"), - Pair("status", "OK"), Pair("client_name", "my-client-name"), - Pair("client_uid", "my-client-uid"), - Pair("app_profile", "my-app-profile"))); - }); - - opentelemetry::nostd::shared_ptr mock_meter = - std::make_shared(); - EXPECT_CALL(*mock_meter, CreateUInt64Counter) - .WillOnce([mock = std::move(mock_counter)]( - opentelemetry::nostd::string_view name, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view) mutable { - EXPECT_THAT(name, Eq("connectivity_error_count")); - return std::move(mock); - }); - - opentelemetry::nostd::shared_ptr mock_provider = - std::make_shared(); - EXPECT_CALL(*mock_provider, GetMeter) -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view, - opentelemetry::common::KeyValueIterable const*) mutable { -#else - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view) mutable { -#endif - EXPECT_THAT(scope, Eq("my-instrument-scope")); - EXPECT_THAT(scope_version, Eq("v1")); - return mock_meter; - }); - - ConnectivityErrorCount connectivity_error_count("my-instrument-scope", - mock_provider); - TableResourceLabels resource_labels{"my-project-id", "my-instance", - "my-table", "", ""}; - TableDataLabels data_labels{"my-method", "my-streaming", - "my-client-name", "my-client-uid", - "my-app-profile", ""}; - auto clone = connectivity_error_count.clone(resource_labels, data_labels); - - grpc::ClientContext client_context; - RpcMetadata server_metadata; - server_metadata.headers.emplace("server-timing", "gfet4t7; dur=15"); - SetServerMetadata(client_context, server_metadata); - auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); - auto clock = std::make_shared(); - clone->PostCall(otel_context, client_context, - {clock->Now(), Status{StatusCode::kOk, "ok"}}); - clock->AdvanceTime(std::chrono::microseconds(1234)); - clone->PostCall(otel_context, client_context, - {clock->Now(), Status{StatusCode::kOk, "ok"}}); - clock->AdvanceTime(std::chrono::microseconds(1234)); - clone->PostCall(otel_context, client_context, - {clock->Now(), Status{StatusCode::kOk, "ok"}}); - clock->AdvanceTime(std::chrono::microseconds(1234)); - clone->PostCall(otel_context, client_context, - {clock->Now(), Status{StatusCode::kOk, "ok"}}); - clone->OnDone(otel_context, {clock->Now(), Status{StatusCode::kOk, "ok"}}); -} - -TEST(ConnectivityErrorCount, Success) { - auto mock_counter = std::make_unique>(); - EXPECT_CALL(*mock_counter, - Add(A(), - A(), - A())) - .WillOnce([](std::uint64_t value, - opentelemetry::common::KeyValueIterable const& attributes, - opentelemetry::context::Context const&) { - EXPECT_THAT(value, Eq(0)); - EXPECT_THAT( - MakeAttributesMap(attributes), - UnorderedElementsAre( - Pair("project_id", "my-project-id"), - Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), - Pair("table", "my-table"), Pair("zone", "my-zone"), - Pair("method", "my-method"), Pair("status", "OK"), - Pair("client_name", "my-client-name"), - Pair("client_uid", "my-client-uid"), - Pair("app_profile", "my-app-profile"))); - }); - - opentelemetry::nostd::shared_ptr mock_meter = - std::make_shared(); - EXPECT_CALL(*mock_meter, CreateUInt64Counter) - .WillOnce([mock = std::move(mock_counter)]( - opentelemetry::nostd::string_view name, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view) mutable { - EXPECT_THAT(name, Eq("connectivity_error_count")); - return std::move(mock); - }); - - opentelemetry::nostd::shared_ptr mock_provider = - std::make_shared(); - EXPECT_CALL(*mock_provider, GetMeter) -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view, - opentelemetry::common::KeyValueIterable const*) mutable { -#else - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view) mutable { -#endif - EXPECT_THAT(scope, Eq("my-instrument-scope")); - EXPECT_THAT(scope_version, Eq("v1")); - return mock_meter; - }); - - ConnectivityErrorCount connectivity_error_count("my-instrument-scope", - mock_provider); - TableResourceLabels resource_labels{"my-project-id", "my-instance", - "my-table", "", ""}; - TableDataLabels data_labels{"my-method", "my-streaming", - "my-client-name", "my-client-uid", - "my-app-profile", ""}; - auto clone = connectivity_error_count.clone(resource_labels, data_labels); - - grpc::ClientContext client_context; - google::bigtable::v2::ResponseParams response_params; - response_params.set_cluster_id("my-cluster"); - response_params.set_zone_id("my-zone"); - RpcMetadata server_metadata; - server_metadata.headers.emplace("server-timing", "gfet4t7; dur=15"); - server_metadata.trailers.emplace("x-goog-ext-425905942-bin", - response_params.SerializeAsString()); - SetServerMetadata(client_context, server_metadata); - auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); - auto clock = std::make_shared(); - - clone->PostCall(otel_context, client_context, - {clock->Now(), Status{StatusCode::kOk, "ok"}}); - clone->OnDone(otel_context, {clock->Now(), Status{StatusCode::kOk, "ok"}}); -} - -TEST(ConnectivityErrorCount, OkAndMissingServerTiming) { - auto mock_counter = std::make_unique>(); - EXPECT_CALL(*mock_counter, - Add(A(), - A(), - A())) - .WillOnce([](std::uint64_t value, - opentelemetry::common::KeyValueIterable const& attributes, - opentelemetry::context::Context const&) { - EXPECT_THAT(value, Eq(1)); - EXPECT_THAT( - MakeAttributesMap(attributes), - UnorderedElementsAre( - Pair("project_id", "my-project-id"), - Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), - Pair("table", "my-table"), Pair("zone", "my-zone"), - Pair("method", "my-method"), Pair("status", "OK"), - Pair("client_name", "my-client-name"), - Pair("client_uid", "my-client-uid"), - Pair("app_profile", "my-app-profile"))); - }); - - opentelemetry::nostd::shared_ptr mock_meter = - std::make_shared(); - EXPECT_CALL(*mock_meter, CreateUInt64Counter) - .WillOnce([mock = std::move(mock_counter)]( - opentelemetry::nostd::string_view name, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view) mutable { - EXPECT_THAT(name, Eq("connectivity_error_count")); - return std::move(mock); - }); - - opentelemetry::nostd::shared_ptr mock_provider = - std::make_shared(); - EXPECT_CALL(*mock_provider, GetMeter) -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view, - opentelemetry::common::KeyValueIterable const*) mutable { -#else - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view) mutable { -#endif - EXPECT_THAT(scope, Eq("my-instrument-scope")); - EXPECT_THAT(scope_version, Eq("v1")); - return mock_meter; - }); - - ConnectivityErrorCount connectivity_error_count("my-instrument-scope", - mock_provider); - TableResourceLabels resource_labels{"my-project-id", "my-instance", - "my-table", "", ""}; - TableDataLabels data_labels{"my-method", "my-streaming", - "my-client-name", "my-client-uid", - "my-app-profile", ""}; - auto clone = connectivity_error_count.clone(resource_labels, data_labels); - - grpc::ClientContext client_context; - SetClusterZone(client_context); - auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); - auto clock = std::make_shared(); - clone->PostCall(otel_context, client_context, - {clock->Now(), Status{StatusCode::kOk, "ok"}}); - clone->OnDone(otel_context, {clock->Now(), Status{StatusCode::kOk, "ok"}}); -} - -TEST(ConnectivityErrorCount, DeadlineExceededAndMissingServerTiming) { - auto mock_counter = std::make_unique>(); - EXPECT_CALL(*mock_counter, - Add(A(), - A(), - A())) - .WillOnce([](std::uint64_t value, - opentelemetry::common::KeyValueIterable const& attributes, - opentelemetry::context::Context const&) { - EXPECT_THAT(value, Eq(0)); - EXPECT_THAT( - MakeAttributesMap(attributes), - UnorderedElementsAre( - Pair("project_id", "my-project-id"), - Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), - Pair("table", "my-table"), Pair("zone", "my-zone"), - Pair("method", "my-method"), - Pair("status", "DEADLINE_EXCEEDED"), - Pair("client_name", "my-client-name"), - Pair("client_uid", "my-client-uid"), - Pair("app_profile", "my-app-profile"))); - }); - - opentelemetry::nostd::shared_ptr mock_meter = - std::make_shared(); - EXPECT_CALL(*mock_meter, CreateUInt64Counter) - .WillOnce([mock = std::move(mock_counter)]( - opentelemetry::nostd::string_view name, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view) mutable { - EXPECT_THAT(name, Eq("connectivity_error_count")); - return std::move(mock); - }); - - opentelemetry::nostd::shared_ptr mock_provider = - std::make_shared(); - EXPECT_CALL(*mock_provider, GetMeter) -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view, - opentelemetry::common::KeyValueIterable const*) mutable { -#else - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view) mutable { -#endif - EXPECT_THAT(scope, Eq("my-instrument-scope")); - EXPECT_THAT(scope_version, Eq("v1")); - return mock_meter; - }); - - ConnectivityErrorCount connectivity_error_count("my-instrument-scope", - mock_provider); - TableResourceLabels resource_labels{"my-project-id", "my-instance", - "my-table", "", ""}; - TableDataLabels data_labels{"my-method", "my-streaming", - "my-client-name", "my-client-uid", - "my-app-profile", ""}; - auto clone = connectivity_error_count.clone(resource_labels, data_labels); - - grpc::ClientContext client_context; - SetClusterZone(client_context); - auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); - auto clock = std::make_shared(); - clone->PostCall( - otel_context, client_context, - {clock->Now(), Status{StatusCode::kDeadlineExceeded, "timeout"}}); - clone->OnDone(otel_context, {clock->Now(), Status{StatusCode::kOk, "ok"}}); -} -TEST(ApplicationBlockingLatency, Success) { - auto mock_histogram = std::make_unique>(); - EXPECT_CALL( - *mock_histogram, - Record(A(), A(), - A())) - .WillOnce([](double value, - opentelemetry::common::KeyValueIterable const& attributes, - opentelemetry::context::Context const&) { - EXPECT_THAT(value, Eq(1.234)); - EXPECT_THAT( - MakeAttributesMap(attributes), - UnorderedElementsAre( - Pair("project_id", "my-project-id"), - Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), - Pair("table", "my-table"), Pair("zone", "my-zone"), - Pair("method", "my-method"), - Pair("client_name", "my-client-name"), - Pair("client_uid", "my-client-uid"), - Pair("app_profile", "my-app-profile"))); - }); - - opentelemetry::nostd::shared_ptr mock_meter = - std::make_shared(); - EXPECT_CALL(*mock_meter, CreateDoubleHistogram) - .WillOnce([mock = std::move(mock_histogram)]( - opentelemetry::nostd::string_view name, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view) mutable { - EXPECT_THAT(name, Eq("application_latencies")); - return std::move(mock); - }); - - opentelemetry::nostd::shared_ptr mock_provider = - std::make_shared(); - EXPECT_CALL(*mock_provider, GetMeter) -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view, - opentelemetry::common::KeyValueIterable const*) mutable { -#else - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view) mutable { -#endif - EXPECT_THAT(scope, Eq("my-instrument-scope")); - EXPECT_THAT(scope_version, Eq("v1")); - return mock_meter; - }); - - ApplicationBlockingLatency application_blocking_latency("my-instrument-scope", - mock_provider); - TableResourceLabels resource_labels{"my-project-id", "my-instance", - "my-table", "", ""}; - TableDataLabels data_labels{"my-method", "my-streaming", - "my-client-name", "my-client-uid", - "my-app-profile", ""}; - auto clone = application_blocking_latency.clone(resource_labels, data_labels); - - grpc::ClientContext client_context; - SetClusterZone(client_context); - auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); - auto clock = std::make_shared(); - - clock->SetTime(std::chrono::steady_clock::now()); - clone->ElementDelivery(otel_context, {clock->Now(), true}); - clock->AdvanceTime(std::chrono::microseconds(1234)); - clone->ElementRequest(otel_context, {clock->Now()}); - clone->PostCall(otel_context, client_context, {clock->Now(), Status{}}); - clone->OnDone(otel_context, {clock->Now(), Status{}}); -} - -TEST(ApplicationBlockingLatency, StreamingData) { - auto mock_histogram = std::make_unique>(); - EXPECT_CALL( - *mock_histogram, - Record(A(), A(), - A())) - .WillOnce([](double value, - opentelemetry::common::KeyValueIterable const& attributes, - opentelemetry::context::Context const&) { - EXPECT_THAT(value, Eq(1.0)); - EXPECT_THAT( - MakeAttributesMap(attributes), - UnorderedElementsAre( - Pair("project_id", "my-project-id"), - Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), - Pair("table", "my-table"), Pair("zone", "my-zone"), - Pair("method", "my-method"), - Pair("client_name", "my-client-name"), - Pair("client_uid", "my-client-uid"), - Pair("app_profile", "my-app-profile"))); - }) - .WillOnce([](double value, - opentelemetry::common::KeyValueIterable const& attributes, - opentelemetry::context::Context const&) { - EXPECT_THAT(value, Eq(5.0)); - EXPECT_THAT( - MakeAttributesMap(attributes), - UnorderedElementsAre( - Pair("project_id", "my-project-id"), - Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), - Pair("table", "my-table"), Pair("zone", "my-zone"), - Pair("method", "my-method"), - Pair("client_name", "my-client-name"), - Pair("client_uid", "my-client-uid"), - Pair("app_profile", "my-app-profile"))); - }); - - opentelemetry::nostd::shared_ptr mock_meter = - std::make_shared(); - EXPECT_CALL(*mock_meter, CreateDoubleHistogram) - .WillOnce([mock = std::move(mock_histogram)]( - opentelemetry::nostd::string_view name, - opentelemetry::nostd::string_view, - opentelemetry::nostd::string_view) mutable { - EXPECT_THAT(name, Eq("application_latencies")); - return std::move(mock); - }); - - opentelemetry::nostd::shared_ptr mock_provider = - std::make_shared(); - EXPECT_CALL(*mock_provider, GetMeter) -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view, - opentelemetry::common::KeyValueIterable const*) mutable { -#else - .WillOnce([&](opentelemetry::nostd::string_view scope, - opentelemetry::nostd::string_view scope_version, - opentelemetry::nostd::string_view) mutable { -#endif - EXPECT_THAT(scope, Eq("my-instrument-scope")); - EXPECT_THAT(scope_version, Eq("v1")); - return mock_meter; - }); - - ApplicationBlockingLatency application_blocking_latency("my-instrument-scope", - mock_provider); - TableResourceLabels resource_labels{"my-project-id", "my-instance", - "my-table", "", ""}; - TableDataLabels data_labels{"my-method", "my-streaming", - "my-client-name", "my-client-uid", - "my-app-profile", ""}; - auto clone = application_blocking_latency.clone(resource_labels, data_labels); - - grpc::ClientContext client_context; - SetClusterZone(client_context); - auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); - auto clock = std::make_shared(); - - clock->SetTime(std::chrono::steady_clock::now()); - clone->ElementDelivery(otel_context, {clock->Now(), true}); - clock->AdvanceTime(std::chrono::milliseconds(1)); - clone->ElementRequest(otel_context, {clock->Now()}); - clock->AdvanceTime(std::chrono::milliseconds(10)); - clone->ElementDelivery(otel_context, {clock->Now(), true}); - clock->AdvanceTime(std::chrono::milliseconds(5)); - clone->ElementRequest(otel_context, {clock->Now()}); - clone->PostCall(otel_context, client_context, {clock->Now(), Status{}}); - clone->OnDone(otel_context, {clock->Now(), Status{}}); -} - -class FakeTableMetric : public Metric { - public: - std::unique_ptr clone(TableResourceLabels const&, - TableDataLabels const&) const override { - return std::make_unique(*this); - } -}; - -TEST(MetricsTest, CloneMetrics) { - auto table_metric = std::make_shared(); - - TableResourceLabels resource_labels{"project", "instance", "table", "cluster", - "zone"}; - TableDataLabels data_labels{"method", "streaming", "client", - "uid", "profile", "status"}; - - std::vector> metrics = {table_metric}; - auto cloned = CloneMetrics(resource_labels, data_labels, metrics); - EXPECT_THAT(cloned, SizeIs(1)); -} - -} // namespace +namespace {} // namespace GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END } // namespace bigtable_internal } // namespace cloud diff --git a/google/cloud/bigtable/internal/operation_context_factory.cc b/google/cloud/bigtable/internal/operation_context_factory.cc index 900c7aa369b61..fb5ebb397b1d5 100644 --- a/google/cloud/bigtable/internal/operation_context_factory.cc +++ b/google/cloud/bigtable/internal/operation_context_factory.cc @@ -16,9 +16,11 @@ #ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS #include "google/cloud/bigtable/internal/metrics.h" +#include "google/cloud/bigtable/internal/table_schema_metrics.h" #include "google/cloud/bigtable/options.h" #include "google/cloud/opentelemetry/internal/monitoring_exporter.h" #include "google/cloud/opentelemetry/monitoring_exporter.h" +#include "google/cloud/opentelemetry/resource_detector.h" #include "google/cloud/internal/algorithm.h" #include "absl/strings/str_split.h" #include "google/api/monitored_resource.pb.h" @@ -544,6 +546,23 @@ std::shared_ptr MetricsOperationContextFactory::ExecuteQuery( clock_); } +std::vector> CloneMetrics( + TableResourceLabels const& resource_labels, + TableDataLabels const& data_labels, + std::vector> const& metrics) { + std::vector> v; + v.reserve(metrics.size()); + for (auto const& m : metrics) { + // We should never add a nullptr Metric to the list. + if (m == nullptr) continue; + if (m->schema() == MetricSchema::kTable) { + v.push_back(static_cast(m.get())->clone( + resource_labels, data_labels)); + } + } + return v; +} + #endif // GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END diff --git a/google/cloud/bigtable/internal/operation_context_factory.h b/google/cloud/bigtable/internal/operation_context_factory.h index c8d77b4f7c6b0..50ea4c932f42f 100644 --- a/google/cloud/bigtable/internal/operation_context_factory.h +++ b/google/cloud/bigtable/internal/operation_context_factory.h @@ -18,6 +18,8 @@ #include "google/cloud/bigtable/internal/operation_context.h" #include "google/cloud/bigtable/version.h" #ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS +#include "google/cloud/bigtable/internal/metrics.h" +#include "google/cloud/bigtable/internal/table_schema_metrics.h" #include "google/cloud/monitoring/v3/metric_connection.h" #include "absl/base/call_once.h" #include @@ -158,6 +160,11 @@ class MetricsOperationContextFactory : public OperationContextFactory { MetricHolder execute_query_metrics_; }; +std::vector> CloneMetrics( + TableResourceLabels const& resource_labels, + TableDataLabels const& data_labels, + std::vector> const& metrics); + #endif // GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END diff --git a/google/cloud/bigtable/internal/operation_context_factory_test.cc b/google/cloud/bigtable/internal/operation_context_factory_test.cc index d00ecf8240045..d10a9d69596f4 100644 --- a/google/cloud/bigtable/internal/operation_context_factory_test.cc +++ b/google/cloud/bigtable/internal/operation_context_factory_test.cc @@ -16,6 +16,7 @@ #include "google/cloud/bigtable/internal/operation_context_factory.h" #include "google/cloud/bigtable/internal/metrics.h" +#include "google/cloud/bigtable/internal/table_schema_metrics.h" #include namespace google { @@ -26,10 +27,11 @@ namespace { using ::testing::Eq; using ::testing::IsEmpty; +using ::testing::SizeIs; -class MockMetric : public Metric { +class MockMetric : public TableSchemaMetric { public: - MOCK_METHOD(std::unique_ptr, clone, + MOCK_METHOD(std::unique_ptr, clone, (TableResourceLabels const&, TableDataLabels const&), (const, override)); }; @@ -208,6 +210,27 @@ TEST(MetricsOperationContextFactoryTest, ExecuteQuery) { factory.ExecuteQuery(instance_full_name, app_profile); } +class FakeTableMetric : public TableSchemaMetric { + public: + std::unique_ptr clone( + TableResourceLabels const&, TableDataLabels const&) const override { + return std::make_unique(*this); + } +}; + +TEST(MetricsOperationContextFactoryTest, CloneMetrics) { + auto table_metric = std::make_shared(); + + TableResourceLabels resource_labels{"project", "instance", "table", "cluster", + "zone"}; + TableDataLabels data_labels{"method", "streaming", "client", + "uid", "profile", "status"}; + + std::vector> metrics = {table_metric}; + auto cloned = CloneMetrics(resource_labels, data_labels, metrics); + EXPECT_THAT(cloned, SizeIs(1)); +} + } // namespace GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END } // namespace bigtable_internal diff --git a/google/cloud/bigtable/internal/operation_context_test.cc b/google/cloud/bigtable/internal/operation_context_test.cc index 3d1a1c395f70d..459612f6a6ef9 100644 --- a/google/cloud/bigtable/internal/operation_context_test.cc +++ b/google/cloud/bigtable/internal/operation_context_test.cc @@ -13,7 +13,7 @@ // limitations under the License. #include "google/cloud/bigtable/internal/operation_context.h" -#include "google/cloud/bigtable/internal/metrics.h" +#include "google/cloud/bigtable/internal/table_schema_metrics.h" #include "google/cloud/testing_util/fake_clock.h" #include "google/cloud/testing_util/validate_metadata.h" #include @@ -120,7 +120,7 @@ TEST_F(OperationContextTest, Retries) { #ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS -class MockMetric : public Metric { +class MockMetric : public TableSchemaMetric { public: MOCK_METHOD(void, PreCall, (opentelemetry::context::Context const&, PreCallParams const&), @@ -140,9 +140,8 @@ class MockMetric : public Metric { (opentelemetry::context::Context const&, ElementDeliveryParams const&), (override)); - MOCK_METHOD(std::unique_ptr, clone, - (TableResourceLabels const& resource_labels, - TableDataLabels const& data_labels), + MOCK_METHOD((std::unique_ptr), clone, + (TableResourceLabels const&, TableDataLabels const&), (const, override)); }; diff --git a/google/cloud/bigtable/internal/partial_result_set_source_test.cc b/google/cloud/bigtable/internal/partial_result_set_source_test.cc index fb70e23e29713..5625a31354ba3 100644 --- a/google/cloud/bigtable/internal/partial_result_set_source_test.cc +++ b/google/cloud/bigtable/internal/partial_result_set_source_test.cc @@ -21,6 +21,7 @@ #include "google/cloud/bigtable/value.h" #ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS #include "google/cloud/bigtable/internal/metrics.h" +#include "google/cloud/bigtable/internal/table_schema_metrics.h" #include "google/cloud/testing_util/fake_clock.h" #endif #include "google/cloud/options.h" @@ -88,7 +89,7 @@ MATCHER_P(IsValidAndEquals, expected, } #ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS -class MockMetric : public Metric { +class MockMetric : public TableSchemaMetric { public: MOCK_METHOD(void, PreCall, (opentelemetry::context::Context const&, PreCallParams const&), @@ -108,7 +109,7 @@ class MockMetric : public Metric { (opentelemetry::context::Context const&, ElementDeliveryParams const&), (override)); - MOCK_METHOD(std::unique_ptr, clone, + MOCK_METHOD(std::unique_ptr, clone, (TableResourceLabels const& resource_labels, TableDataLabels const& data_labels), (const, override)); diff --git a/google/cloud/bigtable/internal/table_schema_metrics.cc b/google/cloud/bigtable/internal/table_schema_metrics.cc new file mode 100644 index 0000000000000..9f597df715dce --- /dev/null +++ b/google/cloud/bigtable/internal/table_schema_metrics.cc @@ -0,0 +1,568 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS + +#include "google/cloud/bigtable/internal/table_schema_metrics.h" +#include "google/cloud/bigtable/version.h" +#include "google/cloud/internal/opentelemetry.h" +#include "absl/strings/ascii.h" +#include "absl/strings/charconv.h" +#include "absl/strings/escaping.h" +#include "absl/strings/match.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_split.h" +#include "absl/strings/strip.h" +#include +#include +#include +#include +#include +#include + +namespace google { +namespace cloud { +namespace bigtable_internal { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN +namespace { +auto constexpr kMeterInstrumentationScopeVersion = "v1"; +} // namespace + +// TODO(#15329): Refactor how we're handling different data labels for +// the various RPCs. Adding a function to each metric type to add its +// TableDataLabels to the map should be more performant than performing a +// set_difference every time. +LabelMap IntoLabelMap(TableResourceLabels const& r, TableDataLabels const& d, + std::set const& filtered_data_labels, + std::optional const& peer_info_labels) { + LabelMap labels = { + {"project_id", r.project_id}, + {"instance", r.instance}, + {"table", r.table}, + {"cluster", r.cluster.empty() ? "" : r.cluster}, + {"zone", r.zone.empty() ? "global" : r.zone}}; + + if (peer_info_labels) { + labels.insert({ + {"transport_type", peer_info_labels->transport_type}, + {"transport_region", peer_info_labels->transport_region}, + {"transport_subzone", peer_info_labels->transport_subzone}, + }); + } + + std::map data = {{ + {"method", d.method}, + {"streaming", d.streaming}, + {"client_name", d.client_name}, + {"client_uid", d.client_uid}, + {"app_profile", d.app_profile}, + {"status", d.status}, + }}; + + if (filtered_data_labels.empty()) { + labels.insert(data.begin(), data.end()); + return labels; + } + + struct Compare { + bool operator()(std::pair const& a, + std::string const& b) { + return a.first < b; + } + + bool operator()(std::string const& a, + std::pair const& b) { + return a < b.first; + } + }; + + std::set_difference(data.begin(), data.end(), filtered_data_labels.begin(), + filtered_data_labels.end(), + std::inserter(labels, labels.begin()), Compare()); + + return labels; +} + +bool HasServerTiming(grpc::ClientContext const& client_context) { + auto const& initial_metadata = client_context.GetServerInitialMetadata(); + auto it = initial_metadata.find("server-timing"); + return it != initial_metadata.end(); +} + +bool IsConnectivityError(google::cloud::Status const& status, + grpc::ClientContext const& client_context) { + return status.code() != google::cloud::StatusCode::kDeadlineExceeded && + !HasServerTiming(client_context); +} + +std::optional +GetResponseParamsFromTrailingMetadata( + grpc::ClientContext const& client_context) { + auto metadata = client_context.GetServerTrailingMetadata(); + auto iter = metadata.find("x-goog-ext-425905942-bin"); + if (iter == metadata.end()) return std::nullopt; + google::bigtable::v2::ResponseParams p; + // The value for this key should always be the same in a response, so we + // return the first value we find. + std::string value{iter->second.data(), iter->second.size()}; + if (p.ParseFromString(value)) return p; + return std::nullopt; +} + +std::optional GetPeerInfoFromServerMetadata( + grpc::ClientContext const& client_context) { + // The peer info is sent in the initial metadata and encoded in WebSafeBase64. + std::string decoded; + auto const& init_metadata = client_context.GetServerInitialMetadata(); + auto iter_init = init_metadata.find("bigtable-peer-info"); + if (iter_init == init_metadata.end() || + !absl::WebSafeBase64Unescape( + std::string_view{iter_init->second.data(), iter_init->second.size()}, + &decoded)) { + // Find it in trailing metadata if not found in initial metadata or failed + // to decode. + auto const& trailing_metadata = client_context.GetServerTrailingMetadata(); + auto iter_trailing = trailing_metadata.find("bigtable-peer-info"); + if (iter_trailing == trailing_metadata.end() || + !absl::WebSafeBase64Unescape( + std::string_view{iter_trailing->second.data(), + iter_trailing->second.size()}, + &decoded)) { + return std::nullopt; + } + } + google::bigtable::v2::PeerInfo p; + if (p.ParseFromString(decoded)) return p; + return std::nullopt; +} + +std::optional GetServerLatencyFromInitialMetadata( + grpc::ClientContext const& client_context) { + auto const& initial_metadata = client_context.GetServerInitialMetadata(); + auto it = initial_metadata.find("server-timing"); + if (it == initial_metadata.end()) { + return std::nullopt; + } + + std::string_view value(it->second.data(), it->second.length()); + + for (std::string_view entry : absl::StrSplit(value, ',')) { + entry = absl::StripAsciiWhitespace(entry); + std::vector parts = absl::StrSplit(entry, ';'); + if (parts.empty()) { + continue; + } + + std::string_view metric_name = absl::StripAsciiWhitespace(parts[0]); + if (metric_name == "gfet4t7") { + // Look for the "dur" parameter within its parts. + for (size_t i = 1; i < parts.size(); ++i) { + std::string_view param = absl::StripAsciiWhitespace(parts[i]); + if (absl::ConsumePrefix(¶m, "dur=")) { + double dur_value; + auto result = absl::from_chars( + param.data(), param.data() + param.size(), dur_value); + if (result.ec == std::errc()) { + return dur_value; + } + return std::nullopt; + } + } + } + } + + return std::nullopt; +} + +OperationLatency::OperationLatency( + std::string const& instrumentation_scope, + opentelemetry::nostd::shared_ptr< + opentelemetry::metrics::MeterProvider> const& provider) + : operation_latencies_(provider + ->GetMeter(instrumentation_scope, + kMeterInstrumentationScopeVersion) + ->CreateDoubleHistogram("operation_latencies")) { +} + +void OperationLatency::PreCall(opentelemetry::context::Context const&, + PreCallParams const& p) { + if (p.first_attempt) { + operation_start_ = p.attempt_start; + } +} + +void OperationLatency::PostCall(opentelemetry::context::Context const&, + grpc::ClientContext const& client_context, + PostCallParams const&) { + auto response_params = GetResponseParamsFromTrailingMetadata(client_context); + if (response_params) { + resource_labels_.cluster = response_params->cluster_id(); + resource_labels_.zone = response_params->zone_id(); + } +} + +void OperationLatency::OnDone(opentelemetry::context::Context const& context, + OnDoneParams const& p) { + data_labels_.status = StatusCodeToString(p.operation_status.code()); + auto operation_elapsed = std::chrono::duration_cast( + p.operation_end - operation_start_); + operation_latencies_->Record(operation_elapsed.count(), + IntoLabelMap(resource_labels_, data_labels_), + context); +} + +std::unique_ptr OperationLatency::clone( + TableResourceLabels const& resource_labels, + TableDataLabels const& data_labels) const { + auto m = std::make_unique(*this); + m->resource_labels_ = resource_labels; + m->data_labels_ = data_labels; + return m; +} + +AttemptLatency::AttemptLatency( + std::string const& instrumentation_scope, + opentelemetry::nostd::shared_ptr< + opentelemetry::metrics::MeterProvider> const& provider) + : attempt_latencies_(provider + ->GetMeter(instrumentation_scope, + kMeterInstrumentationScopeVersion) + ->CreateDoubleHistogram("attempt_latencies")) {} + +void AttemptLatency::PreCall(opentelemetry::context::Context const&, + PreCallParams const& p) { + attempt_start_ = p.attempt_start; +} + +void AttemptLatency::PostCall(opentelemetry::context::Context const& context, + grpc::ClientContext const& client_context, + PostCallParams const& p) { + data_labels_.status = StatusCodeToString(p.attempt_status.code()); + auto response_params = GetResponseParamsFromTrailingMetadata(client_context); + if (response_params) { + resource_labels_.cluster = response_params->cluster_id(); + resource_labels_.zone = response_params->zone_id(); + } + + auto attempt_elapsed = std::chrono::duration_cast( + p.attempt_end - attempt_start_); + attempt_latencies_->Record(attempt_elapsed.count(), + IntoLabelMap(resource_labels_, data_labels_), + context); +} + +std::unique_ptr AttemptLatency::clone( + TableResourceLabels const& resource_labels, + TableDataLabels const& data_labels) const { + auto m = std::make_unique(*this); + m->resource_labels_ = resource_labels; + m->data_labels_ = data_labels; + return m; +} + +AttemptLatency2::AttemptLatency2( + std::string const& instrumentation_scope, + opentelemetry::nostd::shared_ptr< + opentelemetry::metrics::MeterProvider> const& provider) + : attempt_latencies2_(provider + ->GetMeter(instrumentation_scope, + kMeterInstrumentationScopeVersion) + ->CreateDoubleHistogram("attempt_latencies2")) {} + +void AttemptLatency2::PreCall(opentelemetry::context::Context const&, + PreCallParams const& p) { + attempt_start_ = p.attempt_start; +} + +void AttemptLatency2::PostCall(opentelemetry::context::Context const& context, + grpc::ClientContext const& client_context, + PostCallParams const& p) { + data_labels_.status = StatusCodeToString(p.attempt_status.code()); + std::optional response_params = + GetResponseParamsFromTrailingMetadata(client_context); + if (response_params) { + resource_labels_.cluster = response_params->cluster_id(); + resource_labels_.zone = response_params->zone_id(); + } + + std::optional peer_info = + GetPeerInfoFromServerMetadata(client_context); + if (peer_info) { + peer_info_labels_.transport_type = absl::AsciiStrToLower( + google::bigtable::v2::PeerInfo::TransportType_Name( + peer_info->transport_type())); + peer_info_labels_.transport_region = + absl::AsciiStrToLower(peer_info->application_frontend_region()); + peer_info_labels_.transport_subzone = + absl::AsciiStrToLower(peer_info->application_frontend_subzone()); + } else { + peer_info_labels_.transport_type = absl::AsciiStrToLower( + google::bigtable::v2::PeerInfo::TransportType_Name( + google::bigtable::v2::PeerInfo::TRANSPORT_TYPE_UNKNOWN)); + peer_info_labels_.transport_region.clear(); + peer_info_labels_.transport_subzone.clear(); + } + + auto attempt_elapsed = std::chrono::duration_cast( + p.attempt_end - attempt_start_); + attempt_latencies2_->Record( + attempt_elapsed.count(), + IntoLabelMap(resource_labels_, data_labels_, {}, peer_info_labels_), + context); +} + +std::unique_ptr AttemptLatency2::clone( + TableResourceLabels const& resource_labels, + TableDataLabels const& data_labels) const { + auto m = std::make_unique(*this); + m->resource_labels_ = resource_labels; + m->data_labels_ = data_labels; + return m; +} + +RetryCount::RetryCount( + std::string const& instrumentation_scope, + opentelemetry::nostd::shared_ptr< + opentelemetry::metrics::MeterProvider> const& provider) + : retry_count_( + provider + ->GetMeter(instrumentation_scope, + kMeterInstrumentationScopeVersion) + ->CreateUInt64Counter( + "retry_count", + "The number of additional RPCs sent by the client.")) {} + +void RetryCount::PreCall(opentelemetry::context::Context const&, + PreCallParams const& p) { + if (!p.first_attempt) { + num_retries_++; + } +} + +void RetryCount::PostCall(opentelemetry::context::Context const&, + grpc::ClientContext const& client_context, + PostCallParams const&) { + auto response_params = GetResponseParamsFromTrailingMetadata(client_context); + if (response_params) { + resource_labels_.cluster = response_params->cluster_id(); + resource_labels_.zone = response_params->zone_id(); + } +} + +void RetryCount::OnDone(opentelemetry::context::Context const& context, + OnDoneParams const& p) { + data_labels_.status = StatusCodeToString(p.operation_status.code()); + retry_count_->Add(num_retries_, + IntoLabelMap(resource_labels_, data_labels_, + std::set{"streaming"}), + context); +} + +std::unique_ptr RetryCount::clone( + TableResourceLabels const& resource_labels, + TableDataLabels const& data_labels) const { + auto m = std::make_unique(*this); + m->resource_labels_ = resource_labels; + m->data_labels_ = data_labels; + return m; +} + +FirstResponseLatency::FirstResponseLatency( + std::string const& instrumentation_scope, + opentelemetry::nostd::shared_ptr< + opentelemetry::metrics::MeterProvider> const& provider) + : first_response_latencies_( + provider + ->GetMeter(instrumentation_scope, + kMeterInstrumentationScopeVersion) + ->CreateDoubleHistogram("first_response_latencies")) {} + +void FirstResponseLatency::PreCall(opentelemetry::context::Context const&, + PreCallParams const& p) { + if (p.first_attempt) { + operation_start_ = p.attempt_start; + } +} + +void FirstResponseLatency::PostCall(opentelemetry::context::Context const&, + grpc::ClientContext const& client_context, + PostCallParams const&) { + auto response_params = GetResponseParamsFromTrailingMetadata(client_context); + if (response_params) { + resource_labels_.cluster = response_params->cluster_id(); + resource_labels_.zone = response_params->zone_id(); + } +} + +void FirstResponseLatency::ElementDelivery( + opentelemetry::context::Context const&, ElementDeliveryParams const& p) { + if (p.first_response) { + first_response_latency_ = std::chrono::duration_cast( + p.element_delivery - operation_start_); + } +} + +void FirstResponseLatency::OnDone( + opentelemetry::context::Context const& context, OnDoneParams const& p) { + if (first_response_latency_) { + data_labels_.status = StatusCodeToString(p.operation_status.code()); + auto m = IntoLabelMap(resource_labels_, data_labels_, + std::set{"streaming"}); + first_response_latencies_->Record(first_response_latency_->count(), + std::move(m), context); + } +} + +std::unique_ptr FirstResponseLatency::clone( + TableResourceLabels const& resource_labels, + TableDataLabels const& data_labels) const { + auto m = std::make_unique(*this); + m->resource_labels_ = resource_labels; + m->data_labels_ = data_labels; + return m; +} + +ServerLatency::ServerLatency( + std::string const& instrumentation_scope, + opentelemetry::nostd::shared_ptr< + opentelemetry::metrics::MeterProvider> const& provider) + : server_latencies_(provider + ->GetMeter(instrumentation_scope, + kMeterInstrumentationScopeVersion) + ->CreateDoubleHistogram("server_latencies")) {} + +void ServerLatency::PostCall(opentelemetry::context::Context const& context, + grpc::ClientContext const& client_context, + PostCallParams const& p) { + auto response_params = GetResponseParamsFromTrailingMetadata(client_context); + if (response_params) { + resource_labels_.cluster = response_params->cluster_id(); + resource_labels_.zone = response_params->zone_id(); + } + data_labels_.status = StatusCodeToString(p.attempt_status.code()); + auto server_latency = GetServerLatencyFromInitialMetadata(client_context); + if (server_latency) { + auto m = IntoLabelMap(resource_labels_, data_labels_); + server_latencies_->Record(*server_latency, std::move(m), context); + } +} + +std::unique_ptr ServerLatency::clone( + TableResourceLabels const& resource_labels, + TableDataLabels const& data_labels) const { + auto m = std::make_unique(*this); + m->resource_labels_ = resource_labels; + m->data_labels_ = data_labels; + return m; +} + +ApplicationBlockingLatency::ApplicationBlockingLatency( + std::string const& instrumentation_scope, + opentelemetry::nostd::shared_ptr< + opentelemetry::metrics::MeterProvider> const& provider) + : application_blocking_latencies_( + provider + ->GetMeter(instrumentation_scope, + kMeterInstrumentationScopeVersion) + ->CreateDoubleHistogram("application_latencies")) {} + +void ApplicationBlockingLatency::ElementDelivery( + opentelemetry::context::Context const&, ElementDeliveryParams const& p) { + element_delivery_time_ = p.element_delivery; +} + +void ApplicationBlockingLatency::ElementRequest( + opentelemetry::context::Context const&, ElementRequestParams const& p) { + auto application_blocking_latency = + std::chrono::duration_cast(p.element_request - + element_delivery_time_); + pending_latencies_.push_back(application_blocking_latency); +} + +void ApplicationBlockingLatency::PostCall( + opentelemetry::context::Context const&, + grpc::ClientContext const& client_context, PostCallParams const&) { + auto response_params = GetResponseParamsFromTrailingMetadata(client_context); + if (response_params) { + resource_labels_.cluster = response_params->cluster_id(); + resource_labels_.zone = response_params->zone_id(); + } +} + +void ApplicationBlockingLatency::OnDone( + opentelemetry::context::Context const& context, OnDoneParams const&) { + auto m = IntoLabelMap(resource_labels_, data_labels_, + std::set{"streaming", "status"}); + for (auto const& latency : pending_latencies_) { + application_blocking_latencies_->Record(latency.count(), m, context); + } +} + +std::unique_ptr ApplicationBlockingLatency::clone( + TableResourceLabels const& resource_labels, + TableDataLabels const& data_labels) const { + auto m = std::make_unique(*this); + m->resource_labels_ = resource_labels; + m->data_labels_ = data_labels; + return m; +} + +ConnectivityErrorCount::ConnectivityErrorCount( + std::string const& instrumentation_scope, + opentelemetry::nostd::shared_ptr< + opentelemetry::metrics::MeterProvider> const& provider) + : connectivity_error_count_( + provider + ->GetMeter(instrumentation_scope, + kMeterInstrumentationScopeVersion) + ->CreateUInt64Counter("connectivity_error_count")) {} + +void ConnectivityErrorCount::PostCall(opentelemetry::context::Context const&, + grpc::ClientContext const& client_context, + PostCallParams const& p) { + auto response_params = GetResponseParamsFromTrailingMetadata(client_context); + if (response_params) { + resource_labels_.cluster = response_params->cluster_id(); + resource_labels_.zone = response_params->zone_id(); + } + auto const& status = p.attempt_status; + data_labels_.status = StatusCodeToString(status.code()); + if (resource_labels_.cluster.empty() || resource_labels_.zone.empty() || + IsConnectivityError(status, client_context)) { + ++num_errors_; + } +} + +void ConnectivityErrorCount::OnDone( + opentelemetry::context::Context const& context, OnDoneParams const&) { + auto m = IntoLabelMap(resource_labels_, data_labels_, + std::set{"streaming"}); + connectivity_error_count_->Add(num_errors_, std::move(m), context); +} + +std::unique_ptr ConnectivityErrorCount::clone( + TableResourceLabels const& resource_labels, + TableDataLabels const& data_labels) const { + auto m = std::make_unique(*this); + m->resource_labels_ = resource_labels; + m->data_labels_ = data_labels; + return m; +} + +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace bigtable_internal +} // namespace cloud +} // namespace google + +#endif // GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS diff --git a/google/cloud/bigtable/internal/table_schema_metrics.h b/google/cloud/bigtable/internal/table_schema_metrics.h new file mode 100644 index 0000000000000..183b3af5fb434 --- /dev/null +++ b/google/cloud/bigtable/internal/table_schema_metrics.h @@ -0,0 +1,301 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_BIGTABLE_INTERNAL_TABLE_SCHEMA_METRICS_H +#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_BIGTABLE_INTERNAL_TABLE_SCHEMA_METRICS_H + +#ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS + +#include "google/cloud/bigtable/internal/metrics.h" +#include "google/cloud/bigtable/internal/operation_context.h" +#include "google/cloud/bigtable/version.h" +#include "google/cloud/options.h" +#include "google/cloud/status.h" +#include "google/bigtable/v2/peer_info.pb.h" +#include "google/bigtable/v2/response_params.pb.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace google { +namespace cloud { +namespace bigtable_internal { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN + +struct TableResourceLabels { + std::string project_id; + std::string instance; + std::string table; + std::string cluster; + std::string zone; +}; + +struct TableDataLabels { + std::string method; + std::string streaming; + std::string client_name; + std::string client_uid; + std::string app_profile; + std::string status; +}; + +// Labels populated from the peer info metadata. +struct PeerInfoLabels { + std::string transport_type; + std::string transport_region; + std::string transport_subzone; +}; + +using LabelMap = std::unordered_map; +// `peer_info_labels` is optional because only AttemptLatency2 populates it. +LabelMap IntoLabelMap( + TableResourceLabels const& r, TableDataLabels const& d, + std::set const& filtered_data_labels = {}, + std::optional const& peer_info_labels = std::nullopt); + +bool HasServerTiming(grpc::ClientContext const& client_context); +bool IsConnectivityError(google::cloud::Status const& status, + grpc::ClientContext const& client_context); +std::optional +GetResponseParamsFromTrailingMetadata( + grpc::ClientContext const& client_context); +// Retrieve the peer info from server headers or trailers. Returns nullopt if +// not found or decoding or parsing fails. +std::optional GetPeerInfoFromServerMetadata( + grpc::ClientContext const& client_context); +std::optional GetServerLatencyFromInitialMetadata( + grpc::ClientContext const& client_context); + +class TableSchemaMetric : public Metric { + public: + MetricSchema schema() const final { return MetricSchema::kTable; } + virtual std::unique_ptr clone( + TableResourceLabels const& resource_labels, + TableDataLabels const& data_labels) const = 0; +}; + +class OperationLatency : public TableSchemaMetric { + public: + explicit OperationLatency( + std::string const& instrumentation_scope, + opentelemetry::nostd::shared_ptr< + opentelemetry::metrics::MeterProvider> const& provider); + void PreCall(opentelemetry::context::Context const&, + PreCallParams const& p) override; + void PostCall(opentelemetry::context::Context const& context, + grpc::ClientContext const& client_context, + PostCallParams const& p) override; + void OnDone(opentelemetry::context::Context const& context, + OnDoneParams const& p) override; + std::unique_ptr clone( + TableResourceLabels const& resource_labels, + TableDataLabels const& data_labels) const override; + + private: + TableResourceLabels resource_labels_; + TableDataLabels data_labels_; + opentelemetry::nostd::shared_ptr> + operation_latencies_; + OperationContext::Clock::time_point operation_start_; +}; + +class AttemptLatency : public TableSchemaMetric { + public: + AttemptLatency(std::string const& instrumentation_scope, + opentelemetry::nostd::shared_ptr< + opentelemetry::metrics::MeterProvider> const& provider); + void PreCall(opentelemetry::context::Context const&, + PreCallParams const& p) override; + void PostCall(opentelemetry::context::Context const& context, + grpc::ClientContext const& client_context, + PostCallParams const& p) override; + std::unique_ptr clone( + TableResourceLabels const& resource_labels, + TableDataLabels const& data_labels) const override; + + private: + TableResourceLabels resource_labels_; + TableDataLabels data_labels_; + opentelemetry::nostd::shared_ptr> + attempt_latencies_; + OperationContext::Clock::time_point attempt_start_; +}; + +// Similar to AttemptLatency and also populates the peer info. +class AttemptLatency2 : public TableSchemaMetric { + public: + AttemptLatency2(std::string const& instrumentation_scope, + opentelemetry::nostd::shared_ptr< + opentelemetry::metrics::MeterProvider> const& provider); + void PreCall(opentelemetry::context::Context const&, + PreCallParams const& p) override; + void PostCall(opentelemetry::context::Context const& context, + grpc::ClientContext const& client_context, + PostCallParams const& p) override; + std::unique_ptr clone( + TableResourceLabels const& resource_labels, + TableDataLabels const& data_labels) const override; + + private: + TableResourceLabels resource_labels_; + TableDataLabels data_labels_; + PeerInfoLabels peer_info_labels_; + opentelemetry::nostd::shared_ptr> + attempt_latencies2_; + OperationContext::Clock::time_point attempt_start_; +}; + +class RetryCount : public TableSchemaMetric { + public: + RetryCount(std::string const& instrumentation_scope, + opentelemetry::nostd::shared_ptr< + opentelemetry::metrics::MeterProvider> const& provider); + void PreCall(opentelemetry::context::Context const&, + PreCallParams const&) override; + void PostCall(opentelemetry::context::Context const& context, + grpc::ClientContext const& client_context, + PostCallParams const& p) override; + void OnDone(opentelemetry::context::Context const& context, + OnDoneParams const& p) override; + std::unique_ptr clone( + TableResourceLabels const& resource_labels, + TableDataLabels const& data_labels) const override; + + private: + TableResourceLabels resource_labels_; + TableDataLabels data_labels_; + std::uint64_t num_retries_ = 0; + opentelemetry::nostd::shared_ptr< + opentelemetry::metrics::Counter> + retry_count_; +}; + +class FirstResponseLatency : public TableSchemaMetric { + public: + FirstResponseLatency( + std::string const& instrumentation_scope, + opentelemetry::nostd::shared_ptr< + opentelemetry::metrics::MeterProvider> const& provider); + void PreCall(opentelemetry::context::Context const&, + PreCallParams const& p) override; + void PostCall(opentelemetry::context::Context const& context, + grpc::ClientContext const& client_context, + PostCallParams const& p) override; + void ElementDelivery(opentelemetry::context::Context const&, + ElementDeliveryParams const&) override; + void OnDone(opentelemetry::context::Context const& context, + OnDoneParams const& p) override; + + std::unique_ptr clone( + TableResourceLabels const& resource_labels, + TableDataLabels const& data_labels) const override; + + private: + TableResourceLabels resource_labels_; + TableDataLabels data_labels_; + opentelemetry::nostd::shared_ptr> + first_response_latencies_; + OperationContext::Clock::time_point operation_start_; + std::optional first_response_latency_; +}; + +class ServerLatency : public TableSchemaMetric { + public: + ServerLatency(std::string const& instrumentation_scope, + opentelemetry::nostd::shared_ptr< + opentelemetry::metrics::MeterProvider> const& provider); + void PostCall(opentelemetry::context::Context const& context, + grpc::ClientContext const& client_context, + PostCallParams const& p) override; + + std::unique_ptr clone( + TableResourceLabels const& resource_labels, + TableDataLabels const& data_labels) const override; + + private: + TableResourceLabels resource_labels_; + TableDataLabels data_labels_; + opentelemetry::nostd::shared_ptr> + server_latencies_; +}; + +class ApplicationBlockingLatency : public TableSchemaMetric { + public: + ApplicationBlockingLatency( + std::string const& instrumentation_scope, + opentelemetry::nostd::shared_ptr< + opentelemetry::metrics::MeterProvider> const& provider); + void PostCall(opentelemetry::context::Context const& context, + grpc::ClientContext const& client_context, + PostCallParams const& p) override; + void ElementDelivery(opentelemetry::context::Context const&, + ElementDeliveryParams const&) override; + void ElementRequest(opentelemetry::context::Context const&, + ElementRequestParams const&) override; + void OnDone(opentelemetry::context::Context const& context, + OnDoneParams const& p) override; + + std::unique_ptr clone( + TableResourceLabels const& resource_labels, + TableDataLabels const& data_labels) const override; + + private: + TableResourceLabels resource_labels_; + TableDataLabels data_labels_; + opentelemetry::nostd::shared_ptr> + application_blocking_latencies_; + OperationContext::Clock::time_point element_delivery_time_; + std::vector pending_latencies_; +}; + +class ConnectivityErrorCount : public TableSchemaMetric { + public: + ConnectivityErrorCount( + std::string const& instrumentation_scope, + opentelemetry::nostd::shared_ptr< + opentelemetry::metrics::MeterProvider> const& provider); + void PostCall(opentelemetry::context::Context const&, + grpc::ClientContext const& client_context, + PostCallParams const& p) override; + void OnDone(opentelemetry::context::Context const& context, + OnDoneParams const&) override; + std::unique_ptr clone( + TableResourceLabels const& resource_labels, + TableDataLabels const& data_labels) const override; + + private: + TableResourceLabels resource_labels_; + TableDataLabels data_labels_; + std::uint64_t num_errors_ = 0; + opentelemetry::nostd::shared_ptr< + opentelemetry::metrics::Counter> + connectivity_error_count_; +}; + +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace bigtable_internal +} // namespace cloud +} // namespace google + +#endif // GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS + +#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_BIGTABLE_INTERNAL_TABLE_SCHEMA_METRICS_H diff --git a/google/cloud/bigtable/internal/table_schema_metrics_test.cc b/google/cloud/bigtable/internal/table_schema_metrics_test.cc new file mode 100644 index 0000000000000..66840f990ba0e --- /dev/null +++ b/google/cloud/bigtable/internal/table_schema_metrics_test.cc @@ -0,0 +1,2491 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Getting vcpkg, cmake, and opentelemetry to actually build with the STL +// enabled is more trouble than it's worth for these unit tests. +#if !defined(_WIN32) && !defined(__MACH__) +#ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS + +#include "google/cloud/bigtable/internal/table_schema_metrics.h" +#include "google/cloud/bigtable/options.h" +#include "google/cloud/bigtable/version.h" +#include "google/cloud/testing_util/fake_clock.h" +#include "google/cloud/testing_util/validate_metadata.h" +#include "absl/strings/escaping.h" +#include "google/bigtable/v2/peer_info.pb.h" +#include +#include + +namespace google { +namespace cloud { +namespace bigtable_internal { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN +namespace { + +using ::google::cloud::testing_util::FakeSteadyClock; +using ::google::cloud::testing_util::SetServerMetadata; + +using ::testing::A; +using ::testing::Eq; +using ::testing::Pair; +using ::testing::UnorderedElementsAre; + +using ::opentelemetry::metrics::Counter; +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 +using ::opentelemetry::metrics::Gauge; +#endif +using ::opentelemetry::metrics::Histogram; +using ::opentelemetry::metrics::ObservableInstrument; +using ::opentelemetry::metrics::UpDownCounter; + +template +class MockHistogram : public opentelemetry::metrics::Histogram { + public: +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + MOCK_METHOD(void, Record, // NOLINT(bugprone-exception-escape) + (T value), (noexcept, override)); + MOCK_METHOD(void, Record, // NOLINT(bugprone-exception-escape) + (T, opentelemetry::common::KeyValueIterable const&), + (noexcept, override)); + +#endif + MOCK_METHOD(void, Record, // NOLINT(bugprone-exception-escape) + (T value, opentelemetry::context::Context const& context), + (noexcept, override)); + MOCK_METHOD(void, Record, // NOLINT(bugprone-exception-escape) + (T value, + opentelemetry::common::KeyValueIterable const& attributes, + opentelemetry::context::Context const& context), + (noexcept, override)); +}; + +template +class MockCounter : public opentelemetry::metrics::Counter { + public: + MOCK_METHOD(void, Add, // NOLINT(bugprone-exception-escape) + (T value), (noexcept, override)); + MOCK_METHOD(void, Add, // NOLINT(bugprone-exception-escape) + (T value, opentelemetry::context::Context const&), + (noexcept, override)); + MOCK_METHOD(void, Add, // NOLINT(bugprone-exception-escape) + (T value, opentelemetry::common::KeyValueIterable const&), + (noexcept, override)); + MOCK_METHOD(void, Add, // NOLINT(bugprone-exception-escape) + (T value, opentelemetry::common::KeyValueIterable const&, + opentelemetry::context::Context const&), + (noexcept, override)); +}; + +class MockMeter : public opentelemetry::metrics::Meter { + public: + MOCK_METHOD(opentelemetry::nostd::unique_ptr>, + CreateUInt64Counter, // NOLINT(bugprone-exception-escape) + (opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view), + (noexcept, override)); + + MOCK_METHOD(opentelemetry::nostd::unique_ptr>, + CreateDoubleCounter, // NOLINT(bugprone-exception-escape) + (opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view), + (noexcept, override)); + + MOCK_METHOD( + opentelemetry::nostd::shared_ptr, + CreateInt64ObservableCounter, // NOLINT(bugprone-exception-escape) + (opentelemetry::nostd::string_view, opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view), + (noexcept, override)); + + MOCK_METHOD( + opentelemetry::nostd::shared_ptr, + CreateDoubleObservableCounter, // NOLINT(bugprone-exception-escape) + (opentelemetry::nostd::string_view, opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view), + (noexcept, override)); + + MOCK_METHOD(opentelemetry::nostd::unique_ptr>, + CreateUInt64Histogram, // NOLINT(bugprone-exception-escape) + (opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view), + (noexcept, override)); + + MOCK_METHOD(opentelemetry::nostd::unique_ptr>, + CreateDoubleHistogram, // NOLINT(bugprone-exception-escape) + (opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view), + (noexcept, override)); + +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + MOCK_METHOD(opentelemetry::nostd::unique_ptr>, + CreateInt64Gauge, // NOLINT(bugprone-exception-escape) + (opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view), + (noexcept, override)); + + MOCK_METHOD(opentelemetry::nostd::unique_ptr>, + CreateDoubleGauge, // NOLINT(bugprone-exception-escape) + (opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view), + (noexcept, override)); + + MOCK_METHOD(uintptr_t, + RegisterCallback, // NOLINT(bugprone-exception-escape) + (opentelemetry::metrics::MultiObservableCallbackPtr, void*, + opentelemetry::nostd::span< + opentelemetry::metrics::ObservableInstrument*>), + (noexcept, override)); + + MOCK_METHOD(void, + DeregisterCallback, // NOLINT(bugprone-exception-escape) + (uintptr_t), (noexcept, override)); +#endif + + MOCK_METHOD(opentelemetry::nostd::shared_ptr, + CreateInt64ObservableGauge, // NOLINT(bugprone-exception-escape) + (opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view), + (noexcept, override)); + + MOCK_METHOD(opentelemetry::nostd::shared_ptr, + CreateDoubleObservableGauge, // NOLINT(bugprone-exception-escape) + (opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view), + (noexcept, override)); + + MOCK_METHOD(opentelemetry::nostd::unique_ptr>, + CreateInt64UpDownCounter, // NOLINT(bugprone-exception-escape) + (opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view), + (noexcept, override)); + + MOCK_METHOD(opentelemetry::nostd::unique_ptr>, + CreateDoubleUpDownCounter, // NOLINT(bugprone-exception-escape) + (opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view), + (noexcept, override)); + + MOCK_METHOD( + opentelemetry::nostd::shared_ptr, + CreateInt64ObservableUpDownCounter, // NOLINT(bugprone-exception-escape) + (opentelemetry::nostd::string_view, opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view), + (noexcept, override)); + + MOCK_METHOD( + opentelemetry::nostd::shared_ptr, + CreateDoubleObservableUpDownCounter, // NOLINT(bugprone-exception-escape) + (opentelemetry::nostd::string_view, opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view), + (noexcept, override)); +}; + +class MockMeterProvider : public opentelemetry::metrics::MeterProvider { + public: +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + MOCK_METHOD(opentelemetry::nostd::shared_ptr, + GetMeter, // NOLINT(bugprone-exception-escape) + (opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view, + opentelemetry::common::KeyValueIterable const*), + (noexcept, override)); + + MOCK_METHOD(void, RemoveMeter, // NOLINT(bugprone-exception-escape) + (opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view), + (noexcept, override)); + +#else + MOCK_METHOD(opentelemetry::nostd::shared_ptr, + GetMeter, // NOLINT(bugprone-exception-escape) + (opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view), + (noexcept, override)); +#endif +}; + +TEST(LabelMap, IntoLabelMap) { + TableResourceLabels r{"my-project", "my-instance", "my-table", "my-cluster", + "my-zone"}; + TableDataLabels d{"my-method", "my-streaming", "my-client-name", + "my-client-uid", "my-app-profile", "my-status"}; + auto label_map = IntoLabelMap(r, d); + EXPECT_THAT( + label_map, + UnorderedElementsAre( + Pair("project_id", "my-project"), Pair("instance", "my-instance"), + Pair("table", "my-table"), Pair("cluster", "my-cluster"), + Pair("zone", "my-zone"), Pair("method", "my-method"), + Pair("streaming", "my-streaming"), + Pair("client_name", "my-client-name"), + Pair("client_uid", "my-client-uid"), + Pair("app_profile", "my-app-profile"), Pair("status", "my-status"))); +} + +TEST(LabelMap, IntoLabelMapWithDefaults) { + TableResourceLabels r{"my-project", "my-instance", "my-table", "", ""}; + TableDataLabels d{"my-method", "my-streaming", "my-client-name", + "my-client-uid", "my-app-profile", "my-status"}; + auto label_map = IntoLabelMap(r, d); + EXPECT_THAT( + label_map, + UnorderedElementsAre( + Pair("project_id", "my-project"), Pair("instance", "my-instance"), + Pair("table", "my-table"), Pair("cluster", ""), + Pair("zone", "global"), Pair("method", "my-method"), + Pair("streaming", "my-streaming"), + Pair("client_name", "my-client-name"), + Pair("client_uid", "my-client-uid"), + Pair("app_profile", "my-app-profile"), Pair("status", "my-status"))); +} + +TEST(GetResponseParamsFromMetadata, NonEmptyHeader) { + google::bigtable::v2::ResponseParams expected_response_params; + expected_response_params.set_cluster_id("my-cluster"); + expected_response_params.set_zone_id("my-zone"); + grpc::ClientContext client_context; + RpcMetadata server_metadata; + server_metadata.trailers.emplace( + "x-goog-ext-425905942-bin", expected_response_params.SerializeAsString()); + SetServerMetadata(client_context, server_metadata); + + auto result = GetResponseParamsFromTrailingMetadata(client_context); + ASSERT_TRUE(result); + EXPECT_THAT(result->cluster_id(), Eq("my-cluster")); + EXPECT_THAT(result->zone_id(), Eq("my-zone")); +} + +TEST(GetResponseParamsFromMetadata, EmptyHeader) { + grpc::ClientContext client_context; + RpcMetadata server_metadata; + SetServerMetadata(client_context, server_metadata); + + auto result = GetResponseParamsFromTrailingMetadata(client_context); + EXPECT_FALSE(result); +} + +struct CreateServerMetadataOptions { + bool include_peer_info = true; + bool include_response_params = true; +}; + +// Helper function to create server metadata by populating the initial and +// trailers based on the options. +void CreateServerMetadata(grpc::ClientContext& client_context, + CreateServerMetadataOptions const& options = + CreateServerMetadataOptions{}) { + RpcMetadata server_metadata; + if (options.include_peer_info) { + google::bigtable::v2::PeerInfo peer_info; + peer_info.set_transport_type( + google::bigtable::v2::PeerInfo::TRANSPORT_TYPE_DIRECT_ACCESS); + peer_info.set_application_frontend_region("my-region"); + peer_info.set_application_frontend_subzone("my-subzone"); + server_metadata.headers.emplace( + "bigtable-peer-info", + absl::WebSafeBase64Escape(peer_info.SerializeAsString())); + } + if (options.include_response_params) { + google::bigtable::v2::ResponseParams expected_response_params; + expected_response_params.set_cluster_id("my-cluster"); + expected_response_params.set_zone_id("my-zone"); + server_metadata.trailers.emplace( + "x-goog-ext-425905942-bin", + expected_response_params.SerializeAsString()); + } + + SetServerMetadata(client_context, server_metadata); +} + +TEST(GetPeerInfoFromMetadata, NonEmptyHeaderInitial) { + grpc::ClientContext client_context; + CreateServerMetadata(client_context, + CreateServerMetadataOptions{true, false}); + + auto result = GetPeerInfoFromServerMetadata(client_context); + ASSERT_TRUE(result); + EXPECT_THAT(result->transport_type(), + Eq(google::bigtable::v2::PeerInfo::TRANSPORT_TYPE_DIRECT_ACCESS)); + EXPECT_THAT(result->application_frontend_region(), Eq("my-region")); + EXPECT_THAT(result->application_frontend_subzone(), Eq("my-subzone")); +} + +TEST(GetPeerInfoFromMetadata, NonEmptyHeaderTrailers) { + grpc::ClientContext client_context; + google::bigtable::v2::PeerInfo peer_info; + peer_info.set_transport_type( + google::bigtable::v2::PeerInfo::TRANSPORT_TYPE_DIRECT_ACCESS); + peer_info.set_application_frontend_region("my-region"); + peer_info.set_application_frontend_subzone("my-subzone"); + RpcMetadata server_metadata; + // Set the trailers instead of headers to test that the function handles + // both. + server_metadata.trailers.emplace( + "bigtable-peer-info", + absl::WebSafeBase64Escape(peer_info.SerializeAsString())); + SetServerMetadata(client_context, server_metadata); + + auto result = GetPeerInfoFromServerMetadata(client_context); + ASSERT_TRUE(result); + EXPECT_THAT(result->transport_type(), + Eq(google::bigtable::v2::PeerInfo::TRANSPORT_TYPE_DIRECT_ACCESS)); + EXPECT_THAT(result->application_frontend_region(), Eq("my-region")); + EXPECT_THAT(result->application_frontend_subzone(), Eq("my-subzone")); +} + +TEST(GetPeerInfoFromMetadata, EmptyHeader) { + grpc::ClientContext client_context; + // The server metadata is empty when both options are false. + CreateServerMetadata(client_context, + CreateServerMetadataOptions{false, false}); + + EXPECT_THAT(GetPeerInfoFromServerMetadata(client_context), Eq(std::nullopt)); +} + +TEST(GetPeerInfoFromMetadata, EmptyStringInitial) { + grpc::ClientContext client_context; + RpcMetadata server_metadata; + server_metadata.headers.emplace("bigtable-peer-info", ""); + SetServerMetadata(client_context, server_metadata); + + auto result = GetPeerInfoFromServerMetadata(client_context); + ASSERT_TRUE(result); + EXPECT_THAT(result->transport_type(), + Eq(google::bigtable::v2::PeerInfo::TRANSPORT_TYPE_UNKNOWN)); + EXPECT_THAT(result->application_frontend_region(), Eq("")); + EXPECT_THAT(result->application_frontend_subzone(), Eq("")); +} + +TEST(GetPeerInfoFromMetadata, EmptyStringTrailers) { + grpc::ClientContext client_context; + RpcMetadata server_metadata; + server_metadata.trailers.emplace("bigtable-peer-info", ""); + SetServerMetadata(client_context, server_metadata); + + auto result = GetPeerInfoFromServerMetadata(client_context); + ASSERT_TRUE(result); + EXPECT_THAT(result->transport_type(), + Eq(google::bigtable::v2::PeerInfo::TRANSPORT_TYPE_UNKNOWN)); + EXPECT_THAT(result->application_frontend_region(), Eq("")); + EXPECT_THAT(result->application_frontend_subzone(), Eq("")); +} + +TEST(GetPeerInfoFromMetadata, InvalidBase64Initial) { + { + grpc::ClientContext client_context; + RpcMetadata server_metadata; + server_metadata.headers.emplace("bigtable-peer-info", "invalid-base64!"); + SetServerMetadata(client_context, server_metadata); + + EXPECT_THAT(GetPeerInfoFromServerMetadata(client_context), + Eq(std::nullopt)); + } + { + grpc::ClientContext client_context; + RpcMetadata server_metadata; + server_metadata.headers.emplace("bigtable-peer-info", + "invalid+websafe/base64"); + SetServerMetadata(client_context, server_metadata); + + EXPECT_THAT(GetPeerInfoFromServerMetadata(client_context), + Eq(std::nullopt)); + } +} + +TEST(GetPeerInfoFromMetadata, InvalidBase64Trailers) { + { + grpc::ClientContext client_context; + RpcMetadata server_metadata; + server_metadata.trailers.emplace("bigtable-peer-info", "invalid-base64!"); + SetServerMetadata(client_context, server_metadata); + + EXPECT_THAT(GetPeerInfoFromServerMetadata(client_context), + Eq(std::nullopt)); + } + { + grpc::ClientContext client_context; + RpcMetadata server_metadata; + server_metadata.trailers.emplace("bigtable-peer-info", + "invalid+websafe/base64"); + SetServerMetadata(client_context, server_metadata); + + EXPECT_THAT(GetPeerInfoFromServerMetadata(client_context), + Eq(std::nullopt)); + } +} + +std::unordered_map MakeAttributesMap( + opentelemetry::common::KeyValueIterable const& attributes) { + std::unordered_map m; + attributes.ForEachKeyValue([&](opentelemetry::nostd::string_view k, + opentelemetry::common::AttributeValue v) { + if (opentelemetry::nostd::holds_alternative< + opentelemetry::nostd::string_view>(v)) { + m.emplace( + std::string{k}, + opentelemetry::nostd::get(v)); + } + return true; + }); + return m; +} + +void SetClusterZone(grpc::ClientContext& client_context) { + CreateServerMetadata(client_context, + CreateServerMetadataOptions{false, true}); +} + +TEST(OperationLatencyTest, FirstAttemptSuccess) { + auto mock_histogram = std::make_unique>(); + EXPECT_CALL( + *mock_histogram, + Record(A(), A(), + A())) + .WillOnce([](double value, + opentelemetry::common::KeyValueIterable const& attributes, + opentelemetry::context::Context const&) { + EXPECT_THAT(value, Eq(10.0)); + EXPECT_THAT( + MakeAttributesMap(attributes), + UnorderedElementsAre( + Pair("project_id", "my-project-id"), + Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), + Pair("table", "my-table"), Pair("zone", "my-zone"), + Pair("method", "my-method"), Pair("streaming", "my-streaming"), + Pair("status", "OK"), Pair("client_name", "my-client-name"), + Pair("client_uid", "my-client-uid"), + Pair("app_profile", "my-app-profile"))); + }); + + opentelemetry::nostd::shared_ptr mock_meter = + std::make_shared(); + EXPECT_CALL(*mock_meter, CreateDoubleHistogram) + .WillOnce([mock = std::move(mock_histogram)]( + opentelemetry::nostd::string_view name, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view) mutable { + EXPECT_THAT(name, Eq("operation_latencies")); + return std::move(mock); + }); + + opentelemetry::nostd::shared_ptr mock_provider = + std::make_shared(); + EXPECT_CALL(*mock_provider, GetMeter) +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view, + opentelemetry::common::KeyValueIterable const*) mutable { +#else + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view) mutable { +#endif + EXPECT_THAT(scope, Eq("my-instrument-scope")); + EXPECT_THAT(scope_version, Eq("v1")); + return mock_meter; + }); + + OperationLatency operation_latency("my-instrument-scope", mock_provider); + TableResourceLabels resource_labels{"my-project-id", "my-instance", + "my-table", "", ""}; + TableDataLabels data_labels{"my-method", "my-streaming", + "my-client-name", "my-client-uid", + "my-app-profile", ""}; + auto clone = operation_latency.clone(resource_labels, data_labels); + + grpc::ClientContext client_context; + SetClusterZone(client_context); + + auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); + auto clock = std::make_shared(); + + clock->SetTime(std::chrono::steady_clock::now()); + clone->PreCall(otel_context, {clock->Now(), true}); + clock->AdvanceTime(std::chrono::milliseconds(5)); + clone->PostCall(otel_context, client_context, + {clock->Now(), Status{StatusCode::kOk, "ok"}}); + clock->AdvanceTime(std::chrono::milliseconds(5)); + clone->OnDone(otel_context, {clock->Now(), Status{StatusCode::kOk, "ok"}}); +} + +TEST(OperationLatencyTest, ThirdAttemptSuccess) { + auto mock_histogram = std::make_unique>(); + EXPECT_CALL( + *mock_histogram, + Record(A(), A(), + A())) + .WillOnce([](double value, + opentelemetry::common::KeyValueIterable const& attributes, + opentelemetry::context::Context const&) { + EXPECT_THAT(value, Eq(20.0)); + EXPECT_THAT( + MakeAttributesMap(attributes), + UnorderedElementsAre( + Pair("project_id", "my-project-id"), + Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), + Pair("table", "my-table"), Pair("zone", "my-zone"), + Pair("method", "my-method"), Pair("streaming", "my-streaming"), + Pair("status", "OK"), Pair("client_name", "my-client-name"), + Pair("client_uid", "my-client-uid"), + Pair("app_profile", "my-app-profile"))); + }); + + opentelemetry::nostd::shared_ptr mock_meter = + std::make_shared(); + EXPECT_CALL(*mock_meter, CreateDoubleHistogram) + .WillOnce([mock = std::move(mock_histogram)]( + opentelemetry::nostd::string_view name, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view) mutable { + EXPECT_THAT(name, Eq("operation_latencies")); + return std::move(mock); + }); + + opentelemetry::nostd::shared_ptr mock_provider = + std::make_shared(); + EXPECT_CALL(*mock_provider, GetMeter) +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view, + opentelemetry::common::KeyValueIterable const*) mutable { +#else + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view) mutable { +#endif + EXPECT_THAT(scope, Eq("my-instrument-scope")); + EXPECT_THAT(scope_version, Eq("v1")); + return mock_meter; + }); + + OperationLatency operation_latency("my-instrument-scope", mock_provider); + TableResourceLabels resource_labels{"my-project-id", "my-instance", + "my-table", "", ""}; + TableDataLabels data_labels{"my-method", "my-streaming", + "my-client-name", "my-client-uid", + "my-app-profile", ""}; + auto clone = operation_latency.clone(resource_labels, data_labels); + + grpc::ClientContext client_context; + SetClusterZone(client_context); + + auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); + auto clock = std::make_shared(); + + clock->SetTime(std::chrono::steady_clock::now()); + + clone->PreCall(otel_context, {clock->Now(), true}); + clock->AdvanceTime(std::chrono::milliseconds(5)); + clone->PostCall( + otel_context, client_context, + {clock->Now(), Status{StatusCode::kUnavailable, "unavailable"}}); + + clone->PreCall(otel_context, {clock->Now(), false}); + clock->AdvanceTime(std::chrono::milliseconds(5)); + clone->PostCall( + otel_context, client_context, + {clock->Now(), Status{StatusCode::kUnavailable, "unavailable"}}); + + clone->PreCall(otel_context, {clock->Now(), false}); + clock->AdvanceTime(std::chrono::milliseconds(5)); + clone->PostCall(otel_context, client_context, + {clock->Now(), Status{StatusCode::kOk, "ok"}}); + + clock->AdvanceTime(std::chrono::milliseconds(5)); + clone->OnDone(otel_context, {clock->Now(), Status{StatusCode::kOk, "ok"}}); +} + +TEST(OperationLatencyTest, UsesDefaultClusterAndZone) { + auto mock_histogram = std::make_unique>(); + EXPECT_CALL( + *mock_histogram, + Record(A(), A(), + A())) + .WillOnce([](double value, + opentelemetry::common::KeyValueIterable const& attributes, + opentelemetry::context::Context const&) { + EXPECT_THAT(value, Eq(10.0)); + EXPECT_THAT( + MakeAttributesMap(attributes), + UnorderedElementsAre( + Pair("project_id", "my-project-id"), + Pair("instance", "my-instance"), + Pair("cluster", ""), Pair("table", "my-table"), + Pair("zone", "global"), Pair("method", "my-method"), + Pair("streaming", "my-streaming"), Pair("status", "OK"), + Pair("client_name", "my-client-name"), + Pair("client_uid", "my-client-uid"), + Pair("app_profile", "my-app-profile"))); + }); + + opentelemetry::nostd::shared_ptr mock_meter = + std::make_shared(); + EXPECT_CALL(*mock_meter, CreateDoubleHistogram) + .WillOnce([mock = std::move(mock_histogram)]( + opentelemetry::nostd::string_view name, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view) mutable { + EXPECT_THAT(name, Eq("operation_latencies")); + return std::move(mock); + }); + + opentelemetry::nostd::shared_ptr mock_provider = + std::make_shared(); + EXPECT_CALL(*mock_provider, GetMeter) +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view, + opentelemetry::common::KeyValueIterable const*) mutable { +#else + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view) mutable { +#endif + EXPECT_THAT(scope, Eq("my-instrument-scope")); + EXPECT_THAT(scope_version, Eq("v1")); + return mock_meter; + }); + + OperationLatency operation_latency("my-instrument-scope", mock_provider); + TableResourceLabels resource_labels{"my-project-id", "my-instance", + "my-table", "", ""}; + TableDataLabels data_labels{"my-method", "my-streaming", + "my-client-name", "my-client-uid", + "my-app-profile", ""}; + auto clone = operation_latency.clone(resource_labels, data_labels); + + grpc::ClientContext client_context; + + auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); + auto clock = std::make_shared(); + + clock->SetTime(std::chrono::steady_clock::now()); + clone->PreCall(otel_context, {clock->Now(), true}); + clock->AdvanceTime(std::chrono::milliseconds(5)); + clone->PostCall(otel_context, client_context, + {clock->Now(), Status{StatusCode::kOk, "ok"}}); + clock->AdvanceTime(std::chrono::milliseconds(5)); + clone->OnDone(otel_context, {clock->Now(), Status{StatusCode::kOk, "ok"}}); +} + +TEST(AttemptLatencyTest, NoRetry) { + auto mock_histogram = std::make_unique>(); + EXPECT_CALL( + *mock_histogram, + Record(A(), A(), + A())) + .WillOnce([](double value, + opentelemetry::common::KeyValueIterable const& attributes, + opentelemetry::context::Context const&) { + EXPECT_THAT(value, Eq(1.234)); + EXPECT_THAT( + MakeAttributesMap(attributes), + UnorderedElementsAre( + Pair("project_id", "my-project-id"), + Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), + Pair("table", "my-table"), Pair("zone", "my-zone"), + Pair("method", "my-method"), Pair("streaming", "my-streaming"), + Pair("status", "OK"), Pair("client_name", "my-client-name"), + Pair("client_uid", "my-client-uid"), + Pair("app_profile", "my-app-profile"))); + }); + + opentelemetry::nostd::shared_ptr mock_meter = + std::make_shared(); + EXPECT_CALL(*mock_meter, CreateDoubleHistogram) + .WillOnce([mock = std::move(mock_histogram)]( + opentelemetry::nostd::string_view name, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view) mutable { + EXPECT_THAT(name, Eq("attempt_latencies")); + return std::move(mock); + }); + + opentelemetry::nostd::shared_ptr mock_provider = + std::make_shared(); + EXPECT_CALL(*mock_provider, GetMeter) +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view, + opentelemetry::common::KeyValueIterable const*) mutable { +#else + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view) mutable { +#endif + EXPECT_THAT(scope, Eq("my-instrument-scope")); + EXPECT_THAT(scope_version, Eq("v1")); + return mock_meter; + }); + + AttemptLatency attempt_latency("my-instrument-scope", mock_provider); + TableResourceLabels resource_labels{"my-project-id", "my-instance", + "my-table", "", ""}; + TableDataLabels data_labels{"my-method", "my-streaming", + "my-client-name", "my-client-uid", + "my-app-profile", ""}; + auto clone = attempt_latency.clone(resource_labels, data_labels); + + grpc::ClientContext client_context; + SetClusterZone(client_context); + + auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); + auto clock = std::make_shared(); + + clock->SetTime(std::chrono::steady_clock::now()); + clone->PreCall(otel_context, {clock->Now(), true}); + clock->AdvanceTime(std::chrono::microseconds(1234)); + clone->PostCall(otel_context, client_context, + {clock->Now(), Status{StatusCode::kOk, "ok"}}); + clock->AdvanceTime(std::chrono::milliseconds(100)); + clone->OnDone(otel_context, {clock->Now(), Status{StatusCode::kOk, "ok"}}); +} + +TEST(AttemptLatencyTest, ThreeAttempts) { + auto mock_histogram = std::make_unique>(); + EXPECT_CALL( + *mock_histogram, + Record(A(), A(), + A())) + .WillOnce([](double value, + opentelemetry::common::KeyValueIterable const& attributes, + opentelemetry::context::Context const&) { + EXPECT_THAT(value, Eq(1.0)); + EXPECT_THAT( + MakeAttributesMap(attributes), + UnorderedElementsAre( + Pair("project_id", "my-project-id"), + Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), + Pair("table", "my-table"), Pair("zone", "my-zone"), + Pair("method", "my-method"), Pair("streaming", "my-streaming"), + Pair("status", "OK"), Pair("client_name", "my-client-name"), + Pair("client_uid", "my-client-uid"), + Pair("app_profile", "my-app-profile"))); + }) + .WillOnce([](double value, + opentelemetry::common::KeyValueIterable const& attributes, + opentelemetry::context::Context const&) { + EXPECT_THAT(value, Eq(2.0)); + EXPECT_THAT( + MakeAttributesMap(attributes), + UnorderedElementsAre( + Pair("project_id", "my-project-id"), + Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), + Pair("table", "my-table"), Pair("zone", "my-zone"), + Pair("method", "my-method"), Pair("streaming", "my-streaming"), + Pair("status", "OK"), Pair("client_name", "my-client-name"), + Pair("client_uid", "my-client-uid"), + Pair("app_profile", "my-app-profile"))); + }) + .WillOnce([](double value, + opentelemetry::common::KeyValueIterable const& attributes, + opentelemetry::context::Context const&) { + EXPECT_THAT(value, Eq(3.0)); + EXPECT_THAT( + MakeAttributesMap(attributes), + UnorderedElementsAre( + Pair("project_id", "my-project-id"), + Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), + Pair("table", "my-table"), Pair("zone", "my-zone"), + Pair("method", "my-method"), Pair("streaming", "my-streaming"), + Pair("status", "OK"), Pair("client_name", "my-client-name"), + Pair("client_uid", "my-client-uid"), + Pair("app_profile", "my-app-profile"))); + }); + + opentelemetry::nostd::shared_ptr mock_meter = + std::make_shared(); + EXPECT_CALL(*mock_meter, CreateDoubleHistogram) + .WillOnce([mock = std::move(mock_histogram)]( + opentelemetry::nostd::string_view name, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view) mutable { + EXPECT_THAT(name, Eq("attempt_latencies")); + return std::move(mock); + }); + + opentelemetry::nostd::shared_ptr mock_provider = + std::make_shared(); + EXPECT_CALL(*mock_provider, GetMeter) +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view, + opentelemetry::common::KeyValueIterable const*) mutable { +#else + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view) mutable { +#endif + EXPECT_THAT(scope, Eq("my-instrument-scope")); + EXPECT_THAT(scope_version, Eq("v1")); + return mock_meter; + }); + + AttemptLatency attempt_latency("my-instrument-scope", mock_provider); + TableResourceLabels resource_labels{"my-project-id", "my-instance", + "my-table", "", ""}; + TableDataLabels data_labels{"my-method", "my-streaming", + "my-client-name", "my-client-uid", + "my-app-profile", ""}; + auto clone = attempt_latency.clone(resource_labels, data_labels); + + grpc::ClientContext client_context; + SetClusterZone(client_context); + + auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); + auto clock = std::make_shared(); + + clock->SetTime(std::chrono::steady_clock::now()); + clone->PreCall(otel_context, {clock->Now(), true}); + clock->AdvanceTime(std::chrono::milliseconds(1)); + clone->PostCall(otel_context, client_context, + {clock->Now(), Status{StatusCode::kOk, "ok"}}); + clock->AdvanceTime(std::chrono::milliseconds(100)); + clone->PreCall(otel_context, {clock->Now(), false}); + clock->AdvanceTime(std::chrono::milliseconds(2)); + clone->PostCall(otel_context, client_context, + {clock->Now(), Status{StatusCode::kOk, "ok"}}); + clone->PreCall(otel_context, {clock->Now(), false}); + clock->AdvanceTime(std::chrono::milliseconds(3)); + clone->PostCall(otel_context, client_context, + {clock->Now(), Status{StatusCode::kOk, "ok"}}); + clock->AdvanceTime(std::chrono::milliseconds(100)); + clone->OnDone(otel_context, {clock->Now(), Status{StatusCode::kOk, "ok"}}); +} + +TEST(AttemptLatencyTest, UsesDefaultClusterAndZone) { + auto mock_histogram = std::make_unique>(); + EXPECT_CALL( + *mock_histogram, + Record(A(), A(), + A())) + .WillOnce([](double value, + opentelemetry::common::KeyValueIterable const& attributes, + opentelemetry::context::Context const&) { + EXPECT_THAT(value, Eq(1.234)); + EXPECT_THAT( + MakeAttributesMap(attributes), + UnorderedElementsAre( + Pair("project_id", "my-project-id"), + Pair("instance", "my-instance"), + Pair("cluster", ""), Pair("table", "my-table"), + Pair("zone", "global"), Pair("method", "my-method"), + Pair("streaming", "my-streaming"), Pair("status", "OK"), + Pair("client_name", "my-client-name"), + Pair("client_uid", "my-client-uid"), + Pair("app_profile", "my-app-profile"))); + }); + + opentelemetry::nostd::shared_ptr mock_meter = + std::make_shared(); + EXPECT_CALL(*mock_meter, CreateDoubleHistogram) + .WillOnce([mock = std::move(mock_histogram)]( + opentelemetry::nostd::string_view name, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view) mutable { + EXPECT_THAT(name, Eq("attempt_latencies")); + return std::move(mock); + }); + + opentelemetry::nostd::shared_ptr mock_provider = + std::make_shared(); + EXPECT_CALL(*mock_provider, GetMeter) +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view, + opentelemetry::common::KeyValueIterable const*) mutable { +#else + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view) mutable { +#endif + EXPECT_THAT(scope, Eq("my-instrument-scope")); + EXPECT_THAT(scope_version, Eq("v1")); + return mock_meter; + }); + + AttemptLatency attempt_latency("my-instrument-scope", mock_provider); + TableResourceLabels resource_labels{"my-project-id", "my-instance", + "my-table", "", ""}; + TableDataLabels data_labels{"my-method", "my-streaming", + "my-client-name", "my-client-uid", + "my-app-profile", ""}; + auto clone = attempt_latency.clone(resource_labels, data_labels); + + grpc::ClientContext client_context; + + auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); + auto clock = std::make_shared(); + + clock->SetTime(std::chrono::steady_clock::now()); + clone->PreCall(otel_context, {clock->Now(), true}); + clock->AdvanceTime(std::chrono::microseconds(1234)); + clone->PostCall(otel_context, client_context, + {clock->Now(), Status{StatusCode::kOk, "ok"}}); + clock->AdvanceTime(std::chrono::milliseconds(5)); + clone->OnDone(otel_context, {clock->Now(), Status{StatusCode::kOk, "ok"}}); +} + +TEST(AttemptLatency2Test, NoRetry) { + auto mock_histogram = std::make_unique>(); + EXPECT_CALL( + *mock_histogram, + Record(A(), A(), + A())) + .WillOnce([](double value, + opentelemetry::common::KeyValueIterable const& attributes, + opentelemetry::context::Context const&) { + EXPECT_THAT(value, Eq(1.234)); + EXPECT_THAT( + MakeAttributesMap(attributes), + UnorderedElementsAre( + Pair("project_id", "my-project-id"), + Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), + Pair("table", "my-table"), Pair("zone", "my-zone"), + Pair("method", "my-method"), Pair("streaming", "my-streaming"), + Pair("status", "OK"), Pair("client_name", "my-client-name"), + Pair("client_uid", "my-client-uid"), + Pair("app_profile", "my-app-profile"), + Pair("transport_type", "transport_type_direct_access"), + Pair("transport_region", "my-region"), + Pair("transport_subzone", "my-subzone"))); + }); + + opentelemetry::nostd::shared_ptr mock_meter = + std::make_shared(); + EXPECT_CALL(*mock_meter, CreateDoubleHistogram) + .WillOnce([mock = std::move(mock_histogram)]( + opentelemetry::nostd::string_view name, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view) mutable { + EXPECT_THAT(name, Eq("attempt_latencies2")); + return std::move(mock); + }); + + opentelemetry::nostd::shared_ptr mock_provider = + std::make_shared(); + EXPECT_CALL(*mock_provider, GetMeter) +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view, + opentelemetry::common::KeyValueIterable const*) mutable { +#else + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view) mutable { +#endif + EXPECT_THAT(scope, Eq("my-instrument-scope")); + EXPECT_THAT(scope_version, Eq("v1")); + return mock_meter; + }); + + AttemptLatency2 attempt_latency("my-instrument-scope", mock_provider); + TableResourceLabels resource_labels{"my-project-id", "my-instance", + "my-table", "", ""}; + TableDataLabels data_labels{"my-method", "my-streaming", + "my-client-name", "my-client-uid", + "my-app-profile", ""}; + auto clone = attempt_latency.clone(resource_labels, data_labels); + + grpc::ClientContext client_context; + CreateServerMetadata(client_context); + + auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); + auto clock = std::make_shared(); + + clock->SetTime(std::chrono::steady_clock::now()); + clone->PreCall(otel_context, {clock->Now(), true}); + clock->AdvanceTime(std::chrono::microseconds(1234)); + clone->PostCall(otel_context, client_context, + {clock->Now(), Status{StatusCode::kOk, "ok"}}); +} + +TEST(AttemptLatency2Test, ThreeAttempts) { + auto mock_histogram = std::make_unique>(); + EXPECT_CALL( + *mock_histogram, + Record(A(), A(), + A())) + .WillOnce([](double value, + opentelemetry::common::KeyValueIterable const& attributes, + opentelemetry::context::Context const&) { + EXPECT_THAT(value, Eq(1.0)); + EXPECT_THAT( + MakeAttributesMap(attributes), + UnorderedElementsAre( + Pair("project_id", "my-project-id"), + Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), + Pair("table", "my-table"), Pair("zone", "my-zone"), + Pair("method", "my-method"), Pair("streaming", "my-streaming"), + Pair("status", "OK"), Pair("client_name", "my-client-name"), + Pair("client_uid", "my-client-uid"), + Pair("app_profile", "my-app-profile"), + Pair("transport_type", "transport_type_direct_access"), + Pair("transport_region", "my-region"), + Pair("transport_subzone", "my-subzone"))); + }) + .WillOnce([](double value, + opentelemetry::common::KeyValueIterable const& attributes, + opentelemetry::context::Context const&) { + EXPECT_THAT(value, Eq(2.0)); + EXPECT_THAT( + MakeAttributesMap(attributes), + UnorderedElementsAre( + Pair("project_id", "my-project-id"), + Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), + Pair("table", "my-table"), Pair("zone", "my-zone"), + Pair("method", "my-method"), Pair("streaming", "my-streaming"), + Pair("status", "OK"), Pair("client_name", "my-client-name"), + Pair("client_uid", "my-client-uid"), + Pair("app_profile", "my-app-profile"), + Pair("transport_type", "transport_type_direct_access"), + Pair("transport_region", "my-region"), + Pair("transport_subzone", "my-subzone"))); + }) + .WillOnce([](double value, + opentelemetry::common::KeyValueIterable const& attributes, + opentelemetry::context::Context const&) { + EXPECT_THAT(value, Eq(3.0)); + EXPECT_THAT( + MakeAttributesMap(attributes), + UnorderedElementsAre( + Pair("project_id", "my-project-id"), + Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), + Pair("table", "my-table"), Pair("zone", "my-zone"), + Pair("method", "my-method"), Pair("streaming", "my-streaming"), + Pair("status", "OK"), Pair("client_name", "my-client-name"), + Pair("client_uid", "my-client-uid"), + Pair("app_profile", "my-app-profile"), + Pair("transport_type", "transport_type_direct_access"), + Pair("transport_region", "my-region"), + Pair("transport_subzone", "my-subzone"))); + }); + + opentelemetry::nostd::shared_ptr mock_meter = + std::make_shared(); + EXPECT_CALL(*mock_meter, CreateDoubleHistogram) + .WillOnce([mock = std::move(mock_histogram)]( + opentelemetry::nostd::string_view name, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view) mutable { + EXPECT_THAT(name, Eq("attempt_latencies2")); + return std::move(mock); + }); + + opentelemetry::nostd::shared_ptr mock_provider = + std::make_shared(); + EXPECT_CALL(*mock_provider, GetMeter) +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view, + opentelemetry::common::KeyValueIterable const*) mutable { +#else + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view) mutable { +#endif + EXPECT_THAT(scope, Eq("my-instrument-scope")); + EXPECT_THAT(scope_version, Eq("v1")); + return mock_meter; + }); + + AttemptLatency2 attempt_latency("my-instrument-scope", mock_provider); + TableResourceLabels resource_labels{"my-project-id", "my-instance", + "my-table", "", ""}; + TableDataLabels data_labels{"my-method", "my-streaming", + "my-client-name", "my-client-uid", + "my-app-profile", ""}; + auto clone = attempt_latency.clone(resource_labels, data_labels); + + grpc::ClientContext client_context; + CreateServerMetadata(client_context); + + auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); + auto clock = std::make_shared(); + + clock->SetTime(std::chrono::steady_clock::now()); + clone->PreCall(otel_context, {clock->Now(), true}); + clock->AdvanceTime(std::chrono::milliseconds(1)); + clone->PostCall(otel_context, client_context, + {clock->Now(), Status{StatusCode::kOk, "ok"}}); + clock->AdvanceTime(std::chrono::milliseconds(100)); + clone->PreCall(otel_context, {clock->Now(), false}); + clock->AdvanceTime(std::chrono::milliseconds(2)); + clone->PostCall(otel_context, client_context, + {clock->Now(), Status{StatusCode::kOk, "ok"}}); + clone->PreCall(otel_context, {clock->Now(), false}); + clock->AdvanceTime(std::chrono::milliseconds(3)); + clone->PostCall(otel_context, client_context, + {clock->Now(), Status{StatusCode::kOk, "ok"}}); +} + +TEST(AttemptLatency2Test, UsesDefaultClusterAndZone) { + auto mock_histogram = std::make_unique>(); + EXPECT_CALL( + *mock_histogram, + Record(A(), A(), + A())) + .WillOnce([](double value, + opentelemetry::common::KeyValueIterable const& attributes, + opentelemetry::context::Context const&) { + EXPECT_THAT(value, Eq(1.234)); + EXPECT_THAT( + MakeAttributesMap(attributes), + UnorderedElementsAre( + Pair("project_id", "my-project-id"), + Pair("instance", "my-instance"), + Pair("cluster", ""), Pair("table", "my-table"), + Pair("zone", "global"), Pair("method", "my-method"), + Pair("streaming", "my-streaming"), Pair("status", "OK"), + Pair("client_name", "my-client-name"), + Pair("client_uid", "my-client-uid"), + Pair("app_profile", "my-app-profile"), + Pair("transport_type", "transport_type_direct_access"), + Pair("transport_region", "my-region"), + Pair("transport_subzone", "my-subzone"))); + }); + + opentelemetry::nostd::shared_ptr mock_meter = + std::make_shared(); + EXPECT_CALL(*mock_meter, CreateDoubleHistogram) + .WillOnce([mock = std::move(mock_histogram)]( + opentelemetry::nostd::string_view name, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view) mutable { + EXPECT_THAT(name, Eq("attempt_latencies2")); + return std::move(mock); + }); + + opentelemetry::nostd::shared_ptr mock_provider = + std::make_shared(); + EXPECT_CALL(*mock_provider, GetMeter) +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view, + opentelemetry::common::KeyValueIterable const*) mutable { +#else + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view) mutable { +#endif + EXPECT_THAT(scope, Eq("my-instrument-scope")); + EXPECT_THAT(scope_version, Eq("v1")); + return mock_meter; + }); + + AttemptLatency2 attempt_latency("my-instrument-scope", mock_provider); + TableResourceLabels resource_labels{"my-project-id", "my-instance", + "my-table", "", ""}; + TableDataLabels data_labels{"my-method", "my-streaming", + "my-client-name", "my-client-uid", + "my-app-profile", ""}; + auto clone = attempt_latency.clone(resource_labels, data_labels); + + grpc::ClientContext client_context; + CreateServerMetadata(client_context, + CreateServerMetadataOptions{true, false}); + + auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); + auto clock = std::make_shared(); + + clock->SetTime(std::chrono::steady_clock::now()); + clone->PreCall(otel_context, {clock->Now(), true}); + clock->AdvanceTime(std::chrono::microseconds(1234)); + clone->PostCall(otel_context, client_context, + {clock->Now(), Status{StatusCode::kOk, "ok"}}); +} + +TEST(RetryCountTest, NoRetry) { + auto mock_counter = std::make_unique>(); + EXPECT_CALL(*mock_counter, + Add(A(), + A(), + A())) + .WillOnce([](std::uint64_t value, + opentelemetry::common::KeyValueIterable const& attributes, + opentelemetry::context::Context const&) { + EXPECT_THAT(value, Eq(0)); + EXPECT_THAT( + MakeAttributesMap(attributes), + UnorderedElementsAre( + Pair("project_id", "my-project-id"), + Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), + Pair("table", "my-table"), Pair("zone", "my-zone"), + Pair("method", "my-method"), Pair("status", "OK"), + Pair("client_name", "my-client-name"), + Pair("client_uid", "my-client-uid"), + Pair("app_profile", "my-app-profile"))); + }); + + opentelemetry::nostd::shared_ptr mock_meter = + std::make_shared(); + EXPECT_CALL(*mock_meter, CreateUInt64Counter) + .WillOnce([mock = std::move(mock_counter)]( + opentelemetry::nostd::string_view name, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view) mutable { + EXPECT_THAT(name, Eq("retry_count")); + return std::move(mock); + }); + + opentelemetry::nostd::shared_ptr mock_provider = + std::make_shared(); + EXPECT_CALL(*mock_provider, GetMeter) +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view, + opentelemetry::common::KeyValueIterable const*) mutable { +#else + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view) mutable { +#endif + EXPECT_THAT(scope, Eq("my-instrument-scope")); + EXPECT_THAT(scope_version, Eq("v1")); + return mock_meter; + }); + + RetryCount retry_count("my-instrument-scope", mock_provider); + TableResourceLabels resource_labels{"my-project-id", "my-instance", + "my-table", "", ""}; + TableDataLabels data_labels{"my-method", "my-streaming", + "my-client-name", "my-client-uid", + "my-app-profile", ""}; + auto clone = retry_count.clone(resource_labels, data_labels); + + grpc::ClientContext client_context; + SetClusterZone(client_context); + + auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); + auto clock = std::make_shared(); + + clock->SetTime(std::chrono::steady_clock::now()); + clone->PreCall(otel_context, {clock->Now(), true}); + clock->AdvanceTime(std::chrono::microseconds(1234)); + clone->PostCall(otel_context, client_context, + {clock->Now(), Status{StatusCode::kOk, "ok"}}); + clock->AdvanceTime(std::chrono::milliseconds(100)); + clone->OnDone(otel_context, {clock->Now(), Status{StatusCode::kOk, "ok"}}); +} + +TEST(RetryCountTest, ThreeAttempts) { + auto mock_counter = std::make_unique>(); + EXPECT_CALL(*mock_counter, + Add(A(), + A(), + A())) + .WillOnce([](std::uint64_t value, + opentelemetry::common::KeyValueIterable const& attributes, + opentelemetry::context::Context const&) { + EXPECT_THAT(value, Eq(2)); + EXPECT_THAT( + MakeAttributesMap(attributes), + UnorderedElementsAre( + Pair("project_id", "my-project-id"), + Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), + Pair("table", "my-table"), Pair("zone", "my-zone"), + Pair("method", "my-method"), Pair("status", "OK"), + Pair("client_name", "my-client-name"), + Pair("client_uid", "my-client-uid"), + Pair("app_profile", "my-app-profile"))); + }); + + opentelemetry::nostd::shared_ptr mock_meter = + std::make_shared(); + EXPECT_CALL(*mock_meter, CreateUInt64Counter) + .WillOnce([mock = std::move(mock_counter)]( + opentelemetry::nostd::string_view name, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view) mutable { + EXPECT_THAT(name, Eq("retry_count")); + return std::move(mock); + }); + + opentelemetry::nostd::shared_ptr mock_provider = + std::make_shared(); + EXPECT_CALL(*mock_provider, GetMeter) +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view, + opentelemetry::common::KeyValueIterable const*) mutable { +#else + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view) mutable { +#endif + EXPECT_THAT(scope, Eq("my-instrument-scope")); + EXPECT_THAT(scope_version, Eq("v1")); + return mock_meter; + }); + + RetryCount retry_count("my-instrument-scope", mock_provider); + TableResourceLabels resource_labels{"my-project-id", "my-instance", + "my-table", "", ""}; + TableDataLabels data_labels{"my-method", "my-streaming", + "my-client-name", "my-client-uid", + "my-app-profile", ""}; + auto clone = retry_count.clone(resource_labels, data_labels); + + grpc::ClientContext client_context; + SetClusterZone(client_context); + + auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); + auto clock = std::make_shared(); + + clock->SetTime(std::chrono::steady_clock::now()); + clone->PreCall(otel_context, {clock->Now(), true}); + clock->AdvanceTime(std::chrono::microseconds(1)); + clone->PostCall(otel_context, client_context, + {clock->Now(), Status{StatusCode::kOk, "ok"}}); + clock->AdvanceTime(std::chrono::milliseconds(100)); + clone->PreCall(otel_context, {clock->Now(), false}); + clock->AdvanceTime(std::chrono::microseconds(2)); + clone->PostCall(otel_context, client_context, + {clock->Now(), Status{StatusCode::kOk, "ok"}}); + clock->AdvanceTime(std::chrono::milliseconds(100)); + clone->PreCall(otel_context, {clock->Now(), false}); + clock->AdvanceTime(std::chrono::microseconds(3)); + clone->PostCall(otel_context, client_context, + {clock->Now(), Status{StatusCode::kOk, "ok"}}); + clock->AdvanceTime(std::chrono::milliseconds(100)); + clone->OnDone(otel_context, {clock->Now(), Status{StatusCode::kOk, "ok"}}); +} + +TEST(RetryCountTest, UsesDefaultClusterAndZone) { + auto mock_counter = std::make_unique>(); + EXPECT_CALL(*mock_counter, + Add(A(), + A(), + A())) + .WillOnce([](std::uint64_t value, + opentelemetry::common::KeyValueIterable const& attributes, + opentelemetry::context::Context const&) { + EXPECT_THAT(value, Eq(0)); + EXPECT_THAT( + MakeAttributesMap(attributes), + UnorderedElementsAre( + Pair("project_id", "my-project-id"), + Pair("instance", "my-instance"), + Pair("cluster", ""), Pair("table", "my-table"), + Pair("zone", "global"), Pair("method", "my-method"), + Pair("status", "OK"), Pair("client_name", "my-client-name"), + Pair("client_uid", "my-client-uid"), + Pair("app_profile", "my-app-profile"))); + }); + + opentelemetry::nostd::shared_ptr mock_meter = + std::make_shared(); + EXPECT_CALL(*mock_meter, CreateUInt64Counter) + .WillOnce([mock = std::move(mock_counter)]( + opentelemetry::nostd::string_view name, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view) mutable { + EXPECT_THAT(name, Eq("retry_count")); + return std::move(mock); + }); + + opentelemetry::nostd::shared_ptr mock_provider = + std::make_shared(); + EXPECT_CALL(*mock_provider, GetMeter) +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view, + opentelemetry::common::KeyValueIterable const*) mutable { +#else + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view) mutable { +#endif + EXPECT_THAT(scope, Eq("my-instrument-scope")); + EXPECT_THAT(scope_version, Eq("v1")); + return mock_meter; + }); + + RetryCount retry_count("my-instrument-scope", mock_provider); + TableResourceLabels resource_labels{"my-project-id", "my-instance", + "my-table", "", ""}; + TableDataLabels data_labels{"my-method", "my-streaming", + "my-client-name", "my-client-uid", + "my-app-profile", ""}; + auto clone = retry_count.clone(resource_labels, data_labels); + + grpc::ClientContext client_context; + + auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); + auto clock = std::make_shared(); + + clock->SetTime(std::chrono::steady_clock::now()); + clone->PreCall(otel_context, {clock->Now(), true}); + clock->AdvanceTime(std::chrono::microseconds(1234)); + clone->PostCall(otel_context, client_context, + {clock->Now(), Status{StatusCode::kOk, "ok"}}); + clock->AdvanceTime(std::chrono::milliseconds(100)); + clone->OnDone(otel_context, {clock->Now(), Status{StatusCode::kOk, "ok"}}); +} + +TEST(FirstResponseLatency, Success) { + auto mock_histogram = std::make_unique>(); + EXPECT_CALL( + *mock_histogram, + Record(A(), A(), + A())) + .WillOnce([](double value, + opentelemetry::common::KeyValueIterable const& attributes, + opentelemetry::context::Context const&) { + EXPECT_THAT(value, Eq(2.0)); + EXPECT_THAT( + MakeAttributesMap(attributes), + UnorderedElementsAre( + Pair("project_id", "my-project-id"), + Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), + Pair("table", "my-table"), Pair("zone", "my-zone"), + Pair("method", "my-method"), Pair("status", "OK"), + Pair("client_name", "my-client-name"), + Pair("client_uid", "my-client-uid"), + Pair("app_profile", "my-app-profile"))); + }); + + opentelemetry::nostd::shared_ptr mock_meter = + std::make_shared(); + EXPECT_CALL(*mock_meter, CreateDoubleHistogram) + .WillOnce([mock = std::move(mock_histogram)]( + opentelemetry::nostd::string_view name, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view) mutable { + EXPECT_THAT(name, Eq("first_response_latencies")); + return std::move(mock); + }); + + opentelemetry::nostd::shared_ptr mock_provider = + std::make_shared(); + EXPECT_CALL(*mock_provider, GetMeter) +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view, + opentelemetry::common::KeyValueIterable const*) mutable { +#else + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view) mutable { +#endif + EXPECT_THAT(scope, Eq("my-instrument-scope")); + EXPECT_THAT(scope_version, Eq("v1")); + return mock_meter; + }); + + FirstResponseLatency first_response_latency("my-instrument-scope", + mock_provider); + TableResourceLabels resource_labels{"my-project-id", "my-instance", + "my-table", "", ""}; + TableDataLabels data_labels{"my-method", "my-streaming", + "my-client-name", "my-client-uid", + "my-app-profile", ""}; + auto clone = first_response_latency.clone(resource_labels, data_labels); + + grpc::ClientContext client_context; + SetClusterZone(client_context); + + auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); + auto clock = std::make_shared(); + + // Verify that only the first response is recorded. + clock->SetTime(std::chrono::steady_clock::now()); + clone->PreCall(otel_context, {clock->Now(), true}); + clock->AdvanceTime(std::chrono::milliseconds(2)); + clone->ElementDelivery(otel_context, {clock->Now(), true}); + clock->AdvanceTime(std::chrono::milliseconds(5)); + clone->ElementDelivery(otel_context, {clock->Now(), false}); + clock->AdvanceTime(std::chrono::milliseconds(5)); + clone->PostCall(otel_context, client_context, + {clock->Now(), Status{StatusCode::kOk, "ok"}}); + clock->AdvanceTime(std::chrono::milliseconds(5)); + clone->OnDone(otel_context, {clock->Now(), Status{StatusCode::kOk, "ok"}}); +} + +TEST(FirstResponseLatency, NoDataReceived) { + auto mock_histogram = std::make_unique>(); + EXPECT_CALL( + *mock_histogram, + Record(A(), A(), + A())) + .Times(0); + + opentelemetry::nostd::shared_ptr mock_meter = + std::make_shared(); + EXPECT_CALL(*mock_meter, CreateDoubleHistogram) + .WillOnce([mock = std::move(mock_histogram)]( + opentelemetry::nostd::string_view name, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view) mutable { + EXPECT_THAT(name, Eq("first_response_latencies")); + return std::move(mock); + }); + + opentelemetry::nostd::shared_ptr mock_provider = + std::make_shared(); + EXPECT_CALL(*mock_provider, GetMeter) +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view, + opentelemetry::common::KeyValueIterable const*) mutable { +#else + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view) mutable { +#endif + EXPECT_THAT(scope, Eq("my-instrument-scope")); + EXPECT_THAT(scope_version, Eq("v1")); + return mock_meter; + }); + + FirstResponseLatency first_response_latency("my-instrument-scope", + mock_provider); + TableResourceLabels resource_labels{"my-project-id", "my-instance", + "my-table", "", ""}; + TableDataLabels data_labels{"my-method", "my-streaming", + "my-client-name", "my-client-uid", + "my-app-profile", ""}; + auto clone = first_response_latency.clone(resource_labels, data_labels); + + grpc::ClientContext client_context; + SetClusterZone(client_context); + + auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); + auto clock = std::make_shared(); + + // ElementDelivery is not called. + clock->SetTime(std::chrono::steady_clock::now()); + clone->PreCall(otel_context, {clock->Now(), true}); + clock->AdvanceTime(std::chrono::milliseconds(5)); + clone->PostCall( + otel_context, client_context, + {clock->Now(), Status{StatusCode::kDeadlineExceeded, "timeout"}}); + clock->AdvanceTime(std::chrono::milliseconds(5)); + clone->OnDone( + otel_context, + {clock->Now(), Status{StatusCode::kDeadlineExceeded, "timeout"}}); +} + +TEST(FirstResponseLatency, UsesDefaultClusterAndZone) { + auto mock_histogram = std::make_unique>(); + EXPECT_CALL( + *mock_histogram, + Record(A(), A(), + A())) + .WillOnce([](double value, + opentelemetry::common::KeyValueIterable const& attributes, + opentelemetry::context::Context const&) { + EXPECT_THAT(value, Eq(2.0)); + EXPECT_THAT( + MakeAttributesMap(attributes), + UnorderedElementsAre( + Pair("project_id", "my-project-id"), + Pair("instance", "my-instance"), + Pair("cluster", ""), Pair("table", "my-table"), + Pair("zone", "global"), Pair("method", "my-method"), + Pair("status", "OK"), Pair("client_name", "my-client-name"), + Pair("client_uid", "my-client-uid"), + Pair("app_profile", "my-app-profile"))); + }); + + opentelemetry::nostd::shared_ptr mock_meter = + std::make_shared(); + EXPECT_CALL(*mock_meter, CreateDoubleHistogram) + .WillOnce([mock = std::move(mock_histogram)]( + opentelemetry::nostd::string_view name, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view) mutable { + EXPECT_THAT(name, Eq("first_response_latencies")); + return std::move(mock); + }); + + opentelemetry::nostd::shared_ptr mock_provider = + std::make_shared(); + EXPECT_CALL(*mock_provider, GetMeter) +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view, + opentelemetry::common::KeyValueIterable const*) mutable { +#else + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view) mutable { +#endif + EXPECT_THAT(scope, Eq("my-instrument-scope")); + EXPECT_THAT(scope_version, Eq("v1")); + return mock_meter; + }); + + FirstResponseLatency first_response_latency("my-instrument-scope", + mock_provider); + TableResourceLabels resource_labels{"my-project-id", "my-instance", + "my-table", "", ""}; + TableDataLabels data_labels{"my-method", "my-streaming", + "my-client-name", "my-client-uid", + "my-app-profile", ""}; + auto clone = first_response_latency.clone(resource_labels, data_labels); + + grpc::ClientContext client_context; + + auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); + auto clock = std::make_shared(); + + clock->SetTime(std::chrono::steady_clock::now()); + clone->PreCall(otel_context, {clock->Now(), true}); + clock->AdvanceTime(std::chrono::milliseconds(2)); + clone->ElementDelivery(otel_context, {clock->Now(), true}); + clone->PostCall(otel_context, client_context, + {clock->Now(), Status{StatusCode::kOk, "ok"}}); + clone->OnDone(otel_context, {clock->Now(), Status{StatusCode::kOk, "ok"}}); +} + +TEST(GetServerLatencyFromInitialMetadata, NonEmptyHeader) { + grpc::ClientContext client_context; + RpcMetadata server_metadata; + server_metadata.headers.emplace("server-timing", "gfet4t7; dur=10.5"); + SetServerMetadata(client_context, server_metadata); + + auto result = GetServerLatencyFromInitialMetadata(client_context); + ASSERT_TRUE(result); + EXPECT_THAT(result, Eq(10.5)); +} + +TEST(GetServerLatencyFromInitialMetadata, EmptyHeader) { + grpc::ClientContext client_context; + RpcMetadata server_metadata; + SetServerMetadata(client_context, server_metadata); + + auto result = GetServerLatencyFromInitialMetadata(client_context); + EXPECT_FALSE(result); +} + +TEST(GetServerLatencyFromInitialMetadata, MultipleDurValuesInHeader) { + grpc::ClientContext client_context; + RpcMetadata server_metadata; + server_metadata.headers.emplace("server-timing", + " gfet4t7; dur=2.1, gcp; dur=123"); + SetServerMetadata(client_context, server_metadata); + + auto result = GetServerLatencyFromInitialMetadata(client_context); + ASSERT_TRUE(result); + EXPECT_THAT(result, Eq(2.1)); +} + +TEST(GetServerLatencyFromInitialMetadata, NoGfePresent) { + grpc::ClientContext client_context; + RpcMetadata server_metadata; + server_metadata.headers.emplace("server-timing", "gcp; dur=123"); + SetServerMetadata(client_context, server_metadata); + + auto result = GetServerLatencyFromInitialMetadata(client_context); + EXPECT_FALSE(result); +} + +TEST(ServerLatency, SingleSuccess) { + auto mock_histogram = std::make_unique>(); + EXPECT_CALL( + *mock_histogram, + Record(A(), A(), + A())) + .WillOnce([](double value, + opentelemetry::common::KeyValueIterable const& attributes, + opentelemetry::context::Context const&) { + EXPECT_THAT(value, Eq(15.013)); + EXPECT_THAT( + MakeAttributesMap(attributes), + UnorderedElementsAre( + Pair("project_id", "my-project-id"), + Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), + Pair("table", "my-table"), Pair("zone", "my-zone"), + Pair("method", "my-method"), Pair("streaming", "my-streaming"), + Pair("status", "OK"), Pair("client_name", "my-client-name"), + Pair("client_uid", "my-client-uid"), + Pair("app_profile", "my-app-profile"))); + }); + + opentelemetry::nostd::shared_ptr mock_meter = + std::make_shared(); + EXPECT_CALL(*mock_meter, CreateDoubleHistogram) + .WillOnce([mock = std::move(mock_histogram)]( + opentelemetry::nostd::string_view name, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view) mutable { + EXPECT_THAT(name, Eq("server_latencies")); + return std::move(mock); + }); + + opentelemetry::nostd::shared_ptr mock_provider = + std::make_shared(); + EXPECT_CALL(*mock_provider, GetMeter) +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view, + opentelemetry::common::KeyValueIterable const*) mutable { +#else + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view) mutable { +#endif + EXPECT_THAT(scope, Eq("my-instrument-scope")); + EXPECT_THAT(scope_version, Eq("v1")); + return mock_meter; + }); + + ServerLatency server_latency("my-instrument-scope", mock_provider); + TableResourceLabels resource_labels{"my-project-id", "my-instance", + "my-table", "", ""}; + TableDataLabels data_labels{"my-method", "my-streaming", + "my-client-name", "my-client-uid", + "my-app-profile", ""}; + auto clone = server_latency.clone(resource_labels, data_labels); + + grpc::ClientContext client_context; + google::bigtable::v2::ResponseParams response_params; + response_params.set_cluster_id("my-cluster"); + response_params.set_zone_id("my-zone"); + RpcMetadata server_metadata; + server_metadata.headers.emplace("server-timing", "gfet4t7; dur=15.013"); + server_metadata.trailers.emplace("x-goog-ext-425905942-bin", + response_params.SerializeAsString()); + SetServerMetadata(client_context, server_metadata); + + auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); + auto clock = std::make_shared(); + + clone->PostCall(otel_context, client_context, + {clock->Now(), Status{StatusCode::kOk, "ok"}}); +} + +TEST(ServerLatency, UsesDefaultClusterAndZone) { + auto mock_histogram = std::make_unique>(); + EXPECT_CALL( + *mock_histogram, + Record(A(), A(), + A())) + .WillOnce([](double value, + opentelemetry::common::KeyValueIterable const& attributes, + opentelemetry::context::Context const&) { + EXPECT_THAT(value, Eq(15.013)); + EXPECT_THAT( + MakeAttributesMap(attributes), + UnorderedElementsAre( + Pair("project_id", "my-project-id"), + Pair("instance", "my-instance"), + Pair("cluster", ""), Pair("table", "my-table"), + Pair("zone", "global"), Pair("method", "my-method"), + Pair("streaming", "my-streaming"), Pair("status", "OK"), + Pair("client_name", "my-client-name"), + Pair("client_uid", "my-client-uid"), + Pair("app_profile", "my-app-profile"))); + }); + opentelemetry::nostd::shared_ptr mock_meter = + std::make_shared(); + EXPECT_CALL(*mock_meter, CreateDoubleHistogram) + .WillOnce([mock = std::move(mock_histogram)]( + opentelemetry::nostd::string_view name, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view) mutable { + EXPECT_THAT(name, Eq("server_latencies")); + return std::move(mock); + }); + + opentelemetry::nostd::shared_ptr mock_provider = + std::make_shared(); + EXPECT_CALL(*mock_provider, GetMeter) +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view, + opentelemetry::common::KeyValueIterable const*) mutable { +#else + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view) mutable { +#endif + EXPECT_THAT(scope, Eq("my-instrument-scope")); + EXPECT_THAT(scope_version, Eq("v1")); + return mock_meter; + }); + + ServerLatency server_latency("my-instrument-scope", mock_provider); + TableResourceLabels resource_labels{"my-project-id", "my-instance", + "my-table", "", ""}; + TableDataLabels data_labels{"my-method", "my-streaming", + "my-client-name", "my-client-uid", + "my-app-profile", ""}; + auto clone = server_latency.clone(resource_labels, data_labels); + + grpc::ClientContext client_context; + RpcMetadata server_metadata; + server_metadata.headers.emplace("server-timing", "gfet4t7; dur=15.013"); + SetServerMetadata(client_context, server_metadata); + + auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); + clone->PostCall( + otel_context, client_context, + {std::chrono::steady_clock::now(), Status{StatusCode::kOk, "ok"}}); +} + +TEST(ServerLatency, TwoAttempts) { + auto mock_histogram = std::make_unique>(); + EXPECT_CALL( + *mock_histogram, + Record(A(), A(), + A())) + .WillOnce([](double value, + opentelemetry::common::KeyValueIterable const& attributes, + opentelemetry::context::Context const&) { + EXPECT_THAT(value, Eq(15.0)); + EXPECT_THAT( + MakeAttributesMap(attributes), + UnorderedElementsAre( + Pair("project_id", "my-project-id"), + Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), + Pair("table", "my-table"), Pair("zone", "my-zone"), + Pair("method", "my-method"), Pair("streaming", "my-streaming"), + Pair("status", "OK"), Pair("client_name", "my-client-name"), + Pair("client_uid", "my-client-uid"), + Pair("app_profile", "my-app-profile"))); + }) + .WillOnce([](double value, + opentelemetry::common::KeyValueIterable const& attributes, + opentelemetry::context::Context const&) { + EXPECT_THAT(value, Eq(10.0)); + EXPECT_THAT( + MakeAttributesMap(attributes), + UnorderedElementsAre( + Pair("project_id", "my-project-id"), + Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), + Pair("table", "my-table"), Pair("zone", "my-zone"), + Pair("method", "my-method"), Pair("streaming", "my-streaming"), + Pair("status", "DEADLINE_EXCEEDED"), + Pair("client_name", "my-client-name"), + Pair("client_uid", "my-client-uid"), + Pair("app_profile", "my-app-profile"))); + }); + + opentelemetry::nostd::shared_ptr mock_meter = + std::make_shared(); + EXPECT_CALL(*mock_meter, CreateDoubleHistogram) + .WillOnce([mock = std::move(mock_histogram)]( + opentelemetry::nostd::string_view name, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view) mutable { + EXPECT_THAT(name, Eq("server_latencies")); + return std::move(mock); + }); + + opentelemetry::nostd::shared_ptr mock_provider = + std::make_shared(); + EXPECT_CALL(*mock_provider, GetMeter) +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view, + opentelemetry::common::KeyValueIterable const*) mutable { +#else + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view) mutable { +#endif + EXPECT_THAT(scope, Eq("my-instrument-scope")); + EXPECT_THAT(scope_version, Eq("v1")); + return mock_meter; + }); + + ServerLatency server_latency("my-instrument-scope", mock_provider); + TableResourceLabels resource_labels{"my-project-id", "my-instance", + "my-table", "", ""}; + TableDataLabels data_labels{"my-method", "my-streaming", + "my-client-name", "my-client-uid", + "my-app-profile", ""}; + auto clone = server_latency.clone(resource_labels, data_labels); + + grpc::ClientContext client_context; + google::bigtable::v2::ResponseParams response_params; + response_params.set_cluster_id("my-cluster"); + response_params.set_zone_id("my-zone"); + RpcMetadata server_metadata; + server_metadata.headers.emplace("server-timing", "gfet4t7; dur=15"); + server_metadata.trailers.emplace("x-goog-ext-425905942-bin", + response_params.SerializeAsString()); + SetServerMetadata(client_context, server_metadata); + + auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); + auto clock = std::make_shared(); + + clone->PostCall(otel_context, client_context, + {clock->Now(), Status{StatusCode::kOk, "ok"}}); + grpc::ClientContext client_context_two; + RpcMetadata server_metadata_two; + server_metadata_two.headers.emplace("server-timing", "gfet4t7; dur=10"); + server_metadata_two.trailers.emplace("x-goog-ext-425905942-bin", + response_params.SerializeAsString()); + SetServerMetadata(client_context_two, server_metadata_two); + clone->PostCall( + otel_context, client_context_two, + {clock->Now(), Status{StatusCode::kDeadlineExceeded, "timeout"}}); +} + +TEST(ServerLatency, NoServerTiming) { + auto mock_histogram = std::make_unique>(); + EXPECT_CALL( + *mock_histogram, + Record(A(), A(), + A())) + .Times(0); + + opentelemetry::nostd::shared_ptr mock_meter = + std::make_shared(); + EXPECT_CALL(*mock_meter, CreateDoubleHistogram) + .WillOnce([mock = std::move(mock_histogram)]( + opentelemetry::nostd::string_view name, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view) mutable { + EXPECT_THAT(name, Eq("server_latencies")); + return std::move(mock); + }); + + opentelemetry::nostd::shared_ptr mock_provider = + std::make_shared(); + EXPECT_CALL(*mock_provider, GetMeter) +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view, + opentelemetry::common::KeyValueIterable const*) mutable { +#else + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view) mutable { +#endif + EXPECT_THAT(scope, Eq("my-instrument-scope")); + EXPECT_THAT(scope_version, Eq("v1")); + return mock_meter; + }); + + ServerLatency server_latency("my-instrument-scope", mock_provider); + TableResourceLabels resource_labels{"my-project-id", "my-instance", + "my-table", "", ""}; + TableDataLabels data_labels{"my-method", "my-streaming", + "my-client-name", "my-client-uid", + "my-app-profile", ""}; + auto clone = server_latency.clone(resource_labels, data_labels); + + grpc::ClientContext client_context; + SetClusterZone(client_context); + auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); + auto clock = std::make_shared(); + clone->PostCall(otel_context, client_context, + {clock->Now(), Status{StatusCode::kOk, "ok"}}); +} + +TEST(ConnectivityErrorCount, MissingResourceLabels) { + auto mock_counter = std::make_unique>(); + EXPECT_CALL(*mock_counter, + Add(A(), + A(), + A())) + .WillOnce([](std::uint64_t value, + opentelemetry::common::KeyValueIterable const& attributes, + opentelemetry::context::Context const&) { + EXPECT_THAT(value, Eq(4)); + EXPECT_THAT( + MakeAttributesMap(attributes), + UnorderedElementsAre( + Pair("project_id", "my-project-id"), + Pair("instance", "my-instance"), + Pair("cluster", ""), Pair("table", "my-table"), + Pair("zone", "global"), Pair("method", "my-method"), + Pair("status", "OK"), Pair("client_name", "my-client-name"), + Pair("client_uid", "my-client-uid"), + Pair("app_profile", "my-app-profile"))); + }); + + opentelemetry::nostd::shared_ptr mock_meter = + std::make_shared(); + EXPECT_CALL(*mock_meter, CreateUInt64Counter) + .WillOnce([mock = std::move(mock_counter)]( + opentelemetry::nostd::string_view name, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view) mutable { + EXPECT_THAT(name, Eq("connectivity_error_count")); + return std::move(mock); + }); + + opentelemetry::nostd::shared_ptr mock_provider = + std::make_shared(); + EXPECT_CALL(*mock_provider, GetMeter) +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view, + opentelemetry::common::KeyValueIterable const*) mutable { +#else + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view) mutable { +#endif + EXPECT_THAT(scope, Eq("my-instrument-scope")); + EXPECT_THAT(scope_version, Eq("v1")); + return mock_meter; + }); + + ConnectivityErrorCount connectivity_error_count("my-instrument-scope", + mock_provider); + TableResourceLabels resource_labels{"my-project-id", "my-instance", + "my-table", "", ""}; + TableDataLabels data_labels{"my-method", "my-streaming", + "my-client-name", "my-client-uid", + "my-app-profile", ""}; + auto clone = connectivity_error_count.clone(resource_labels, data_labels); + + grpc::ClientContext client_context; + RpcMetadata server_metadata; + server_metadata.headers.emplace("server-timing", "gfet4t7; dur=15"); + SetServerMetadata(client_context, server_metadata); + auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); + auto clock = std::make_shared(); + clone->PostCall(otel_context, client_context, + {clock->Now(), Status{StatusCode::kOk, "ok"}}); + clock->AdvanceTime(std::chrono::microseconds(1234)); + clone->PostCall(otel_context, client_context, + {clock->Now(), Status{StatusCode::kOk, "ok"}}); + clock->AdvanceTime(std::chrono::microseconds(1234)); + clone->PostCall(otel_context, client_context, + {clock->Now(), Status{StatusCode::kOk, "ok"}}); + clock->AdvanceTime(std::chrono::microseconds(1234)); + clone->PostCall(otel_context, client_context, + {clock->Now(), Status{StatusCode::kOk, "ok"}}); + clone->OnDone(otel_context, {clock->Now(), Status{StatusCode::kOk, "ok"}}); +} + +TEST(ConnectivityErrorCount, Success) { + auto mock_counter = std::make_unique>(); + EXPECT_CALL(*mock_counter, + Add(A(), + A(), + A())) + .WillOnce([](std::uint64_t value, + opentelemetry::common::KeyValueIterable const& attributes, + opentelemetry::context::Context const&) { + EXPECT_THAT(value, Eq(0)); + EXPECT_THAT( + MakeAttributesMap(attributes), + UnorderedElementsAre( + Pair("project_id", "my-project-id"), + Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), + Pair("table", "my-table"), Pair("zone", "my-zone"), + Pair("method", "my-method"), Pair("status", "OK"), + Pair("client_name", "my-client-name"), + Pair("client_uid", "my-client-uid"), + Pair("app_profile", "my-app-profile"))); + }); + + opentelemetry::nostd::shared_ptr mock_meter = + std::make_shared(); + EXPECT_CALL(*mock_meter, CreateUInt64Counter) + .WillOnce([mock = std::move(mock_counter)]( + opentelemetry::nostd::string_view name, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view) mutable { + EXPECT_THAT(name, Eq("connectivity_error_count")); + return std::move(mock); + }); + + opentelemetry::nostd::shared_ptr mock_provider = + std::make_shared(); + EXPECT_CALL(*mock_provider, GetMeter) +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view, + opentelemetry::common::KeyValueIterable const*) mutable { +#else + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view) mutable { +#endif + EXPECT_THAT(scope, Eq("my-instrument-scope")); + EXPECT_THAT(scope_version, Eq("v1")); + return mock_meter; + }); + + ConnectivityErrorCount connectivity_error_count("my-instrument-scope", + mock_provider); + TableResourceLabels resource_labels{"my-project-id", "my-instance", + "my-table", "", ""}; + TableDataLabels data_labels{"my-method", "my-streaming", + "my-client-name", "my-client-uid", + "my-app-profile", ""}; + auto clone = connectivity_error_count.clone(resource_labels, data_labels); + + grpc::ClientContext client_context; + google::bigtable::v2::ResponseParams response_params; + response_params.set_cluster_id("my-cluster"); + response_params.set_zone_id("my-zone"); + RpcMetadata server_metadata; + server_metadata.headers.emplace("server-timing", "gfet4t7; dur=15"); + server_metadata.trailers.emplace("x-goog-ext-425905942-bin", + response_params.SerializeAsString()); + SetServerMetadata(client_context, server_metadata); + auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); + auto clock = std::make_shared(); + + clone->PostCall(otel_context, client_context, + {clock->Now(), Status{StatusCode::kOk, "ok"}}); + clone->OnDone(otel_context, {clock->Now(), Status{StatusCode::kOk, "ok"}}); +} + +TEST(ConnectivityErrorCount, OkAndMissingServerTiming) { + auto mock_counter = std::make_unique>(); + EXPECT_CALL(*mock_counter, + Add(A(), + A(), + A())) + .WillOnce([](std::uint64_t value, + opentelemetry::common::KeyValueIterable const& attributes, + opentelemetry::context::Context const&) { + EXPECT_THAT(value, Eq(1)); + EXPECT_THAT( + MakeAttributesMap(attributes), + UnorderedElementsAre( + Pair("project_id", "my-project-id"), + Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), + Pair("table", "my-table"), Pair("zone", "my-zone"), + Pair("method", "my-method"), Pair("status", "OK"), + Pair("client_name", "my-client-name"), + Pair("client_uid", "my-client-uid"), + Pair("app_profile", "my-app-profile"))); + }); + + opentelemetry::nostd::shared_ptr mock_meter = + std::make_shared(); + EXPECT_CALL(*mock_meter, CreateUInt64Counter) + .WillOnce([mock = std::move(mock_counter)]( + opentelemetry::nostd::string_view name, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view) mutable { + EXPECT_THAT(name, Eq("connectivity_error_count")); + return std::move(mock); + }); + + opentelemetry::nostd::shared_ptr mock_provider = + std::make_shared(); + EXPECT_CALL(*mock_provider, GetMeter) +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view, + opentelemetry::common::KeyValueIterable const*) mutable { +#else + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view) mutable { +#endif + EXPECT_THAT(scope, Eq("my-instrument-scope")); + EXPECT_THAT(scope_version, Eq("v1")); + return mock_meter; + }); + + ConnectivityErrorCount connectivity_error_count("my-instrument-scope", + mock_provider); + TableResourceLabels resource_labels{"my-project-id", "my-instance", + "my-table", "", ""}; + TableDataLabels data_labels{"my-method", "my-streaming", + "my-client-name", "my-client-uid", + "my-app-profile", ""}; + auto clone = connectivity_error_count.clone(resource_labels, data_labels); + + grpc::ClientContext client_context; + SetClusterZone(client_context); + auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); + auto clock = std::make_shared(); + clone->PostCall(otel_context, client_context, + {clock->Now(), Status{StatusCode::kOk, "ok"}}); + clone->OnDone(otel_context, {clock->Now(), Status{StatusCode::kOk, "ok"}}); +} + +TEST(ConnectivityErrorCount, DeadlineExceededAndMissingServerTiming) { + auto mock_counter = std::make_unique>(); + EXPECT_CALL(*mock_counter, + Add(A(), + A(), + A())) + .WillOnce([](std::uint64_t value, + opentelemetry::common::KeyValueIterable const& attributes, + opentelemetry::context::Context const&) { + EXPECT_THAT(value, Eq(0)); + EXPECT_THAT( + MakeAttributesMap(attributes), + UnorderedElementsAre( + Pair("project_id", "my-project-id"), + Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), + Pair("table", "my-table"), Pair("zone", "my-zone"), + Pair("method", "my-method"), + Pair("status", "DEADLINE_EXCEEDED"), + Pair("client_name", "my-client-name"), + Pair("client_uid", "my-client-uid"), + Pair("app_profile", "my-app-profile"))); + }); + + opentelemetry::nostd::shared_ptr mock_meter = + std::make_shared(); + EXPECT_CALL(*mock_meter, CreateUInt64Counter) + .WillOnce([mock = std::move(mock_counter)]( + opentelemetry::nostd::string_view name, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view) mutable { + EXPECT_THAT(name, Eq("connectivity_error_count")); + return std::move(mock); + }); + + opentelemetry::nostd::shared_ptr mock_provider = + std::make_shared(); + EXPECT_CALL(*mock_provider, GetMeter) +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view, + opentelemetry::common::KeyValueIterable const*) mutable { +#else + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view) mutable { +#endif + EXPECT_THAT(scope, Eq("my-instrument-scope")); + EXPECT_THAT(scope_version, Eq("v1")); + return mock_meter; + }); + + ConnectivityErrorCount connectivity_error_count("my-instrument-scope", + mock_provider); + TableResourceLabels resource_labels{"my-project-id", "my-instance", + "my-table", "", ""}; + TableDataLabels data_labels{"my-method", "my-streaming", + "my-client-name", "my-client-uid", + "my-app-profile", ""}; + auto clone = connectivity_error_count.clone(resource_labels, data_labels); + + grpc::ClientContext client_context; + SetClusterZone(client_context); + auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); + auto clock = std::make_shared(); + clone->PostCall( + otel_context, client_context, + {clock->Now(), Status{StatusCode::kDeadlineExceeded, "timeout"}}); + clone->OnDone(otel_context, {clock->Now(), Status{StatusCode::kOk, "ok"}}); +} +TEST(ApplicationBlockingLatency, Success) { + auto mock_histogram = std::make_unique>(); + EXPECT_CALL( + *mock_histogram, + Record(A(), A(), + A())) + .WillOnce([](double value, + opentelemetry::common::KeyValueIterable const& attributes, + opentelemetry::context::Context const&) { + EXPECT_THAT(value, Eq(1.234)); + EXPECT_THAT( + MakeAttributesMap(attributes), + UnorderedElementsAre( + Pair("project_id", "my-project-id"), + Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), + Pair("table", "my-table"), Pair("zone", "my-zone"), + Pair("method", "my-method"), + Pair("client_name", "my-client-name"), + Pair("client_uid", "my-client-uid"), + Pair("app_profile", "my-app-profile"))); + }); + + opentelemetry::nostd::shared_ptr mock_meter = + std::make_shared(); + EXPECT_CALL(*mock_meter, CreateDoubleHistogram) + .WillOnce([mock = std::move(mock_histogram)]( + opentelemetry::nostd::string_view name, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view) mutable { + EXPECT_THAT(name, Eq("application_latencies")); + return std::move(mock); + }); + + opentelemetry::nostd::shared_ptr mock_provider = + std::make_shared(); + EXPECT_CALL(*mock_provider, GetMeter) +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view, + opentelemetry::common::KeyValueIterable const*) mutable { +#else + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view) mutable { +#endif + EXPECT_THAT(scope, Eq("my-instrument-scope")); + EXPECT_THAT(scope_version, Eq("v1")); + return mock_meter; + }); + + ApplicationBlockingLatency application_blocking_latency("my-instrument-scope", + mock_provider); + TableResourceLabels resource_labels{"my-project-id", "my-instance", + "my-table", "", ""}; + TableDataLabels data_labels{"my-method", "my-streaming", + "my-client-name", "my-client-uid", + "my-app-profile", ""}; + auto clone = application_blocking_latency.clone(resource_labels, data_labels); + + grpc::ClientContext client_context; + SetClusterZone(client_context); + auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); + auto clock = std::make_shared(); + + clock->SetTime(std::chrono::steady_clock::now()); + clone->ElementDelivery(otel_context, {clock->Now(), true}); + clock->AdvanceTime(std::chrono::microseconds(1234)); + clone->ElementRequest(otel_context, {clock->Now()}); + clone->PostCall(otel_context, client_context, {clock->Now(), Status{}}); + clone->OnDone(otel_context, {clock->Now(), Status{}}); +} + +TEST(ApplicationBlockingLatency, StreamingData) { + auto mock_histogram = std::make_unique>(); + EXPECT_CALL( + *mock_histogram, + Record(A(), A(), + A())) + .WillOnce([](double value, + opentelemetry::common::KeyValueIterable const& attributes, + opentelemetry::context::Context const&) { + EXPECT_THAT(value, Eq(1.0)); + EXPECT_THAT( + MakeAttributesMap(attributes), + UnorderedElementsAre( + Pair("project_id", "my-project-id"), + Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), + Pair("table", "my-table"), Pair("zone", "my-zone"), + Pair("method", "my-method"), + Pair("client_name", "my-client-name"), + Pair("client_uid", "my-client-uid"), + Pair("app_profile", "my-app-profile"))); + }) + .WillOnce([](double value, + opentelemetry::common::KeyValueIterable const& attributes, + opentelemetry::context::Context const&) { + EXPECT_THAT(value, Eq(5.0)); + EXPECT_THAT( + MakeAttributesMap(attributes), + UnorderedElementsAre( + Pair("project_id", "my-project-id"), + Pair("instance", "my-instance"), Pair("cluster", "my-cluster"), + Pair("table", "my-table"), Pair("zone", "my-zone"), + Pair("method", "my-method"), + Pair("client_name", "my-client-name"), + Pair("client_uid", "my-client-uid"), + Pair("app_profile", "my-app-profile"))); + }); + + opentelemetry::nostd::shared_ptr mock_meter = + std::make_shared(); + EXPECT_CALL(*mock_meter, CreateDoubleHistogram) + .WillOnce([mock = std::move(mock_histogram)]( + opentelemetry::nostd::string_view name, + opentelemetry::nostd::string_view, + opentelemetry::nostd::string_view) mutable { + EXPECT_THAT(name, Eq("application_latencies")); + return std::move(mock); + }); + + opentelemetry::nostd::shared_ptr mock_provider = + std::make_shared(); + EXPECT_CALL(*mock_provider, GetMeter) +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view, + opentelemetry::common::KeyValueIterable const*) mutable { +#else + .WillOnce([&](opentelemetry::nostd::string_view scope, + opentelemetry::nostd::string_view scope_version, + opentelemetry::nostd::string_view) mutable { +#endif + EXPECT_THAT(scope, Eq("my-instrument-scope")); + EXPECT_THAT(scope_version, Eq("v1")); + return mock_meter; + }); + + ApplicationBlockingLatency application_blocking_latency("my-instrument-scope", + mock_provider); + TableResourceLabels resource_labels{"my-project-id", "my-instance", + "my-table", "", ""}; + TableDataLabels data_labels{"my-method", "my-streaming", + "my-client-name", "my-client-uid", + "my-app-profile", ""}; + auto clone = application_blocking_latency.clone(resource_labels, data_labels); + + grpc::ClientContext client_context; + SetClusterZone(client_context); + auto otel_context = opentelemetry::context::RuntimeContext::GetCurrent(); + auto clock = std::make_shared(); + + clock->SetTime(std::chrono::steady_clock::now()); + clone->ElementDelivery(otel_context, {clock->Now(), true}); + clock->AdvanceTime(std::chrono::milliseconds(1)); + clone->ElementRequest(otel_context, {clock->Now()}); + clock->AdvanceTime(std::chrono::milliseconds(10)); + clone->ElementDelivery(otel_context, {clock->Now(), true}); + clock->AdvanceTime(std::chrono::milliseconds(5)); + clone->ElementRequest(otel_context, {clock->Now()}); + clone->PostCall(otel_context, client_context, {clock->Now(), Status{}}); + clone->OnDone(otel_context, {clock->Now(), Status{}}); +} + +} // namespace +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace bigtable_internal +} // namespace cloud +} // namespace google + +#endif // GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS +#endif // !defined(_WIN32) && !defined(__APPLE_)