Skip to content

Commit

Permalink
Update xmlrpc_locustfile.py
Browse files Browse the repository at this point in the history
  • Loading branch information
cyberw committed May 3, 2021
1 parent df51814 commit c797358
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 51 deletions.
33 changes: 9 additions & 24 deletions docs/testing-other-systems.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.event.Events.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 <locust.event.Events.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 <https://github.com/psycopg/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 <https://github.com/psycopg/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 <https://github.com/SvenskaSpel/locust-plugins#users>`_
52 changes: 25 additions & 27 deletions examples/custom_xmlrpc_client/xmlrpc_locustfile.py
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit c797358

Please sign in to comment.