mirror of https://github.com/grpc/grpc.git
parent
a4e7ecab1a
commit
0482c1046b
12 changed files with 869 additions and 4 deletions
@ -0,0 +1,28 @@ |
||||
# Copyright 2016, Google Inc. |
||||
# All rights reserved. |
||||
# |
||||
# Redistribution and use in source and binary forms, with or without |
||||
# modification, are permitted provided that the following conditions are |
||||
# met: |
||||
# |
||||
# * Redistributions of source code must retain the above copyright |
||||
# notice, this list of conditions and the following disclaimer. |
||||
# * Redistributions in binary form must reproduce the above |
||||
# copyright notice, this list of conditions and the following disclaimer |
||||
# in the documentation and/or other materials provided with the |
||||
# distribution. |
||||
# * Neither the name of Google Inc. nor the names of its |
||||
# contributors may be used to endorse or promote products derived from |
||||
# this software without specific prior written permission. |
||||
# |
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,186 @@ |
||||
# Copyright 2016, Google Inc. |
||||
# All rights reserved. |
||||
# |
||||
# Redistribution and use in source and binary forms, with or without |
||||
# modification, are permitted provided that the following conditions are |
||||
# met: |
||||
# |
||||
# * Redistributions of source code must retain the above copyright |
||||
# notice, this list of conditions and the following disclaimer. |
||||
# * Redistributions in binary form must reproduce the above |
||||
# copyright notice, this list of conditions and the following disclaimer |
||||
# in the documentation and/or other materials provided with the |
||||
# distribution. |
||||
# * Neither the name of Google Inc. nor the names of its |
||||
# contributors may be used to endorse or promote products derived from |
||||
# this software without specific prior written permission. |
||||
# |
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
"""Defines test client behaviors (UNARY/STREAMING) (SYNC/ASYNC).""" |
||||
|
||||
import abc |
||||
import time |
||||
try: |
||||
import Queue as queue # Python 2.x |
||||
except ImportError: |
||||
import queue # Python 3 |
||||
|
||||
from concurrent import futures |
||||
|
||||
from grpc.beta import implementations |
||||
from src.proto.grpc.testing import messages_pb2 |
||||
from src.proto.grpc.testing import services_pb2 |
||||
from tests.unit import resources |
||||
from tests.unit.beta import test_utilities |
||||
|
||||
_TIMEOUT = 60 * 60 * 24 |
||||
|
||||
|
||||
class BenchmarkClient: |
||||
"""Benchmark client interface that exposes a non-blocking send_request().""" |
||||
|
||||
__metaclass__ = abc.ABCMeta |
||||
|
||||
def __init__(self, server, config, hist): |
||||
# Create the stub |
||||
host, port = server.split(':') |
||||
port = int(port) |
||||
if config.HasField('security_params'): |
||||
creds = implementations.ssl_channel_credentials( |
||||
resources.test_root_certificates()) |
||||
channel = test_utilities.not_really_secure_channel( |
||||
host, port, creds, config.security_params.server_host_override) |
||||
else: |
||||
channel = implementations.insecure_channel(host, port) |
||||
|
||||
if config.payload_config.WhichOneof('payload') == 'simple_params': |
||||
self._generic = False |
||||
self._stub = services_pb2.beta_create_BenchmarkService_stub(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 = implementations.generic_stub(channel) |
||||
self._request = '\0' * config.payload_config.bytebuf_params.req_size |
||||
|
||||
self._hist = hist |
||||
self._response_callbacks = [] |
||||
|
||||
def add_response_callback(self, callback): |
||||
self._response_callbacks.append(callback) |
||||
|
||||
@abc.abstractmethod |
||||
def send_request(self): |
||||
"""Non-blocking wrapper for a client's request operation.""" |
||||
raise NotImplementedError() |
||||
|
||||
def start(self): |
||||
pass |
||||
|
||||
def stop(self): |
||||
pass |
||||
|
||||
def _handle_response(self, query_time): |
||||
self._hist.add(query_time * 1e9) # Report times in nanoseconds |
||||
for callback in self._response_callbacks: |
||||
callback(query_time) |
||||
|
||||
|
||||
class UnarySyncBenchmarkClient(BenchmarkClient): |
||||
|
||||
def __init__(self, server, config, hist): |
||||
super(UnarySyncBenchmarkClient, self).__init__(server, config, hist) |
||||
self._pool = futures.ThreadPoolExecutor( |
||||
max_workers=config.outstanding_rpcs_per_channel) |
||||
|
||||
def send_request(self): |
||||
# Send requests in seperate threads to support multiple outstanding rpcs |
||||
# (See src/proto/grpc/testing/control.proto) |
||||
self._pool.submit(self._dispatch_request) |
||||
|
||||
def stop(self): |
||||
self._pool.shutdown(wait=True) |
||||
self._stub = None |
||||
|
||||
def _dispatch_request(self): |
||||
start_time = time.time() |
||||
self._stub.UnaryCall(self._request, _TIMEOUT) |
||||
end_time = time.time() |
||||
self._handle_response(end_time - start_time) |
||||
|
||||
|
||||
class UnaryAsyncBenchmarkClient(BenchmarkClient): |
||||
|
||||
def send_request(self): |
||||
# Use the Future callback api to support multiple outstanding rpcs |
||||
start_time = time.time() |
||||
response_future = self._stub.UnaryCall.future(self._request, _TIMEOUT) |
||||
response_future.add_done_callback( |
||||
lambda resp: self._response_received(start_time, resp)) |
||||
|
||||
def _response_received(self, start_time, resp): |
||||
resp.result() |
||||
end_time = time.time() |
||||
self._handle_response(end_time - start_time) |
||||
|
||||
def stop(self): |
||||
self._stub = None |
||||
|
||||
|
||||
class StreamingAsyncBenchmarkClient(BenchmarkClient): |
||||
|
||||
def __init__(self, server, config, hist): |
||||
super(StreamingAsyncBenchmarkClient, self).__init__(server, config, hist) |
||||
self._is_streaming = False |
||||
self._pool = futures.ThreadPoolExecutor(max_workers=1) |
||||
# Use a thread-safe queue to put requests on the stream |
||||
self._request_queue = queue.Queue() |
||||
self._send_time_queue = queue.Queue() |
||||
|
||||
def send_request(self): |
||||
self._send_time_queue.put(time.time()) |
||||
self._request_queue.put(self._request) |
||||
|
||||
def start(self): |
||||
self._is_streaming = True |
||||
self._pool.submit(self._request_stream) |
||||
|
||||
def stop(self): |
||||
self._is_streaming = False |
||||
self._pool.shutdown(wait=True) |
||||
self._stub = None |
||||
|
||||
def _request_stream(self): |
||||
self._is_streaming = True |
||||
if self._generic: |
||||
response_stream = self._stub.inline_stream_stream( |
||||
'grpc.testing.BenchmarkService', 'StreamingCall', |
||||
self._request_generator(), _TIMEOUT) |
||||
else: |
||||
response_stream = self._stub.StreamingCall(self._request_generator(), |
||||
_TIMEOUT) |
||||
for _ in response_stream: |
||||
end_time = time.time() |
||||
self._handle_response(end_time - self._send_time_queue.get_nowait()) |
||||
|
||||
def _request_generator(self): |
||||
while self._is_streaming: |
||||
try: |
||||
request = self._request_queue.get(block=True, timeout=1.0) |
||||
yield request |
||||
except queue.Empty: |
||||
pass |
@ -0,0 +1,58 @@ |
||||
# Copyright 2016, Google Inc. |
||||
# All rights reserved. |
||||
# |
||||
# Redistribution and use in source and binary forms, with or without |
||||
# modification, are permitted provided that the following conditions are |
||||
# met: |
||||
# |
||||
# * Redistributions of source code must retain the above copyright |
||||
# notice, this list of conditions and the following disclaimer. |
||||
# * Redistributions in binary form must reproduce the above |
||||
# copyright notice, this list of conditions and the following disclaimer |
||||
# in the documentation and/or other materials provided with the |
||||
# distribution. |
||||
# * Neither the name of Google Inc. nor the names of its |
||||
# contributors may be used to endorse or promote products derived from |
||||
# this software without specific prior written permission. |
||||
# |
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
from src.proto.grpc.testing import messages_pb2 |
||||
from src.proto.grpc.testing import services_pb2 |
||||
|
||||
|
||||
class BenchmarkServer(services_pb2.BetaBenchmarkServiceServicer): |
||||
"""Synchronous Server implementation for the Benchmark service.""" |
||||
|
||||
def UnaryCall(self, request, context): |
||||
payload = messages_pb2.Payload(body='\0' * request.response_size) |
||||
return messages_pb2.SimpleResponse(payload=payload) |
||||
|
||||
def StreamingCall(self, request_iterator, context): |
||||
for request in request_iterator: |
||||
payload = messages_pb2.Payload(body='\0' * request.response_size) |
||||
yield messages_pb2.SimpleResponse(payload=payload) |
||||
|
||||
|
||||
class GenericBenchmarkServer(services_pb2.BetaBenchmarkServiceServicer): |
||||
"""Generic Server implementation for the Benchmark service.""" |
||||
|
||||
def __init__(self, resp_size): |
||||
self._response = '\0' * resp_size |
||||
|
||||
def UnaryCall(self, request, context): |
||||
return self._response |
||||
|
||||
def StreamingCall(self, request_iterator, context): |
||||
for request in request_iterator: |
||||
yield self._response |
@ -0,0 +1,104 @@ |
||||
# Copyright 2016, Google Inc. |
||||
# All rights reserved. |
||||
# |
||||
# Redistribution and use in source and binary forms, with or without |
||||
# modification, are permitted provided that the following conditions are |
||||
# met: |
||||
# |
||||
# * Redistributions of source code must retain the above copyright |
||||
# notice, this list of conditions and the following disclaimer. |
||||
# * Redistributions in binary form must reproduce the above |
||||
# copyright notice, this list of conditions and the following disclaimer |
||||
# in the documentation and/or other materials provided with the |
||||
# distribution. |
||||
# * Neither the name of Google Inc. nor the names of its |
||||
# contributors may be used to endorse or promote products derived from |
||||
# this software without specific prior written permission. |
||||
# |
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
"""Defines behavior for WHEN clients send requests. |
||||
|
||||
Each client exposes a non-blocking send_request() method that the |
||||
ClientRunner invokes either periodically or in response to some event. |
||||
""" |
||||
|
||||
import abc |
||||
import thread |
||||
import time |
||||
|
||||
|
||||
class ClientRunner: |
||||
"""Abstract interface for sending requests from clients.""" |
||||
|
||||
__metaclass__ = abc.ABCMeta |
||||
|
||||
def __init__(self, client): |
||||
self._client = client |
||||
|
||||
@abc.abstractmethod |
||||
def start(self): |
||||
raise NotImplementedError() |
||||
|
||||
@abc.abstractmethod |
||||
def stop(self): |
||||
raise NotImplementedError() |
||||
|
||||
|
||||
class OpenLoopClientRunner(ClientRunner): |
||||
|
||||
def __init__(self, client, interval_generator): |
||||
super(OpenLoopClientRunner, self).__init__(client) |
||||
self._is_running = False |
||||
self._interval_generator = interval_generator |
||||
|
||||
def start(self): |
||||
self._is_running = True |
||||
self._client.start() |
||||
thread.start_new_thread(self._dispatch_requests, ()) |
||||
|
||||
def stop(self): |
||||
self._is_running = False |
||||
self._client.stop() |
||||
self._client = None |
||||
|
||||
def _dispatch_requests(self): |
||||
while self._is_running: |
||||
self._client.send_request() |
||||
time.sleep(next(self._interval_generator)) |
||||
|
||||
|
||||
class ClosedLoopClientRunner(ClientRunner): |
||||
|
||||
def __init__(self, client, request_count): |
||||
super(ClosedLoopClientRunner, self).__init__(client) |
||||
self._is_running = False |
||||
self._request_count = request_count |
||||
# Send a new request on each response for closed loop |
||||
self._client.add_response_callback(self._send_request) |
||||
|
||||
def start(self): |
||||
self._is_running = True |
||||
for _ in xrange(self._request_count): |
||||
self._client.send_request() |
||||
self._client.start() |
||||
|
||||
def stop(self): |
||||
self._is_running = False |
||||
self._client.stop() |
||||
self._client = None |
||||
|
||||
def _send_request(self, response_time): |
||||
if self._is_running: |
||||
self._client.send_request() |
||||
|
@ -0,0 +1,85 @@ |
||||
# Copyright 2016, Google Inc. |
||||
# All rights reserved. |
||||
# |
||||
# Redistribution and use in source and binary forms, with or without |
||||
# modification, are permitted provided that the following conditions are |
||||
# met: |
||||
# |
||||
# * Redistributions of source code must retain the above copyright |
||||
# notice, this list of conditions and the following disclaimer. |
||||
# * Redistributions in binary form must reproduce the above |
||||
# copyright notice, this list of conditions and the following disclaimer |
||||
# in the documentation and/or other materials provided with the |
||||
# distribution. |
||||
# * Neither the name of Google Inc. nor the names of its |
||||
# contributors may be used to endorse or promote products derived from |
||||
# this software without specific prior written permission. |
||||
# |
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
import math |
||||
import threading |
||||
|
||||
from src.proto.grpc.testing import stats_pb2 |
||||
|
||||
|
||||
class Histogram(object): |
||||
"""Histogram class used for recording performance testing data. |
||||
|
||||
This class is thread safe. |
||||
""" |
||||
|
||||
def __init__(self, resolution, max_possible): |
||||
self._lock = threading.Lock() |
||||
self._resolution = resolution |
||||
self._max_possible = max_possible |
||||
self._sum = 0 |
||||
self._sum_of_squares = 0 |
||||
self.multiplier = 1.0 + self._resolution |
||||
self._count = 0 |
||||
self._min = self._max_possible |
||||
self._max = 0 |
||||
self._buckets = [0] * (self._bucket_for(self._max_possible) + 1) |
||||
|
||||
def reset(self): |
||||
with self._lock: |
||||
self._sum = 0 |
||||
self._sum_of_squares = 0 |
||||
self._count = 0 |
||||
self._min = self._max_possible |
||||
self._max = 0 |
||||
self._buckets = [0] * (self._bucket_for(self._max_possible) + 1) |
||||
|
||||
def add(self, val): |
||||
with self._lock: |
||||
self._sum += val |
||||
self._sum_of_squares += val * val |
||||
self._count += 1 |
||||
self._min = min(self._min, val) |
||||
self._max = max(self._max, val) |
||||
self._buckets[self._bucket_for(val)] += 1 |
||||
|
||||
def get_data(self): |
||||
with self._lock: |
||||
data = stats_pb2.HistogramData() |
||||
data.bucket.extend(self._buckets) |
||||
data.min_seen = self._min |
||||
data.max_seen = self._max |
||||
data.sum = self._sum |
||||
data.sum_of_squares = self._sum_of_squares |
||||
data.count = self._count |
||||
return data |
||||
|
||||
def _bucket_for(self, val): |
||||
val = min(val, self._max_possible) |
||||
return int(math.log(val, self.multiplier)) |
@ -0,0 +1,60 @@ |
||||
# Copyright 2016, Google Inc. |
||||
# All rights reserved. |
||||
# |
||||
# Redistribution and use in source and binary forms, with or without |
||||
# modification, are permitted provided that the following conditions are |
||||
# met: |
||||
# |
||||
# * Redistributions of source code must retain the above copyright |
||||
# notice, this list of conditions and the following disclaimer. |
||||
# * Redistributions in binary form must reproduce the above |
||||
# copyright notice, this list of conditions and the following disclaimer |
||||
# in the documentation and/or other materials provided with the |
||||
# distribution. |
||||
# * Neither the name of Google Inc. nor the names of its |
||||
# contributors may be used to endorse or promote products derived from |
||||
# this software without specific prior written permission. |
||||
# |
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
"""The entry point for the qps worker.""" |
||||
|
||||
import argparse |
||||
import time |
||||
|
||||
from src.proto.grpc.testing import services_pb2 |
||||
|
||||
from tests.qps import worker_server |
||||
|
||||
|
||||
def run_worker_server(port): |
||||
servicer = worker_server.WorkerServer() |
||||
server = services_pb2.beta_create_WorkerService_server(servicer) |
||||
server.add_insecure_port('[::]:{}'.format(port)) |
||||
server.start() |
||||
servicer.wait_for_quit() |
||||
# Drain outstanding requests for clean exit |
||||
time.sleep(2) |
||||
server.stop(0) |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
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() |
||||
|
||||
run_worker_server(args.port) |
@ -0,0 +1,184 @@ |
||||
# Copyright 2016, Google Inc. |
||||
# All rights reserved. |
||||
# |
||||
# Redistribution and use in source and binary forms, with or without |
||||
# modification, are permitted provided that the following conditions are |
||||
# met: |
||||
# |
||||
# * Redistributions of source code must retain the above copyright |
||||
# notice, this list of conditions and the following disclaimer. |
||||
# * Redistributions in binary form must reproduce the above |
||||
# copyright notice, this list of conditions and the following disclaimer |
||||
# in the documentation and/or other materials provided with the |
||||
# distribution. |
||||
# * Neither the name of Google Inc. nor the names of its |
||||
# contributors may be used to endorse or promote products derived from |
||||
# this software without specific prior written permission. |
||||
# |
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
import multiprocessing |
||||
import random |
||||
import threading |
||||
import time |
||||
|
||||
from grpc.beta import implementations |
||||
from grpc.framework.interfaces.face import utilities |
||||
from src.proto.grpc.testing import control_pb2 |
||||
from src.proto.grpc.testing import services_pb2 |
||||
from src.proto.grpc.testing import stats_pb2 |
||||
|
||||
from tests.qps import benchmark_client |
||||
from tests.qps import benchmark_server |
||||
from tests.qps import client_runner |
||||
from tests.qps import histogram |
||||
from tests.unit import resources |
||||
|
||||
|
||||
class WorkerServer(services_pb2.BetaWorkerServiceServicer): |
||||
"""Python Worker Server implementation.""" |
||||
|
||||
def __init__(self): |
||||
self._quit_event = threading.Event() |
||||
|
||||
def RunServer(self, request_iterator, context): |
||||
config = next(request_iterator).setup |
||||
server, port = self._create_server(config) |
||||
cores = multiprocessing.cpu_count() |
||||
server.start() |
||||
start_time = time.time() |
||||
yield self._get_server_status(start_time, start_time, port, cores) |
||||
|
||||
for request in request_iterator: |
||||
end_time = time.time() |
||||
status = self._get_server_status(start_time, end_time, port, cores) |
||||
if request.mark.reset: |
||||
start_time = end_time |
||||
yield status |
||||
server.stop(0) |
||||
|
||||
def _get_server_status(self, start_time, end_time, port, cores): |
||||
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=cores) |
||||
|
||||
def _create_server(self, config): |
||||
if config.server_type == control_pb2.SYNC_SERVER: |
||||
servicer = benchmark_server.BenchmarkServer() |
||||
server = services_pb2.beta_create_BenchmarkService_server(servicer) |
||||
elif config.server_type == control_pb2.ASYNC_GENERIC_SERVER: |
||||
resp_size = config.payload_config.bytebuf_params.resp_size |
||||
servicer = benchmark_server.GenericBenchmarkServer(resp_size) |
||||
method_implementations = { |
||||
('grpc.testing.BenchmarkService', 'StreamingCall'): |
||||
utilities.stream_stream_inline(servicer.StreamingCall), |
||||
('grpc.testing.BenchmarkService', 'UnaryCall'): |
||||
utilities.unary_unary_inline(servicer.UnaryCall), |
||||
} |
||||
server = implementations.server(method_implementations) |
||||
else: |
||||
raise Exception('Unsupported server type {}'.format(config.server_type)) |
||||
|
||||
if config.HasField('security_params'): # Use SSL |
||||
server_creds = implementations.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 RunClient(self, request_iterator, context): |
||||
config = next(request_iterator).setup |
||||
client_runners = [] |
||||
qps_data = histogram.Histogram(config.histogram_params.resolution, |
||||
config.histogram_params.max_possible) |
||||
start_time = time.time() |
||||
|
||||
# Create a client for each channel |
||||
for i in xrange(config.client_channels): |
||||
server = config.server_targets[i % len(config.server_targets)] |
||||
runner = self._create_client_runner(server, config, qps_data) |
||||
client_runners.append(runner) |
||||
runner.start() |
||||
|
||||
end_time = time.time() |
||||
yield self._get_client_status(start_time, end_time, qps_data) |
||||
|
||||
# Respond to stat requests |
||||
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 runner in client_runners: |
||||
runner.stop() |
||||
|
||||
def _get_client_status(self, start_time, end_time, qps_data): |
||||
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_runner(self, server, config, qps_data): |
||||
if config.client_type == control_pb2.SYNC_CLIENT: |
||||
if config.rpc_type == control_pb2.UNARY: |
||||
client = benchmark_client.UnarySyncBenchmarkClient( |
||||
server, config, qps_data) |
||||
else: |
||||
raise Exception('STREAMING SYNC client not supported') |
||||
elif config.client_type == control_pb2.ASYNC_CLIENT: |
||||
if config.rpc_type == control_pb2.UNARY: |
||||
client = benchmark_client.UnaryAsyncBenchmarkClient( |
||||
server, config, qps_data) |
||||
elif config.rpc_type == control_pb2.STREAMING: |
||||
client = benchmark_client.StreamingAsyncBenchmarkClient( |
||||
server, config, qps_data) |
||||
else: |
||||
raise Exception('Unsupported client type {}'.format(config.client_type)) |
||||
|
||||
# In multi-channel tests, we split the load across all channels |
||||
load_factor = float(config.client_channels) |
||||
if config.load_params.WhichOneof('load') == 'closed_loop': |
||||
runner = client_runner.ClosedLoopClientRunner( |
||||
client, config.outstanding_rpcs_per_channel) |
||||
else: # Open loop Poisson |
||||
alpha = config.load_params.poisson.offered_load / load_factor |
||||
def poisson(): |
||||
while True: |
||||
yield random.expovariate(alpha) |
||||
|
||||
runner = client_runner.OpenLoopClientRunner(client, poisson()) |
||||
|
||||
return runner |
||||
|
||||
def CoreCount(self, request, context): |
||||
return control_pb2.CoreResponse(cores=multiprocessing.cpu_count()) |
||||
|
||||
def QuitWorker(self, request, context): |
||||
self._quit_event.set() |
||||
return control_pb2.Void() |
||||
|
||||
def wait_for_quit(self): |
||||
self._quit_event.wait() |
@ -0,0 +1,35 @@ |
||||
#!/bin/bash |
||||
# Copyright 2016, Google Inc. |
||||
# All rights reserved. |
||||
# |
||||
# Redistribution and use in source and binary forms, with or without |
||||
# modification, are permitted provided that the following conditions are |
||||
# met: |
||||
# |
||||
# * Redistributions of source code must retain the above copyright |
||||
# notice, this list of conditions and the following disclaimer. |
||||
# * Redistributions in binary form must reproduce the above |
||||
# copyright notice, this list of conditions and the following disclaimer |
||||
# in the documentation and/or other materials provided with the |
||||
# distribution. |
||||
# * Neither the name of Google Inc. nor the names of its |
||||
# contributors may be used to endorse or promote products derived from |
||||
# this software without specific prior written permission. |
||||
# |
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
set -ex |
||||
|
||||
cd $(dirname $0)/../../.. |
||||
|
||||
PYTHONPATH=src/python/grpcio:src/python/gens .tox/py27/bin/python src/python/grpcio/tests/qps/qps_worker.py $@ |
Loading…
Reference in new issue