Looks like while implementing custom client likeHttpLocust when adding inline TaskSet class to Locust inline class is not loaded by Locust.
Any inline class should be executed by Locust.
According to https://docs.locust.io/en/latest/testing-other-systems.html#sample-xml-rpc-locust-client class task_set should be loaded by Locust and all tasks in this class should be executed.
I have implemented websocket client following this documentation. I have two Locust classes in one file loaded by locust and executed by:
locust -f locustfiles/api_load_tests.py --host=https://my_ip--csv=${report_dir}/report_data --no-web -c ${NUM_CLIENTS} -r ${HATCH_RATE} # ApiVehicleUser ApiMessageUser
Class ApiMessageUser that sets task_set = ApiMessage class attribute is loaded and executed correctly but class ApiVehicleUser which is inline class is not loaded. No error is raised neither.
Bellow is a snippet of the locust file:
class BtsLocust(Locust):
"""
Provides an Http and websocket clients.
"""
def __init__(self, *args, **kwargs):
super(BtsLocust, self).__init__(*args, **kwargs)
self.ws_client = locust_ws_client.LocustWebSocketClient(self.host) # this is wrapper like `XmlRpcClient` class
class ApiVehicleUser(BtsLocust):
# task_set = ApiVehicle
min_wait = 500
max_wait = 500
@task
class ApiVehicle(TaskSet):
def on_start(self):
# some setup
def on_stop(self):
# some clean up
@task
def here_goes_my_tasks_for_ApiVehicle(self):
# some setup
class ApiMessage(TaskSet):
def on_start(self):
# some setup
@task
def here_goes_my_tasks_for_ApiMessage(self):
# some setup
class ApiMessageUser(HttpLocust):
task_set = ApiMessage
min_wait = 500
max_wait = 500
The thing is that I must have HttpLocust and websocket client at one Locust class to combine tests. If I uncomment # task_set = ApiVehicle then ApiVehicle taks set cannot access to websocket client because for some it is not inherited (and HttpLocust is). So there are few problems over here but the main is mentioned above. So assuming that ApiVehicle will be loaded into Locust all should be fine.
If anyone have idea how to integrate these two clients using even workarounds I would be glad to do this. I tried to set task_set = ApiVehicle and pass ws_client from outside by __init__ methods but this is messing up too much in Locust super-class and causes too much problems.
I have tried also to add @task decorator to inline class, nothing works.
I have found several bugs related to problem above. Also I have working code.
First of all you guys must investigate these problems and take action is this a bug or outdated wiki.
Example from https://docs.locust.io/en/latest/testing-other-systems.html#sample-xml-rpc-locust-client does not work.
Working code is (important part are commented):
class ApiVehicle(TaskSet):
def on_start(self):
# some setup
def on_stop(self):
# some clean up
@task
def here_goes_my_tasks_for_ApiVehicle(self):
# some setup
class ApiVehicleUser(HttpLocust):
task_set = ApiVehicle # THIS IS MISSING IN DOC, ALSO INNER CLASS CANNOT BE USED CAUSE IT HAS OTHER SCOPE THAN PARENT ONE
min_wait = 500
max_wait = 500
def __init__(self, *args, **kwargs):
super(ApiVehicleUser, self).__init__(*args, **kwargs)
self.task_set.ws_client = WebSocketClient(self.host) # THIS SHOULD GO HERE, NOT IN SUPER CLASS LIKE ON WIKI
class ApiMessage(TaskSet):
def on_start(self):
# some setup
@task
def here_goes_my_tasks_for_ApiMessage(self):
# some setup
class ApiMessageUser(HttpLocust):
task_set = ApiMessage
min_wait = 500
max_wait = 500
Wrapper from documentation example class XmlRpcClient(xmlrpclib.ServerProxy): doesn't work.
This wrapper runs somehow the code but doesn't log events.request_failure.fire and events.request_success.fire. In order to have logged requests I had to define simple wrapper like this one:
def locust_wrapper(func, arg, name, endpoint=None):
start_time = time.time()
try:
rsp = func(arg, endpoint)
except Exception as e:
print(e)
total_time = int((time.time() - start_time) * 1000)
events.request_failure.fire(request_type="WS", name=name, response_time=total_time, exception=e)
else:
total_time = int((time.time() - start_time) * 1000)
events.request_success.fire(request_type="WS", name=name, response_time=total_time, response_length=0)
return rsp
First of all you guys must investigate these problems and take action
by "you guys", do you mean the small group of unpaid volunteers helping to maintain the project that provided free software for you?
If so, a quicker option would be for you to step up and take action. The documentation is part of the codebase, and I'd be happy to review a Pull Request with your proposed changes.
Yeah, brutally yes, that I meant ;)
So If I will be able to find additional time I will do some update to the docs.
Cheers!
@JaniszM If you name the inlined class task_set - like it is in the documentation - I think it should work.
@heyman Unfortunatelly not. Looks like scope of the inner class in python has nothing to do with super class.
@JaniszM I just tested, and the following code works perfectly fine for me:
from locust import HttpLocust, TaskSet, task
class WebsiteUser(HttpLocust):
host = "http://127.0.0.1:8089"
min_wait = 2000
max_wait = 5000
class task_set(TaskSet):
@task
def page404(self):
self.client.get("/does_not_exist")
@task
def index(self):
self.client.get("/")
@task
def stats(self):
self.client.get("/stats/requests")
Thats weird. Maybe this is matter of python version?
Not updated for a long time? Closing this as could not reproduce.
Most helpful comment
by "you guys", do you mean the small group of unpaid volunteers helping to maintain the project that provided free software for you?
If so, a quicker option would be for you to step up and take action. The documentation is part of the codebase, and I'd be happy to review a Pull Request with your proposed changes.