grpc压力测试 基于Locust 父类和client重写

proto文件helloword.proto:

// Copyright 2015 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.

syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";

package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  bytes message = 1;
}

 grpc_server.py:

# Copyright 2015 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 implementation of the GRPC helloworld.Greeter server."""

from concurrent import futures
import logging

import grpc

import helloworld_pb2
import helloworld_pb2_grpc


class Greeter(helloworld_pb2_grpc.GreeterServicer):

    def SayHello(self, request, context):
        return helloworld_pb2.HelloReply(message= request.name)


def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
    server.add_insecure_port('[::]:50051')
    server.start()
    server.wait_for_termination()


if __name__ == '__main__':
    logging.basicConfig()
    serve()

helloword_pb2.py

# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler.  DO NOT EDIT!
# source: helloworld.proto

from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()




DESCRIPTOR = _descriptor.FileDescriptor(
  name='helloworld.proto',
  package='helloworld',
  syntax='proto3',
  serialized_options=b'\n\033io.grpc.examples.helloworldB\017HelloWorldProtoP\001\242\002\003HLW',
  serialized_pb=b'\n\x10helloworld.proto\x12\nhelloworld\"\x1c\n\x0cHelloRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"\x1d\n\nHelloReply\x12\x0f\n\x07message\x18\x01 \x01(\t2I\n\x07Greeter\x12>\n\x08SayHello\x12\x18.helloworld.HelloRequest\x1a\x16.helloworld.HelloReply\"\x00\x42\x36\n\x1bio.grpc.examples.helloworldB\x0fHelloWorldProtoP\x01\xa2\x02\x03HLWb\x06proto3'
)




_HELLOREQUEST = _descriptor.Descriptor(
  name='HelloRequest',
  full_name='helloworld.HelloRequest',
  filename=None,
  file=DESCRIPTOR,
  containing_type=None,
  fields=[
    _descriptor.FieldDescriptor(
      name='name', full_name='helloworld.HelloRequest.name', index=0,
      number=1, type=9, cpp_type=9, label=1,
      has_default_value=False, default_value=b"".decode('utf-8'),
      message_type=None, enum_type=None, containing_type=None,
      is_extension=False, extension_scope=None,
      serialized_options=None, file=DESCRIPTOR),
  ],
  extensions=[
  ],
  nested_types=[],
  enum_types=[
  ],
  serialized_options=None,
  is_extendable=False,
  syntax='proto3',
  extension_ranges=[],
  oneofs=[
  ],
  serialized_start=32,
  serialized_end=60,
)


_HELLOREPLY = _descriptor.Descriptor(
  name='HelloReply',
  full_name='helloworld.HelloReply',
  filename=None,
  file=DESCRIPTOR,
  containing_type=None,
  fields=[
    _descriptor.FieldDescriptor(
      name='message', full_name='helloworld.HelloReply.message', index=0,
      number=1, type=9, cpp_type=9, label=1,
      has_default_value=False, default_value=b"".decode('utf-8'),
      message_type=None, enum_type=None, containing_type=None,
      is_extension=False, extension_scope=None,
      serialized_options=None, file=DESCRIPTOR),
  ],
  extensions=[
  ],
  nested_types=[],
  enum_types=[
  ],
  serialized_options=None,
  is_extendable=False,
  syntax='proto3',
  extension_ranges=[],
  oneofs=[
  ],
  serialized_start=62,
  serialized_end=91,
)

DESCRIPTOR.message_types_by_name['HelloRequest'] = _HELLOREQUEST
DESCRIPTOR.message_types_by_name['HelloReply'] = _HELLOREPLY
_sym_db.RegisterFileDescriptor(DESCRIPTOR)

HelloRequest = _reflection.GeneratedProtocolMessageType('HelloRequest', (_message.Message,), {
  'DESCRIPTOR' : _HELLOREQUEST,
  '__module__' : 'helloworld_pb2'
  # @@protoc_insertion_point(class_scope:helloworld.HelloRequest)
  })
_sym_db.RegisterMessage(HelloRequest)

HelloReply = _reflection.GeneratedProtocolMessageType('HelloReply', (_message.Message,), {
  'DESCRIPTOR' : _HELLOREPLY,
  '__module__' : 'helloworld_pb2'
  # @@protoc_insertion_point(class_scope:helloworld.HelloReply)
  })
_sym_db.RegisterMessage(HelloReply)


DESCRIPTOR._options = None

_GREETER = _descriptor.ServiceDescriptor(
  name='Greeter',
  full_name='helloworld.Greeter',
  file=DESCRIPTOR,
  index=0,
  serialized_options=None,
  serialized_start=93,
  serialized_end=166,
  methods=[
  _descriptor.MethodDescriptor(
    name='SayHello',
    full_name='helloworld.Greeter.SayHello',
    index=0,
    containing_service=None,
    input_type=_HELLOREQUEST,
    output_type=_HELLOREPLY,
    serialized_options=None,
  ),
])
_sym_db.RegisterServiceDescriptor(_GREETER)

DESCRIPTOR.services_by_name['Greeter'] = _GREETER

# @@protoc_insertion_point(module_scope)

helloword_pb2_grpc.py:

# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
import grpc

import helloworld_pb2 as helloworld__pb2


class GreeterStub(object):
    """The greeting service definition.
    """

    def __init__(self, channel):
        """Constructor.

        Args:
            channel: A grpc.Channel.
        """
        self.SayHello = channel.unary_unary(
                '/helloworld.Greeter/SayHello',
                request_serializer=helloworld__pb2.HelloRequest.SerializeToString,
                response_deserializer=helloworld__pb2.HelloReply.FromString,
                )


class GreeterServicer(object):
    """The greeting service definition.
    """

    def SayHello(self, request, context):
        """Sends a greeting
        """
        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
        context.set_details('Method not implemented!')
        raise NotImplementedError('Method not implemented!')


def add_GreeterServicer_to_server(servicer, server):
    rpc_method_handlers = {
            'SayHello': grpc.unary_unary_rpc_method_handler(
                    servicer.SayHello,
                    request_deserializer=helloworld__pb2.HelloRequest.FromString,
                    response_serializer=helloworld__pb2.HelloReply.SerializeToString,
            ),
    }
    generic_handler = grpc.method_handlers_generic_handler(
            'helloworld.Greeter', rpc_method_handlers)
    server.add_generic_rpc_handlers((generic_handler,))


 # This class is part of an EXPERIMENTAL API.
class Greeter(object):
    """The greeting service definition.
    """

    @staticmethod
    def SayHello(request,
            target,
            options=(),
            channel_credentials=None,
            call_credentials=None,
            compression=None,
            wait_for_ready=None,
            timeout=None,
            metadata=None):
        return grpc.experimental.unary_unary(request, target, '/helloworld.Greeter/SayHello',
            helloworld__pb2.HelloRequest.SerializeToString,
            helloworld__pb2.HelloReply.FromString,
            options, channel_credentials,
            call_credentials, compression, wait_for_ready, timeout, metadata)

项目结构:

demos
├─.idea
├─dependency
├─protos
└─python
└─helloword

     ---greeter_server.py

     ---helloworld_pb2.py

     ---helloworld_pb2_grpc.py

└─__pycache__

 压力测试类:

import logging
import time
import grpc
import helloworld_pb2
import helloworld_pb2_grpc
import json
from locust import (TaskSet, task, events, Locust)
from gevent._semaphore import Semaphore
import random

log_fmt = "[%(levelname)s]%(asctime)s line %(lineno)d :\n%(message)s"
c_fmt = "[%(levelname)s]%(asctime)s %(filename)s.%(funcName)s():line %(lineno)d :\n%(message)s"
date_format = "%Y-%m-%d %H:%M:%S %a"
# 设置控制台输出level
logging.basicConfig(level=logging.INFO,
                    format=c_fmt,
                    datefmt=date_format
                    )
all_locusts_spawned = Semaphore()
all_locusts_spawned.acquire()
host = "192.168.110.135"
port = "50051"


def on_hatch_complete(**kwargs):
    all_locusts_spawned.release()


events.hatch_complete += on_hatch_complete


def run():
    """test grpc server demo """
    with grpc.insecure_channel('192.168.110.135:50051') as channel:
        stub = helloworld_pb2_grpc.GreeterStub(channel)
        req = {"name": "jack", "id": 1001}
        body = json.dumps(req).encode("utf-8")
        response = stub.SayHello(helloworld_pb2.HelloRequest(name=body))
    print(response.message)  # str


class GrpcClient(object):
    """overide client"""

    def __init__(self):
        self.host = host
        self.port = port

    def grpc_request(self, body):
        start_time = int(time.time())
        response = None
        try:
            address = "{}:{}".format(host, port)
            channel = grpc.insecure_channel(address)
            # p2_grpc.Stub class implement new locust client
            client_stub = helloworld_pb2_grpc.GreeterStub(channel=channel)
            request_object = helloworld_pb2.HelloRequest(name=body)
            back_req = client_stub.SayHello(request_object)
            response = back_req.message
            elapsed = int((time.time() - start_time) * 1000)
            text = json.loads(response)
            logging.info("get response is {}".format(response))
            if text["retcode"] != 0:
                raise Exception("response get not expect,actual is {}".format(text))
            events.request_success.fire(
                request_type='grpc',
                name=r'/SayHello',
                response_time=elapsed,
                response_length=0
            )
        except Exception as e:
            total_time = int((time.time() - start_time) * 1000)
            events.request_failure.fire(
                request_type='grpc',
                name='/SayHello',
                response_time=total_time,
                exception=e
            )

        return response


class GrpcLocust(Locust):
    """ overide Locust to implement GrpcLocust"""

    def __init__(self, *args, **kwargs):
        super(GrpcLocust, self).__init__()
        self.client = GrpcClient()


class GrpcUserBehavior(TaskSet):
    """ super TaskSet class implement new GrpcTaskSet"""

    def on_task(self):
        """wait task event spawn"""
        all_locusts_spawned.wait()

    def on_stop(self):
        pass

    @task
    def inference_task(self):
        code = random.choice([101, 102, 0, 0])
        data = {"name": "{}".format("ZhangSan"), "retcode": code}
        body = json.dumps(data).encode("utf-8")
        response_msg = self.client.grpc_request(body)
        # print(response_msg)


class WebsiteUser(GrpcLocust):
    task_set = GrpcUserBehavior
    min_wait = 200  # think time ms
    max_wait = 500

  压力测试命令:locust -f grpc_client.py -c 4 -r 2  --run-time 15s --no-web


[2020-05-01 18:45:25,820] DESKTOP-PBNSFDJ/INFO/root: get response is {"name": "ZhangSan", "retcode": 101}
[2020-05-01 18:45:25,870] DESKTOP-PBNSFDJ/INFO/root: get response is {"name": "ZhangSan", "retcode": 101}
[2020-05-01 18:45:26,012] DESKTOP-PBNSFDJ/INFO/root: get response is {"name": "ZhangSan", "retcode": 102}
[2020-05-01 18:45:26,083] DESKTOP-PBNSFDJ/INFO/root: get response is {"name": "ZhangSan", "retcode": 101}
[2020-05-01 18:45:26,088] DESKTOP-PBNSFDJ/INFO/root: get response is {"name": "ZhangSan", "retcode": 0}
[2020-05-01 18:45:26,129] DESKTOP-PBNSFDJ/INFO/locust.main: Time limit reached. Stopping Locust.
[2020-05-01 18:45:26,130] DESKTOP-PBNSFDJ/INFO/locust.main: Shutting down (exit code 1), bye.
[2020-05-01 18:45:26,130] DESKTOP-PBNSFDJ/INFO/locust.main: Cleaning up runner...
[2020-05-01 18:45:26,131] DESKTOP-PBNSFDJ/INFO/locust.main: Running teardowns...
Name # reqs # fails Avg Min Max | Median req/s
--------------------------------------------------------------------------------------------------------------------------------------------
grpc /SayHello 158 87(35.51%) 532 10 1052 | 540 11.40
--------------------------------------------------------------------------------------------------------------------------------------------
Total 158 87(55.06%) 11.40

Percentage of the requests completed within given times
Name # reqs 50% 66% 75% 80% 90% 95% 98% 99% 100%
--------------------------------------------------------------------------------------------------------------------------------------------
grpc /SayHello 158 540 690 760 840 960 980 990 1000 1100
--------------------------------------------------------------------------------------------------------------------------------------------
Total 158 540 690 760 840 960 980 990 1000 1100

Error report
# occurrences Error
--------------------------------------------------------------------------------------------------------------------------------------------
46 grpc /SayHello: 'Exception("response get not expect,actual is {\'name\': \'ZhangSan\', \'retcode\': 101}")'
41 grpc /SayHello: 'Exception("response get not expect,actual is {\'name\': \'ZhangSan\', \'retcode\': 102}")'
--------------------------------------------------------------------------------------------------------------------------------------------

web模式:

 

上一篇:Locust性能测试-分布式执行的方法(亲测ok)


下一篇:【locust】使用locust + boomer实现对接口的压测