Running tests in a debugger¶
Running Locust in a debugger is extremely useful when developing your tests. Among other things, you can examine a particular response or check some User instance variable.
But debuggers sometimes have issues with complex gevent-applications like Locust, and there is a lot going on in the framework itself that you probably aren’t interested in. To simplify this, Locust provides a method called run_single_user
:
from locust import HttpUser, run_single_user, task
class QuickstartUser(HttpUser):
host = "http://localhost"
@task
def hello_world(self):
with self.client.get("/hello", catch_response=True) as resp:
pass # maybe set a breakpoint here to analyze the resp object?
# if launched directly, e.g. "python3 debugging.py", not "locust -f debugging.py"
if __name__ == "__main__":
run_single_user(QuickstartUser)
It implicitly registers an event handler for the request event to print some stats about every request made:
type name resp_ms exception
GET /hello 38 ConnectionRefusedError(61, 'Connection refused')
GET /hello 4 ConnectionRefusedError(61, 'Connection refused')
You can configure exactly what is printed by specifying parameters to run_single_user
.
Make sure you have enabled gevent in your debugger settings. In VS Code’s launch.json
it looks like this:
{
"version": "0.2.0",
"configurations": [
{
"name": "Run current file",
"type": "python",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"gevent": true
},
{
"name": "Run locust",
"type": "python",
"request": "launch",
"module": "locust",
"args": [
"-f",
"${file}",
"--headless",
"--users=5"
],
"console": "integratedTerminal",
"gevent": true
}
]
}
If you want to the whole Locust runtime (with ramp up, command line parsing etc), you can do that too:
{
"version": "0.2.0",
"configurations": [
{
"name": "Locust: 5 users, with specific config file",
"type": "python",
"request": "launch",
"module": "locust",
"args": [
"-f",
"${file}",
"--headless",
"--users=5",
"--config=${fileDirname}/../locust.conf"
],
"console": "integratedTerminal",
"gevent": true
}
]
}
There is a similar setting in PyCharm.
Note
sys.settrace() should not be used when the debugger is being used
You can execute run_single_user multiple times, as shown in debugging_advanced.py.
Print HTTP communication¶
Sometimes it can be hard to understand why an HTTP request fails in Locust when it works from a regular browser/other application. Here’s how to examine the communication in detail:
For HttpUser
(python-requests):
# put this at the top of your locustfile (or just before the request you want to trace)
import logging
from http.client import HTTPConnection
HTTPConnection.debuglevel = 1
logging.basicConfig()
logging.getLogger().setLevel(logging.DEBUG)
requests_log = logging.getLogger("requests.packages.urllib3")
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True
For FastHttpUser
(geventhttpclient):
import sys
...
class MyUser(FastHttpUser):
@task
def t(self):
self.client.get("http://example.com/", debug_stream=sys.stderr)
Example output (for FastHttpUser):
REQUEST: http://example.com/
GET / HTTP/1.1
user-agent: python/gevent-http-client-1.5.3
accept-encoding: gzip, deflate
host: example.com
RESPONSE: HTTP/1.1 200
Content-Encoding: gzip
Accept-Ranges: bytes
Age: 460461
Cache-Control: max-age=604800
Content-Type: text/html; charset=UTF-8
Date: Fri, 12 Aug 2022 09:20:07 GMT
Etag: "3147526947+ident"
Expires: Fri, 19 Aug 2022 09:20:07 GMT
Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
Server: ECS (nyb/1D20)
Vary: Accept-Encoding
X-Cache: HIT
Content-Length: 648
<!doctype html>
<html>
<head>
...
These approaches can of course be used when doing a full load test, but you might get a lot of output :)