mirror of https://github.com/grpc/grpc.git
parent
c65bad469b
commit
1ee45615e2
9 changed files with 558 additions and 21 deletions
@ -0,0 +1,27 @@ |
|||||||
|
# Copyright 2020 The gRPC Authors |
||||||
|
# |
||||||
|
# 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 |
||||||
|
# |
||||||
|
# http://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. |
||||||
|
|
||||||
|
package( |
||||||
|
default_testonly = 1, |
||||||
|
default_visibility = ["//visibility:public"], |
||||||
|
) |
||||||
|
|
||||||
|
py_library( |
||||||
|
name = "histogram", |
||||||
|
srcs = ["histogram.py"], |
||||||
|
srcs_version = "PY2AND3", |
||||||
|
deps = [ |
||||||
|
"//src/proto/grpc/testing:stats_py_pb2", |
||||||
|
], |
||||||
|
) |
@ -0,0 +1,136 @@ |
|||||||
|
# Copyright 2020 The gRPC Authors |
||||||
|
# |
||||||
|
# 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 |
||||||
|
# |
||||||
|
# http://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. |
||||||
|
"""The Python AsyncIO Benchmark Clients.""" |
||||||
|
|
||||||
|
import abc |
||||||
|
import asyncio |
||||||
|
import time |
||||||
|
|
||||||
|
import grpc |
||||||
|
from grpc.experimental import aio |
||||||
|
|
||||||
|
from src.proto.grpc.testing import (benchmark_service_pb2_grpc, control_pb2, |
||||||
|
messages_pb2) |
||||||
|
from tests.qps import histogram |
||||||
|
from tests.unit import resources |
||||||
|
|
||||||
|
|
||||||
|
class GenericStub(object): |
||||||
|
|
||||||
|
def __init__(self, channel: aio.Channel): |
||||||
|
self.UnaryCall = channel.unary_unary( |
||||||
|
'/grpc.testing.BenchmarkService/UnaryCall') |
||||||
|
self.StreamingCall = channel.stream_stream( |
||||||
|
'/grpc.testing.BenchmarkService/StreamingCall') |
||||||
|
|
||||||
|
|
||||||
|
class BenchmarkClient(abc.ABC): |
||||||
|
"""Benchmark client interface that exposes a non-blocking send_request().""" |
||||||
|
|
||||||
|
def __init__(self, address: str, config: control_pb2.ClientConfig, hist: histogram.Histogram): |
||||||
|
# Creates the channel |
||||||
|
if config.HasField('security_params'): |
||||||
|
channel_credentials = grpc.ssl_channel_credentials( |
||||||
|
resources.test_root_certificates()) |
||||||
|
self._channel = aio.secure_channel(address, channel_credentials, (( |
||||||
|
'grpc.ssl_target_name_override', |
||||||
|
config.security_params.server_host_override, |
||||||
|
),)) |
||||||
|
else: |
||||||
|
self._channel = aio.insecure_channel(address) |
||||||
|
|
||||||
|
# Creates the stub |
||||||
|
if config.payload_config.WhichOneof('payload') == 'simple_params': |
||||||
|
self._generic = False |
||||||
|
self._stub = benchmark_service_pb2_grpc.BenchmarkServiceStub( |
||||||
|
channel) |
||||||
|
payload = messages_pb2.Payload( |
||||||
|
body='\0' * config.payload_config.simple_params.req_size) |
||||||
|
self._request = messages_pb2.SimpleRequest( |
||||||
|
payload=payload, |
||||||
|
response_size=config.payload_config.simple_params.resp_size) |
||||||
|
else: |
||||||
|
self._generic = True |
||||||
|
self._stub = GenericStub(channel) |
||||||
|
self._request = '\0' * config.payload_config.bytebuf_params.req_size |
||||||
|
|
||||||
|
self._hist = hist |
||||||
|
self._response_callbacks = [] |
||||||
|
self._concurrency = config.outstanding_rpcs_per_channel |
||||||
|
|
||||||
|
async def run(self) -> None: |
||||||
|
await aio.channel_ready(self._channel) |
||||||
|
|
||||||
|
async def stop(self) -> None: |
||||||
|
await self._channel.close() |
||||||
|
|
||||||
|
def _record_query_time(self, query_time: float) -> None: |
||||||
|
self._hist.add(query_time * 1e9) |
||||||
|
|
||||||
|
|
||||||
|
class UnaryAsyncBenchmarkClient(BenchmarkClient): |
||||||
|
|
||||||
|
def __init__(self, address: str, config: control_pb2.ClientConfig, hist: histogram.Histogram): |
||||||
|
super().__init__(address, config, hist) |
||||||
|
self._running = None |
||||||
|
self._stopped = asyncio.Event() |
||||||
|
|
||||||
|
async def _send_request(self): |
||||||
|
start_time = time.time() |
||||||
|
await self._stub.UnaryCall(self._request) |
||||||
|
self._record_query_time(self, time.time() - start_time) |
||||||
|
|
||||||
|
async def _infinite_sender(self) -> None: |
||||||
|
while self._running: |
||||||
|
await self._send_request() |
||||||
|
|
||||||
|
async def run(self) -> None: |
||||||
|
await super().run() |
||||||
|
self._running = True |
||||||
|
senders = (self._infinite_sender() for _ in range(self._concurrency)) |
||||||
|
await asyncio.wait(senders) |
||||||
|
self._stopped.set() |
||||||
|
|
||||||
|
async def stop(self) -> None: |
||||||
|
self._running = False |
||||||
|
await self._stopped.wait() |
||||||
|
await super().stop() |
||||||
|
|
||||||
|
|
||||||
|
class StreamingAsyncBenchmarkClient(BenchmarkClient): |
||||||
|
|
||||||
|
def __init__(self, address: str, config: control_pb2.ClientConfig, hist: histogram.Histogram): |
||||||
|
super().__init__(address, config, hist) |
||||||
|
self._running = None |
||||||
|
self._stopped = asyncio.Event() |
||||||
|
|
||||||
|
async def _one_streamming_call(self): |
||||||
|
call = self._stub.StreamingCall() |
||||||
|
while self._running: |
||||||
|
start_time = time.time() |
||||||
|
await call.write(self._request) |
||||||
|
await call.read() |
||||||
|
self._record_query_time(self, time.time() - start_time) |
||||||
|
|
||||||
|
async def run(self): |
||||||
|
await super().run() |
||||||
|
self._running = True |
||||||
|
senders = (self._one_streamming_call() for _ in range(self._concurrency)) |
||||||
|
await asyncio.wait(senders) |
||||||
|
self._stopped.set() |
||||||
|
|
||||||
|
async def stop(self): |
||||||
|
self._running = False |
||||||
|
await self._stopped.wait() |
||||||
|
await super().stop() |
@ -0,0 +1,55 @@ |
|||||||
|
# Copyright 2020 The gRPC Authors |
||||||
|
# |
||||||
|
# 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 |
||||||
|
# |
||||||
|
# http://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. |
||||||
|
"""The Python AsyncIO Benchmark Servicers.""" |
||||||
|
|
||||||
|
import asyncio |
||||||
|
import logging |
||||||
|
import unittest |
||||||
|
|
||||||
|
from grpc.experimental import aio |
||||||
|
|
||||||
|
from src.proto.grpc.testing import benchmark_service_pb2_grpc, messages_pb2 |
||||||
|
|
||||||
|
|
||||||
|
class BenchmarkServicer(benchmark_service_pb2_grpc.BenchmarkServiceServicer): |
||||||
|
|
||||||
|
async def UnaryCall(self, request, unused_context): |
||||||
|
payload = messages_pb2.Payload(body=b'\0' * request.response_size) |
||||||
|
return messages_pb2.SimpleResponse(payload=payload) |
||||||
|
|
||||||
|
async def StreamingFromServer(self, request, unused_context): |
||||||
|
payload = messages_pb2.Payload(body=b'\0' * request.response_size) |
||||||
|
# Sends response at full capacity! |
||||||
|
while True: |
||||||
|
yield messages_pb2.SimpleResponse(payload=payload) |
||||||
|
|
||||||
|
async def StreamingCall(self, request_iterator, unused_context): |
||||||
|
payload = messages_pb2.Payload(body='\0' * request.response_size) |
||||||
|
async for request in request_iterator: |
||||||
|
yield messages_pb2.SimpleResponse(payload=payload) |
||||||
|
|
||||||
|
|
||||||
|
class GenericBenchmarkServicer(benchmark_service_pb2_grpc.BenchmarkServiceServicer |
||||||
|
): |
||||||
|
"""Generic (no-codec) Server implementation for the Benchmark service.""" |
||||||
|
|
||||||
|
def __init__(self, resp_size): |
||||||
|
self._response = '\0' * resp_size |
||||||
|
|
||||||
|
async def UnaryCall(self, unused_request, unused_context): |
||||||
|
return self._response |
||||||
|
|
||||||
|
async def StreamingCall(self, request_iterator, unused_context): |
||||||
|
async for request in request_iterator: |
||||||
|
yield self._response |
@ -0,0 +1,53 @@ |
|||||||
|
# Copyright 2020 The gRPC Authors |
||||||
|
# |
||||||
|
# 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 |
||||||
|
# |
||||||
|
# http://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. |
||||||
|
|
||||||
|
import argparse |
||||||
|
import asyncio |
||||||
|
import logging |
||||||
|
|
||||||
|
from grpc.experimental import aio |
||||||
|
|
||||||
|
from src.proto.grpc.testing import worker_service_pb2_grpc |
||||||
|
from tests_aio.benchmark import worker_servicer |
||||||
|
|
||||||
|
|
||||||
|
async def run_worker_server(port: int) -> None: |
||||||
|
aio.init_grpc_aio() |
||||||
|
server = aio.server() |
||||||
|
|
||||||
|
servicer = worker_servicer.WorkerServicer() |
||||||
|
worker_service_pb2_grpc.add_WorkerServiceServicer_to_server( |
||||||
|
servicer, server) |
||||||
|
|
||||||
|
server.add_insecure_port('[::]:{}'.format(port)) |
||||||
|
|
||||||
|
await server.start() |
||||||
|
|
||||||
|
await servicer.wait_for_quit() |
||||||
|
await server.stop(None) |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__': |
||||||
|
logging.basicConfig(level=logging.INFO) |
||||||
|
parser = argparse.ArgumentParser( |
||||||
|
description='gRPC Python performance testing worker') |
||||||
|
parser.add_argument('--driver_port', |
||||||
|
type=int, |
||||||
|
dest='port', |
||||||
|
help='The port the worker should listen on') |
||||||
|
args = parser.parse_args() |
||||||
|
|
||||||
|
asyncio.get_event_loop().run_until_complete( |
||||||
|
run_worker_server(args.port) |
||||||
|
) |
@ -0,0 +1,168 @@ |
|||||||
|
# Copyright 2020 The gRPC Authors |
||||||
|
# |
||||||
|
# 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 |
||||||
|
# |
||||||
|
# http://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. |
||||||
|
|
||||||
|
import asyncio |
||||||
|
import multiprocessing |
||||||
|
import time |
||||||
|
from typing import Tuple |
||||||
|
|
||||||
|
import grpc |
||||||
|
from grpc.experimental import aio |
||||||
|
|
||||||
|
from src.proto.grpc.testing import (benchmark_service_pb2_grpc, control_pb2, |
||||||
|
stats_pb2, worker_service_pb2_grpc) |
||||||
|
from tests.qps import histogram |
||||||
|
from tests.unit import resources |
||||||
|
from tests_aio.benchmark import benchmark_client, benchmark_servicer |
||||||
|
|
||||||
|
_NUM_CORES = multiprocessing.cpu_count() |
||||||
|
_NUM_CORE_PYTHON_CAN_USE = 1 |
||||||
|
|
||||||
|
|
||||||
|
def _get_server_status(start_time: float, end_time: float, port: int) -> control_pb2.ServerStatus: |
||||||
|
end_time = time.time() |
||||||
|
elapsed_time = end_time - start_time |
||||||
|
stats = stats_pb2.ServerStats(time_elapsed=elapsed_time, |
||||||
|
time_user=elapsed_time, |
||||||
|
time_system=elapsed_time) |
||||||
|
return control_pb2.ServerStatus(stats=stats, port=port, cores=_NUM_CORE_PYTHON_CAN_USE) |
||||||
|
|
||||||
|
|
||||||
|
def _create_server(config: control_pb2.ServerConfig) -> Tuple[aio.Server, int]: |
||||||
|
if config.async_server_threads != 1: |
||||||
|
logging.warning('config.async_server_threads [%d] != 1', config.async_server_threads) |
||||||
|
|
||||||
|
server = aio.server() |
||||||
|
if config.server_type == control_pb2.ASYNC_SERVER: |
||||||
|
servicer = benchmark_servicer.BenchmarkServicer() |
||||||
|
benchmark_service_pb2_grpc.add_BenchmarkServiceServicer_to_server( |
||||||
|
servicer, server) |
||||||
|
elif config.server_type == control_pb2.ASYNC_GENERIC_SERVER: |
||||||
|
resp_size = config.payload_config.bytebuf_params.resp_size |
||||||
|
servicer = benchmark_servicer.GenericBenchmarkServicer(resp_size) |
||||||
|
method_implementations = { |
||||||
|
'StreamingCall': |
||||||
|
grpc.stream_stream_rpc_method_handler(servicer.StreamingCall |
||||||
|
), |
||||||
|
'UnaryCall': |
||||||
|
grpc.unary_unary_rpc_method_handler(servicer.UnaryCall), |
||||||
|
} |
||||||
|
handler = grpc.method_handlers_generic_handler( |
||||||
|
'grpc.testing.BenchmarkService', method_implementations) |
||||||
|
server.add_generic_rpc_handlers((handler,)) |
||||||
|
else: |
||||||
|
raise NotImplementedError('Unsupported server type {}'.format( |
||||||
|
config.server_type)) |
||||||
|
|
||||||
|
if config.HasField('security_params'): # Use SSL |
||||||
|
server_creds = grpc.ssl_server_credentials( |
||||||
|
((resources.private_key(), resources.certificate_chain()),)) |
||||||
|
port = server.add_secure_port('[::]:{}'.format(config.port), |
||||||
|
server_creds) |
||||||
|
else: |
||||||
|
port = server.add_insecure_port('[::]:{}'.format(config.port)) |
||||||
|
|
||||||
|
return server, port |
||||||
|
|
||||||
|
|
||||||
|
def _get_client_status(start_time: float, end_time: float, qps_data: histogram.Histogram) -> control_pb2.ClientStatus: |
||||||
|
latencies = qps_data.get_data() |
||||||
|
end_time = time.time() |
||||||
|
elapsed_time = end_time - start_time |
||||||
|
stats = stats_pb2.ClientStats(latencies=latencies, |
||||||
|
time_elapsed=elapsed_time, |
||||||
|
time_user=elapsed_time, |
||||||
|
time_system=elapsed_time) |
||||||
|
return control_pb2.ClientStatus(stats=stats) |
||||||
|
|
||||||
|
|
||||||
|
def _create_client(server: str, config: control_pb2.ClientConfig, qps_data: histogram.Histogram) -> benchmark_client.BenchmarkClient: |
||||||
|
if config.load_params.WhichOneof('load') != 'closed_loop': |
||||||
|
raise NotImplementedError(f'Unsupported load parameter {config.load_params}') |
||||||
|
|
||||||
|
if config.client_type == control_pb2.ASYNC_CLIENT: |
||||||
|
if config.rpc_type == control_pb2.UNARY: |
||||||
|
client_type = benchmark_client.UnaryAsyncBenchmarkClient |
||||||
|
if config.rpc_type == control_pb2.STREAMING: |
||||||
|
client_type = benchmark_client.StreamingAsyncBenchmarkClient |
||||||
|
else: |
||||||
|
raise NotImplementedError(f'Unsupported rpc_type [{config.rpc_type}]') |
||||||
|
else: |
||||||
|
raise NotImplementedError(f'Unsupported client type {config.client_type}') |
||||||
|
|
||||||
|
return client_type(server, config, qps_data) |
||||||
|
|
||||||
|
|
||||||
|
class WorkerServicer(worker_service_pb2_grpc.WorkerServiceServicer): |
||||||
|
"""Python Worker Server implementation.""" |
||||||
|
|
||||||
|
def __init__(self): |
||||||
|
self._loop = asyncio.get_event_loop() |
||||||
|
self._quit_event = asyncio.Event() |
||||||
|
|
||||||
|
async def RunServer(self, request_iterator, context): |
||||||
|
config = (await context.read()).setup |
||||||
|
|
||||||
|
server, port = _create_server(config) |
||||||
|
await server.start() |
||||||
|
start_time = time.time() |
||||||
|
yield self._get_server_status(start_time, start_time, port) |
||||||
|
|
||||||
|
async for request in request_iterator: |
||||||
|
end_time = time.time() |
||||||
|
status = self._get_server_status(start_time, end_time, port) |
||||||
|
if request.mark.reset: |
||||||
|
start_time = end_time |
||||||
|
yield status |
||||||
|
server.stop(None) |
||||||
|
|
||||||
|
async def RunClient(self, request_iterator, context): |
||||||
|
config = (await context.read()).setup |
||||||
|
|
||||||
|
running_tasks = [] |
||||||
|
qps_data = histogram.Histogram(config.histogram_params.resolution, |
||||||
|
config.histogram_params.max_possible) |
||||||
|
start_time = time.time() |
||||||
|
|
||||||
|
# Create a client for each channel as asyncio.Task |
||||||
|
for i in range(config.client_channels): |
||||||
|
server = config.server_targets[i % len(config.server_targets)] |
||||||
|
client = self._create_client(server, config, qps_data) |
||||||
|
running_tasks.append(self._loop.create_task(client.run())) |
||||||
|
|
||||||
|
end_time = time.time() |
||||||
|
yield self._get_client_status(start_time, end_time, qps_data) |
||||||
|
|
||||||
|
# Respond to stat requests |
||||||
|
async for request in request_iterator: |
||||||
|
end_time = time.time() |
||||||
|
status = self._get_client_status(start_time, end_time, qps_data) |
||||||
|
if request.mark.reset: |
||||||
|
qps_data.reset() |
||||||
|
start_time = time.time() |
||||||
|
yield status |
||||||
|
|
||||||
|
# Cleanup the clients |
||||||
|
for task in running_tasks: |
||||||
|
task.cancel() |
||||||
|
|
||||||
|
async def CoreCount(self, request, context): |
||||||
|
return control_pb2.CoreResponse(cores=_NUM_CORES) |
||||||
|
|
||||||
|
async def QuitWorker(self, request, context): |
||||||
|
self._quit_event.set() |
||||||
|
return control_pb2.Void() |
||||||
|
|
||||||
|
async def wait_for_quit(self): |
||||||
|
await self._quit_event.wait() |
Loading…
Reference in new issue