From be1bdc77075cb3c2efdc27141e46cd33fffc0371 Mon Sep 17 00:00:00 2001 From: Richard Belleville Date: Thu, 28 Jan 2021 02:20:33 +0000 Subject: [PATCH 1/7] Report RPC statuses --- src/proto/grpc/testing/messages.proto | 25 ++++++++++++++++--- .../interop/xds_interop_client.py | 9 +++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/proto/grpc/testing/messages.proto b/src/proto/grpc/testing/messages.proto index 5acc2f0eadb..559876ed7c0 100644 --- a/src/proto/grpc/testing/messages.proto +++ b/src/proto/grpc/testing/messages.proto @@ -219,11 +219,27 @@ message LoadBalancerAccumulatedStatsRequest {} // Accumulated stats for RPCs sent by a test client. message LoadBalancerAccumulatedStatsResponse { // The total number of RPCs have ever issued for each type. - map num_rpcs_started_by_method = 1; + // Deprecated: use stats_per_method.rpcs_started instead. + map num_rpcs_started_by_method = 1 [deprecated = true]; // The total number of RPCs have ever completed successfully for each type. - map num_rpcs_succeeded_by_method = 2; + // Deprecated: use stats_per_method.result instead. + map num_rpcs_succeeded_by_method = 2 [deprecated = true]; // The total number of RPCs have ever failed for each type. - map num_rpcs_failed_by_method = 3; + // Deprecated: use stats_per_method.result instead. + map num_rpcs_failed_by_method = 3 [deprecated = true]; + + message MethodStats { + // The number of RPCs that were started for this method. + int32 rpcs_started = 1; + + // The number of RPCs that completed with each status for this method. The + // key is the integral value of a google.rpc.Code; the value is the count. + map result = 2; + } + + // Per-method RPC statistics. The key is the RpcType in string form; e.g. + // 'EMPTY_CALL' or 'UNARY_CALL' + map stats_per_method = 4; } // Configurations for a test client. @@ -245,6 +261,9 @@ message ClientConfigureRequest { repeated RpcType types = 1; // The collection of custom metadata to be attached to RPCs sent by the client. repeated Metadata metadata = 2; + // The deadline to use, in seconds, for all RPCs. If unset or zero, the + // client will use the default from the command-line. + int32 timeout_sec = 3; } // Response for updating a test client's configuration. diff --git a/src/python/grpcio_tests/tests_py3_only/interop/xds_interop_client.py b/src/python/grpcio_tests/tests_py3_only/interop/xds_interop_client.py index 138693890c6..640cb7dbd31 100644 --- a/src/python/grpcio_tests/tests_py3_only/interop/xds_interop_client.py +++ b/src/python/grpcio_tests/tests_py3_only/interop/xds_interop_client.py @@ -118,6 +118,9 @@ _global_rpcs_started: Mapping[str, int] = collections.defaultdict(int) _global_rpcs_succeeded: Mapping[str, int] = collections.defaultdict(int) _global_rpcs_failed: Mapping[str, int] = collections.defaultdict(int) +# Mapping[method, Mapping[status_code, count]] +_global_rpc_statuses: Mapping[str, Mapping[int, int]] = collections.defaultdict(lambda: collections.defaultdict(int)) + def _handle_sigint(sig, frame) -> None: _stop_event.set() @@ -163,6 +166,9 @@ class _LoadBalancerStatsServicer(test_pb2_grpc.LoadBalancerStatsServiceServicer caps_method] = _global_rpcs_succeeded[method] response.num_rpcs_failed_by_method[ caps_method] = _global_rpcs_failed[method] + response.stats_per_method[caps_method].rpcs_started = _global_rpcs_started[method] + for code, count in _global_rpc_statuses[method].items(): + response.stats_per_method[caps_method].result[code] = count logger.info("Returning cumulative stats response.") return response @@ -189,6 +195,7 @@ def _on_rpc_done(rpc_id: int, future: grpc.Future, method: str, print_response: bool) -> None: exception = future.exception() hostname = "" + _global_rpc_statuses[method][future.code().value[0]] += 1 if exception is not None: with _global_lock: _global_rpcs_failed[method] += 1 @@ -399,6 +406,8 @@ def parse_rpc_arg(rpc_arg: str) -> Sequence[str]: ", ".join(_SUPPORTED_METHODS))) return methods +resp = messages_pb2.LoadBalancerAccumulatedStatsResponse() +resp.stats_per_method["/method1"].result[2] = 11 if __name__ == "__main__": parser = argparse.ArgumentParser( From 791626a28f738c663d36abd3e6f0fc6e6cf0eb32 Mon Sep 17 00:00:00 2001 From: Richard Belleville Date: Thu, 28 Jan 2021 02:48:48 +0000 Subject: [PATCH 2/7] Accept programmatic configuration of RPC timeout --- .../tests_py3_only/interop/xds_interop_client.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/python/grpcio_tests/tests_py3_only/interop/xds_interop_client.py b/src/python/grpcio_tests/tests_py3_only/interop/xds_interop_client.py index 640cb7dbd31..07861a3c3de 100644 --- a/src/python/grpcio_tests/tests_py3_only/interop/xds_interop_client.py +++ b/src/python/grpcio_tests/tests_py3_only/interop/xds_interop_client.py @@ -324,13 +324,22 @@ class _XdsUpdateClientConfigureServicer( metadata = ((md.key, md.value) for md in request.metadata if md.type == method_enum) + # For backward compatibility, do not change timeout when we + # receive a default value timeout. + if request.timeout_secs == 0: + timeout_sec = channel_config.rpc_timeout_sec + else: + timeout_sec = request.timeout_sec else: qps = 0 metadata = () + # Leave timeout unchanged for backward compatibility. + timeout_sec = channel_config.rpc_timeout_sec channel_config = self._per_method_configs[method] with channel_config.condition: channel_config.qps = qps channel_config.metadata = list(metadata) + channel_config.rpc_timeout_sec = timeout_sec channel_config.condition.notify_all() return messages_pb2.ClientConfigureResponse() From a07674de5c0fc5634ea259f41457c29fea02ae49 Mon Sep 17 00:00:00 2001 From: Richard Belleville Date: Fri, 5 Feb 2021 20:01:29 +0000 Subject: [PATCH 3/7] Conform to updated test spec --- .../interop/xds_interop_client.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/python/grpcio_tests/tests_py3_only/interop/xds_interop_client.py b/src/python/grpcio_tests/tests_py3_only/interop/xds_interop_client.py index f7b75c3481b..db9a37dbf03 100644 --- a/src/python/grpcio_tests/tests_py3_only/interop/xds_interop_client.py +++ b/src/python/grpcio_tests/tests_py3_only/interop/xds_interop_client.py @@ -54,6 +54,11 @@ _METHOD_STR_TO_ENUM = { "EmptyCall": messages_pb2.ClientConfigureRequest.EMPTY_CALL, } +_METHOD_CAMEL_TO_FULL_PATH = { + "UnaryCall": "/grpc.testing.TestService/UnaryCall", + "EmptyCall": "/grpc.testing.TestService/EmptyCall", +} + _METHOD_ENUM_TO_STR = {v: k for k, v in _METHOD_STR_TO_ENUM.items()} PerMethodMetadataType = Mapping[str, Sequence[Tuple[str, str]]] @@ -160,16 +165,16 @@ class _LoadBalancerStatsServicer(test_pb2_grpc.LoadBalancerStatsServiceServicer response = messages_pb2.LoadBalancerAccumulatedStatsResponse() with _global_lock: for method in _SUPPORTED_METHODS: - caps_method = _METHOD_CAMEL_TO_CAPS_SNAKE[method] + method_path = _METHOD_CAMEL_TO_FULL_PATH[method] response.num_rpcs_started_by_method[ - caps_method] = _global_rpcs_started[method] + method_path] = _global_rpcs_started[method] response.num_rpcs_succeeded_by_method[ - caps_method] = _global_rpcs_succeeded[method] + method_path] = _global_rpcs_succeeded[method] response.num_rpcs_failed_by_method[ - caps_method] = _global_rpcs_failed[method] - response.stats_per_method[caps_method].rpcs_started = _global_rpcs_started[method] + method_path] = _global_rpcs_failed[method] + response.stats_per_method[method_path].rpcs_started = _global_rpcs_started[method] for code, count in _global_rpc_statuses[method].items(): - response.stats_per_method[caps_method].result[code] = count + response.stats_per_method[method_path].result[code] = count logger.info("Returning cumulative stats response.") return response @@ -417,9 +422,6 @@ def parse_rpc_arg(rpc_arg: str) -> Sequence[str]: ", ".join(_SUPPORTED_METHODS))) return methods -resp = messages_pb2.LoadBalancerAccumulatedStatsResponse() -resp.stats_per_method["/method1"].result[2] = 11 - if __name__ == "__main__": parser = argparse.ArgumentParser( description='Run Python XDS interop client.') From 8960ddea249b0a2499537f1b40e45becedc8a5ad Mon Sep 17 00:00:00 2001 From: Richard Belleville Date: Fri, 5 Feb 2021 20:07:47 +0000 Subject: [PATCH 4/7] Yapf --- .../tests_py3_only/interop/xds_interop_client.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/python/grpcio_tests/tests_py3_only/interop/xds_interop_client.py b/src/python/grpcio_tests/tests_py3_only/interop/xds_interop_client.py index db9a37dbf03..15e84f56b04 100644 --- a/src/python/grpcio_tests/tests_py3_only/interop/xds_interop_client.py +++ b/src/python/grpcio_tests/tests_py3_only/interop/xds_interop_client.py @@ -124,7 +124,8 @@ _global_rpcs_succeeded: Mapping[str, int] = collections.defaultdict(int) _global_rpcs_failed: Mapping[str, int] = collections.defaultdict(int) # Mapping[method, Mapping[status_code, count]] -_global_rpc_statuses: Mapping[str, Mapping[int, int]] = collections.defaultdict(lambda: collections.defaultdict(int)) +_global_rpc_statuses: Mapping[str, Mapping[int, int]] = collections.defaultdict( + lambda: collections.defaultdict(int)) def _handle_sigint(sig, frame) -> None: @@ -172,7 +173,8 @@ class _LoadBalancerStatsServicer(test_pb2_grpc.LoadBalancerStatsServiceServicer method_path] = _global_rpcs_succeeded[method] response.num_rpcs_failed_by_method[ method_path] = _global_rpcs_failed[method] - response.stats_per_method[method_path].rpcs_started = _global_rpcs_started[method] + response.stats_per_method[ + method_path].rpcs_started = _global_rpcs_started[method] for code, count in _global_rpc_statuses[method].items(): response.stats_per_method[method_path].result[code] = count logger.info("Returning cumulative stats response.") @@ -422,6 +424,7 @@ def parse_rpc_arg(rpc_arg: str) -> Sequence[str]: ", ".join(_SUPPORTED_METHODS))) return methods + if __name__ == "__main__": parser = argparse.ArgumentParser( description='Run Python XDS interop client.') From b47279d923475c2f5a95b94ae9482b3547900767 Mon Sep 17 00:00:00 2001 From: Richard Belleville Date: Fri, 5 Feb 2021 20:31:56 +0000 Subject: [PATCH 5/7] Clean up --- .../tests_py3_only/interop/xds_interop_client.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/python/grpcio_tests/tests_py3_only/interop/xds_interop_client.py b/src/python/grpcio_tests/tests_py3_only/interop/xds_interop_client.py index 15e84f56b04..0382007f208 100644 --- a/src/python/grpcio_tests/tests_py3_only/interop/xds_interop_client.py +++ b/src/python/grpcio_tests/tests_py3_only/interop/xds_interop_client.py @@ -44,11 +44,6 @@ _SUPPORTED_METHODS = ( "EmptyCall", ) -_METHOD_CAMEL_TO_CAPS_SNAKE = { - "UnaryCall": "UNARY_CALL", - "EmptyCall": "EMPTY_CALL", -} - _METHOD_STR_TO_ENUM = { "UnaryCall": messages_pb2.ClientConfigureRequest.UNARY_CALL, "EmptyCall": messages_pb2.ClientConfigureRequest.EMPTY_CALL, @@ -328,6 +323,7 @@ class _XdsUpdateClientConfigureServicer( method_strs = (_METHOD_ENUM_TO_STR[t] for t in request.types) for method in _SUPPORTED_METHODS: method_enum = _METHOD_STR_TO_ENUM[method] + channel_config = self._per_method_configs[method] if method in method_strs: qps = self._qps metadata = ((md.key, md.value) @@ -344,7 +340,6 @@ class _XdsUpdateClientConfigureServicer( metadata = () # Leave timeout unchanged for backward compatibility. timeout_sec = channel_config.rpc_timeout_sec - channel_config = self._per_method_configs[method] with channel_config.condition: channel_config.qps = qps channel_config.metadata = list(metadata) From 3cf0266843700b8a12004bff1fd63099b844c18c Mon Sep 17 00:00:00 2001 From: Richard Belleville Date: Sat, 6 Feb 2021 00:15:22 +0000 Subject: [PATCH 6/7] And back to enums again --- src/proto/grpc/testing/messages.proto | 4 ++-- .../interop/xds_interop_client.py | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/proto/grpc/testing/messages.proto b/src/proto/grpc/testing/messages.proto index cfb1222ac82..559876ed7c0 100644 --- a/src/proto/grpc/testing/messages.proto +++ b/src/proto/grpc/testing/messages.proto @@ -237,8 +237,8 @@ message LoadBalancerAccumulatedStatsResponse { map result = 2; } - // Per-method RPC statistics. The key is the full method path; i.e. - // "/proto.package.ServiceName/MethodName". + // Per-method RPC statistics. The key is the RpcType in string form; e.g. + // 'EMPTY_CALL' or 'UNARY_CALL' map stats_per_method = 4; } diff --git a/src/python/grpcio_tests/tests_py3_only/interop/xds_interop_client.py b/src/python/grpcio_tests/tests_py3_only/interop/xds_interop_client.py index 0382007f208..7580d7ff70e 100644 --- a/src/python/grpcio_tests/tests_py3_only/interop/xds_interop_client.py +++ b/src/python/grpcio_tests/tests_py3_only/interop/xds_interop_client.py @@ -49,9 +49,9 @@ _METHOD_STR_TO_ENUM = { "EmptyCall": messages_pb2.ClientConfigureRequest.EMPTY_CALL, } -_METHOD_CAMEL_TO_FULL_PATH = { - "UnaryCall": "/grpc.testing.TestService/UnaryCall", - "EmptyCall": "/grpc.testing.TestService/EmptyCall", +_METHOD_CAMEL_TO_CAPS_SNAKE = { + "UnaryCall": "UNARY_CALL", + "EmptyCall": "EMPTY_CALL", } _METHOD_ENUM_TO_STR = {v: k for k, v in _METHOD_STR_TO_ENUM.items()} @@ -161,17 +161,17 @@ class _LoadBalancerStatsServicer(test_pb2_grpc.LoadBalancerStatsServiceServicer response = messages_pb2.LoadBalancerAccumulatedStatsResponse() with _global_lock: for method in _SUPPORTED_METHODS: - method_path = _METHOD_CAMEL_TO_FULL_PATH[method] + caps_method = _METHOD_CAMEL_TO_CAPS_SNAKE[method] response.num_rpcs_started_by_method[ - method_path] = _global_rpcs_started[method] + caps_method] = _global_rpcs_started[method] response.num_rpcs_succeeded_by_method[ - method_path] = _global_rpcs_succeeded[method] + caps_method] = _global_rpcs_succeeded[method] response.num_rpcs_failed_by_method[ - method_path] = _global_rpcs_failed[method] + caps_method] = _global_rpcs_failed[method] response.stats_per_method[ - method_path].rpcs_started = _global_rpcs_started[method] + caps_method].rpcs_started = _global_rpcs_started[method] for code, count in _global_rpc_statuses[method].items(): - response.stats_per_method[method_path].result[code] = count + response.stats_per_method[caps_method].result[code] = count logger.info("Returning cumulative stats response.") return response From 95b06a7bd4f8e032cc63c1282b8cb939e72f162a Mon Sep 17 00:00:00 2001 From: Richard Belleville Date: Sat, 6 Feb 2021 01:20:27 +0000 Subject: [PATCH 7/7] Avoid dancing diff --- .../tests_py3_only/interop/xds_interop_client.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/python/grpcio_tests/tests_py3_only/interop/xds_interop_client.py b/src/python/grpcio_tests/tests_py3_only/interop/xds_interop_client.py index 7580d7ff70e..99002d4376b 100644 --- a/src/python/grpcio_tests/tests_py3_only/interop/xds_interop_client.py +++ b/src/python/grpcio_tests/tests_py3_only/interop/xds_interop_client.py @@ -44,16 +44,16 @@ _SUPPORTED_METHODS = ( "EmptyCall", ) -_METHOD_STR_TO_ENUM = { - "UnaryCall": messages_pb2.ClientConfigureRequest.UNARY_CALL, - "EmptyCall": messages_pb2.ClientConfigureRequest.EMPTY_CALL, -} - _METHOD_CAMEL_TO_CAPS_SNAKE = { "UnaryCall": "UNARY_CALL", "EmptyCall": "EMPTY_CALL", } +_METHOD_STR_TO_ENUM = { + "UnaryCall": messages_pb2.ClientConfigureRequest.UNARY_CALL, + "EmptyCall": messages_pb2.ClientConfigureRequest.EMPTY_CALL, +} + _METHOD_ENUM_TO_STR = {v: k for k, v in _METHOD_STR_TO_ENUM.items()} PerMethodMetadataType = Mapping[str, Sequence[Tuple[str, str]]]