From c7973583f46c71547d74c5164fed684b17bf7c44 Mon Sep 17 00:00:00 2001 From: Lars Holmberg Date: Mon, 3 May 2021 23:17:46 +0200 Subject: [PATCH] Update xmlrpc_locustfile.py --- docs/testing-other-systems.rst | 33 ++++-------- .../custom_xmlrpc_client/xmlrpc_locustfile.py | 52 +++++++++---------- 2 files changed, 34 insertions(+), 51 deletions(-) diff --git a/docs/testing-other-systems.rst b/docs/testing-other-systems.rst index 5634cb7608..57a8a621ea 100644 --- a/docs/testing-other-systems.rst +++ b/docs/testing-other-systems.rst @@ -4,38 +4,23 @@ Testing other systems using custom clients =========================================== -Locust was built with HTTP as its main target. However, it can easily be extended to load test -any request/response based system, by writing a custom client that triggers -:py:attr:`request ` +Locust was built with HTTP as its main use case but it can be extended to load test almost any system. You do this by writing a custom client that triggers :py:attr:`request ` .. note:: - Any protocol libraries that you use must be gevent-friendly (use the Python ``socket`` module or some other standard library function like ``subprocess``), or your calls will block the whole Locust process. + Any protocol libraries that you use must be gevent-friendly (use the Python ``socket`` module or some other standard library function like ``subprocess``), or your calls are likely to block the whole Locust/Python process. - Some C libraries cannot be monkey patched by gevent, but allow for other workarounds. For example, if you want to use psycopg2 to performance test PostgreSQL, can use `psycogreen `_. + Some C libraries cannot be monkey patched by gevent, but allow for other workarounds. For example, if you want to use psycopg2 to performance test PostgreSQL, you can use `psycogreen `_. -Sample XML-RPC User client -============================ +Example: writing an XML-RPC User/client +======================================= -Here is an example of a User class, **XmlRpcUser**, which provides an XML-RPC client, -**XmlRpcUser**, and tracks all requests made: +Lets assume we had an XML-RPC server that we wanted to load test -.. literalinclude:: ../examples/custom_xmlrpc_client/xmlrpc_locustfile.py - -If you've written Locust tests before, you'll recognize the class called ``ApiUser`` which is a normal -User class that has a couple of tasks declared. However, the ``ApiUser`` inherits from -``XmlRpcUser`` that you can see right above ``ApiUser``. The ``XmlRpcUser`` is marked as abstract -using ``abstract = True`` which means that Locust will not try to create simulated users from that class -(only of classes that extend it). ``XmlRpcUser`` provides an instance of XmlRpcClient under the -``client`` attribute. - -The ``XmlRpcClient`` is a wrapper around the standard -library's :py:class:`xmlrpc.client.ServerProxy`. It basically just proxies the function calls, but with the -important addition of firing :py:attr:`locust.event.Events.request` -event, which will record all calls in Locust's statistics. +.. literalinclude:: ../examples/custom_xmlrpc_client/server.py -Here's an implementation of an XML-RPC server that would work as a server for the code above: +We can build a generic XML-RPC client, by wrapping :py:class:`xmlrpc.client.ServerProxy` -.. literalinclude:: ../examples/custom_xmlrpc_client/server.py +.. literalinclude:: ../examples/custom_xmlrpc_client/xmlrpc_locustfile.py For more examples, see `locust-plugins `_ diff --git a/examples/custom_xmlrpc_client/xmlrpc_locustfile.py b/examples/custom_xmlrpc_client/xmlrpc_locustfile.py index 347346a0c1..114f9fee46 100644 --- a/examples/custom_xmlrpc_client/xmlrpc_locustfile.py +++ b/examples/custom_xmlrpc_client/xmlrpc_locustfile.py @@ -1,67 +1,65 @@ import time from xmlrpc.client import ServerProxy, Fault -from locust import User, task, between +from locust import User, task class XmlRpcClient(ServerProxy): """ - Simple, sample XML RPC client implementation that wraps xmlrpclib.ServerProxy and - fires locust events on request, so that all requests - get tracked in locust's statistics. + XmlRpcClient is a wrapper around the standard library's ServerProxy. + It proxies any function calls and fires the *request* event when they finish, + so that the calls get recorded in Locust. """ - _locust_environment = None + def __init__(self, host, request_event): + super().__init__(host) + self._request_event = request_event def __getattr__(self, name): func = ServerProxy.__getattr__(self, name) def wrapper(*args, **kwargs): - start_time = time.time() + start_time = time.monotonic() request_meta = { "request_type": "xmlrpc", "name": name, - "response_time": 0, - "response_length": 0, - "context": {}, + "response_length": 0, # calculating this for an xmlrpc.client response would be too hard + "response": None, + "context": {}, # see HttpUser if you actually want to implement contexts "exception": None, } - try: - result = func(*args, **kwargs) + request_meta["response"] = func(*args, **kwargs) except Fault as e: request_meta["exception"] = e - - request_meta["response_time"] = int((time.time() - start_time) * 1000) - self._locust_environment.events.request.fire(**request_meta) - # In this example, I've hardcoded response_length=0. If we would want the response length to be - # reported correctly in the statistics, we would probably need to hook in at a lower level + request_meta["response_time"] = (time.monotonic() - start_time) * 1000 + self._request_event.fire(**request_meta) # This is what makes the request actually get logged in Locust + return request_meta["response"] return wrapper class XmlRpcUser(User): """ - This is the abstract User class which should be subclassed. It provides an XML-RPC client - that can be used to make XML-RPC requests that will be tracked in Locust's statistics. + A minimal Locust user class that provides an XmlRpcClient to its subclasses """ - abstract = True + abstract = True # dont instantiate this as an actual user when running Locust - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.client = XmlRpcClient(self.host) - self.client._locust_environment = self.environment + def __init__(self, environment): + super().__init__(environment) + self.client = XmlRpcClient(self.host, request_event=environment.events.request) -class ApiUser(XmlRpcUser): +# The real user class that will be instantiated and run by Locust +# This is the only thing that is actually specific to the service that we are testing. +class MyUser(XmlRpcUser): host = "http://127.0.0.1:8877/" - wait_time = between(0.1, 1) - @task(10) + @task def get_time(self): self.client.get_time() - @task(5) + @task def get_random_number(self): self.client.get_random_number(0, 100)