1. 介绍
使用pip进行安装,下面是个简单例子:
from locust import HttpUser, taskclass HelloWorldUser(HttpUser):@taskdef hello_world(self):self.client.get("/hello")self.client.get("/world")
然后打开web页面:
点击start,会执行脚本代码,调用hello和world接口。
ramp-up的设置,一般而言:100以内的并发用户数,ramp-up时间设置为1-2s;100-500左右,rramp-up时间设置为2-3s;500以上,ramp-up时间设置为5-10s。
也可以使用命令行:
$ locust --headless --users 10 --spawn-rate 1 -H http://your-server.com
[2021-07-24 10:41:10,947] .../INFO/locust.main: No run time limit set, use CTRL+C to interrupt.
[2021-07-24 10:41:10,947] .../INFO/locust.main: Starting Locust 2.28.0
[2021-07-24 10:41:10,949] .../INFO/locust.runners: Ramping to 10 users using a 1.00 spawn rate
Name # reqs # fails | Avg Min Max Median | req/s failures/s
----------------------------------------------------------------------------------------------
GET /hello 1 0(0.00%) | 115 115 115 115 | 0.00 0.00
GET /world 1 0(0.00%) | 119 119 119 119 | 0.00 0.00
----------------------------------------------------------------------------------------------
Aggregated 2 0(0.00%) | 117 115 119 117 | 0.00 0.00[2021-07-24 10:44:42,484] .../INFO/locust.runners: All users spawned: {"HelloWorldUser": 10} (10 total users)
2. 测试脚本分析
import time
from locust import HttpUser, task, betweenclass QuickstartUser(HttpUser):wait_time = between(1, 5) # 或者可以使用@taskdef hello_world(self):self.client.get("/hello")self.client.get("/world")@task(3)def view_items(self):for item_id in range(10):self.client.get(f"/item?id={item_id}", name="/item")time.sleep(1)def on_start(self):self.client.post("/login", json={"username":"foo", "password":"bar"})
脚本中至少需要包含一个class。HttpUser继承自HttpSession,会给每一个用户创建一个self.client。
@task会给每一个用户创建一个greenlet(协程),然后就可以调用clinet的get和post方法来调用http服务:
response = self.client.get("/my-profile")
response = self.client.post("/login", {"username":"testuser", "password":"secret"})
@task中的数值表示权重。每个用户会以权重概率,从所有的task中选取一个执行。
view_items中的name属性,会将所有的url都归类到同一个/item下,而不是分成10个单独的url进行统计。
wait_time除了between还有如下几种:
constant(X):等待固定时间
constant_throughput: for an adaptive time that ensures the task runs (at most) X times per second.
constant_pacing:for an adaptive time that ensures the task runs (at most) once every X seconds
也可以自定义wait_time函数:
class MyUser(User):last_wait_time = 0def wait_time(self):self.last_wait_time += 1return self.last_wait_time
@tag标签可以用来指定跑哪些tasks。例如下面的例子中:
from locust import User, constant, task, tagclass MyUser(User):wait_time = constant(1)@tag('tag1')@taskdef task1(self):pass@tag('tag1', 'tag2')@taskdef task2(self):pass@tag('tag3')@taskdef task3(self):pass@taskdef task4(self):pass
If you started this test with --tags tag1, only task1 and task2 would be executed during the test. If you started it with --tags tag2 tag3, only task2 and task3 would be executed.
–exclude-tags will behave in the exact opposite way. So, if you start the test with --exclude-tags tag3, only task1, task2, and task4 will be executed. Exclusion always wins over inclusion, so if a task has a tag you’ve included and a tag you’ve excluded, it will not be executed.
3. 启动函数
如果需要在整个测试开始前或结束后执行代码,则需要下面的函数:
from locust import events@events.test_start.add_listener
def on_test_start(environment, **kwargs):print("A new test is starting")@events.test_stop.add_listener
def on_test_stop(environment, **kwargs):print("A new test is ending")
如果需要在每个process开始前执行一些代码,则需要下面的函数:
from locust import events
from locust.runners import MasterRunner@events.init.add_listener
def on_locust_init(environment, **kwargs):if isinstance(environment.runner, MasterRunner):print("I'm on master node")else:print("I'm on a worker or standalone node")
如果是每次request请求前都要执行一些代码,则需要在User里执行on_start函数:
class QuickstartUser(HttpUser):def on_start(self):self.client.post("/login", json={"username":"foo", "password":"bar"})
4. FastHttpUser
FastHttpUser使用了geventhttpclient,性能比基于requests的HttpUser快了数倍。In a best case scenario (doing small requests inside a while True-loop) a single Locust process (limited to one CPU core) can do around 16000 requests per second using FastHttpUser, and 4000 using HttpUser (tested on a 2021 M1 MacBook Pro and Python 3.11)
下面是一个例子:
from locust import FastHttpUser, run_single_user, task
from locust.contrib.fasthttp import RestResponseContextManager
from locust.user.wait_time import constantfrom collections.abc import Generator
from contextlib import contextmanagerclass MyUser(FastHttpUser):host = "https://postman-echo.com"wait_time = constant(180) # be nice to postman-echo.com, and dont run this at scale.@taskdef t(self):# should workwith self.rest("GET", "/get", json={"foo": 1}) as resp:if resp.js["args"]["foo"] != 1:resp.failure(f"Unexpected value of foo in response {resp.text}")# should workwith self.rest("POST", "/post", json={"foo": 1}) as resp:if resp.js["data"]["foo"] != 1:resp.failure(f"Unexpected value of foo in response {resp.text}")# assertions are a nice short way to express your expectations about the response. The AssertionError thrown will be caught# and fail the request, including the message and the payload in the failure content.assert resp.js["data"]["foo"] == 1, "Unexpected value of foo in response"# assertions are a nice short way to validate the response. The AssertionError they raise# will be caught by rest() and mark the request as failedwith self.rest("POST", "/post", json={"foo": 1}) as resp:# mark the request as failed with the message "Assertion failed"assert resp.js["data"]["foo"] == 2with self.rest("POST", "/post", json={"foo": 1}) as resp:# custom failure messageassert resp.js["data"]["foo"] == 2, "my custom error message"with self.rest("POST", "/post", json={"foo": 1}) as resp:# use a trailing comma to append the response text to the custom messageassert resp.js["data"]["foo"] == 2, "my custom error message with response text,"with self.rest("", "/post", json={"foo": 1}) as resp:# assign and assert in one lineassert (foo := resp.js["foo"])print(f"the number {foo} is awesome")# rest() catches most exceptions, so any programming mistakes you make automatically marks the request as a failure# and stores the callstack in the failure messagewith self.rest("POST", "/post", json={"foo": 1}) as resp:1 / 0 # pylint: disable=pointless-statement# response isn't even json, but RestUser will already have been marked it as a failure, so we dont have to do it againwith self.rest("GET", "/") as resp:passwith self.rest("GET", "/") as resp:# If resp.js is None (which it will be when there is a connection failure, a non-json responses etc),# reading from resp.js will raise a TypeError (instead of an AssertionError), so lets avoid that:if resp.js:assert resp.js["foo"] == 2# or, as a mildly confusing oneliner:assert not resp.js or resp.js["foo"] == 2# 404with self.rest("GET", "http://example.com/") as resp:pass# connection closedwith self.rest("POST", "http://example.com:42/", json={"foo": 1}) as resp:pass# An example of how you might write a common base class for an API that always requires
# certain headers, or where you always want to check the response in a certain way
class RestUserThatLooksAtErrors(FastHttpUser):abstract = True@contextmanagerdef rest(self, method, url, **kwargs) -> Generator[RestResponseContextManager, None, None]:extra_headers = {"my_header": "my_value"}with super().rest(method, url, headers=extra_headers, **kwargs) as resp:if resp.js and "error" in resp.js and resp.js["error"] is not None:resp.failure(resp.js["error"])yield respclass MyOtherRestUser(RestUserThatLooksAtErrors):host = "https://postman-echo.com"wait_time = constant(180) # be nice to postman-echo.com, and dont run this at scale.@taskdef t(self):with self.rest("GET", "/") as _resp:passif __name__ == "__main__":run_single_user(MyUser)
5. 其他参数
locust --help
Usage: locust [options] [UserClass ...]Common options:-h, --help show this help message and exit-f <filename>, --locustfile <filename>The Python file or module that contains your test,e.g. 'my_test.py'. Accepts multiple comma-separated.py files, a package name/directory or a url to aremote locustfile. Defaults to 'locustfile'.--config <filename> File to read additional configuration from. See https://docs.locust.io/en/stable/configuration.html#configuration-file-H <base url>, --host <base url>Host to load test, in the following format:https://www.example.com-u <int>, --users <int>Peak number of concurrent Locust users. Primarily usedtogether with --headless or --autostart. Can bechanged during a test by keyboard inputs w, W (spawn1, 10 users) and s, S (stop 1, 10 users)-r <float>, --spawn-rate <float>Rate to spawn users at (users per second). Primarilyused together with --headless or --autostart-t <time string>, --run-time <time string>Stop after the specified amount of time, e.g. (300s,20m, 3h, 1h30m, etc.). Only used together with--headless or --autostart. Defaults to run forever.-l, --list Show list of possible User classes and exit--config-users [CONFIG_USERS ...]User configuration as a JSON string or file. A list ofarguments or an Array of JSON configuration may beprovidedWeb UI options:--web-host <ip> Host to bind the web interface to. Defaults to '*'(all interfaces)--web-port <port number>, -P <port number>Port on which to run web host--headless Disable the web interface, and start the testimmediately. Use -u and -t to control user count andrun time--autostart Starts the test immediately (like --headless, butwithout disabling the web UI)--autoquit <seconds> Quits Locust entirely, X seconds after the run isfinished. Only used together with --autostart. Thedefault is to keep Locust running until you shut itdown using CTRL+C--web-login Protects the web interface with a login page. Seehttps://docs.locust.io/en/stable/extending-locust.html#authentication--tls-cert <filename>Optional path to TLS certificate to use to serve overHTTPS--tls-key <filename> Optional path to TLS private key to use to serve overHTTPS--class-picker Enable select boxes in the web interface to choosefrom all available User classes and Shape classesMaster options:Options for running a Locust Master node when running Locust distributed. A Master node need Worker nodes that connect to it before it can run load tests.--master Launch locust as a master node, to which worker nodesconnect.--master-bind-host <ip>IP address for the master to listen on, e.g'192.168.1.1'. Defaults to * (all availableinterfaces).--master-bind-port <port number>Port for the master to listen on. Defaults to 5557.--expect-workers <int>Delay starting the test until this number of workershave connected (only used in combination with--headless/--autostart).--expect-workers-max-wait <int>How long should the master wait for workers to connectbefore giving up. Defaults to wait forever--enable-rebalancing Re-distribute users if new workers are added orremoved during a test run. Experimental.Worker options:Options for running a Locust Worker node when running Locust distributed.Typically ONLY these options (and --locustfile) need to be specified on workers, since other options (-u, -r, -t, ...) are controlled by the master node.--worker Set locust to run in distributed mode with thisprocess as worker. Can be combined with setting--locustfile to '-' to download it from master.--processes <int> Number of times to fork the locust process, to enableusing system. Combine with --worker flag or let itautomatically set --worker and --master flags for anall-in-one-solution. Not available on Windows.Experimental.--master-host <hostname>Hostname of locust master node to connect to. Defaultsto 127.0.0.1.--master-port <port number>Port to connect to on master node. Defaults to 5557.Tag options:Locust tasks can be tagged using the @tag decorator. These options let specify which tasks to include or exclude during a test.-T [<tag> ...], --tags [<tag> ...]List of tags to include in the test, so only taskswith at least one matching tag will be executed-E [<tag> ...], --exclude-tags [<tag> ...]List of tags to exclude from the test, so only taskswith no matching tags will be executedRequest statistics options:--csv <filename> Store request stats to files in CSV format. Settingthis option will generate three files:<filename>_stats.csv, <filename>_stats_history.csv and<filename>_failures.csv. Any folders part of theprefix will be automatically created--csv-full-history Store each stats entry in CSV format to_stats_history.csv file. You must also specify the '--csv' argument to enable this.--print-stats Enable periodic printing of request stats in UI runs--only-summary Disable periodic printing of request stats during--headless run--reset-stats Reset statistics once spawning has been completed.Should be set on both master and workers when runningin distributed mode--html <filename> Store HTML report to file path specified--json Prints the final stats in JSON format to stdout.Useful for parsing the results in otherprograms/scripts. Use together with --headless and--skip-log for an output only with the json data.Logging options:--skip-log-setup Disable Locust's logging setup. Instead, theconfiguration is provided by the Locust test or Pythondefaults.--loglevel <level>, -L <level>Choose between DEBUG/INFO/WARNING/ERROR/CRITICAL.Default is INFO.--logfile <filename> Path to log file. If not set, log will go to stderrOther options:--show-task-ratio Print table of the User classes' task execution ratio.Use this with non-zero --user option if some classesdefine non-zero fixed_count attribute.--show-task-ratio-jsonPrint json data of the User classes' task executionratio. Use this with non-zero --user option if someclasses define non-zero fixed_count attribute.--version, -V Show program's version number and exit--exit-code-on-error <int>Sets the process exit code to use when a test resultcontain any failure or error. Defaults to 1.-s <number>, --stop-timeout <number>Number of seconds to wait for a simulated user tocomplete any executing task before exiting. Default isto terminate immediately. When running distributed,this only needs to be specified on the master.--equal-weights Use equally distributed task weights, overriding theweights specified in the locustfile.User classes:<UserClass1 UserClass2>At the end of the command line, you can list Userclasses to be used (available User classes can belisted with --list). LOCUST_USER_CLASSES environmentvariable can also be used to specify User classes.Default is to use all available User classesExamples:locust -f my_test.py -H https://www.example.comlocust --headless -u 100 -t 20m --processes 4 MyHttpUser AnotherUserSee documentation for more details, including how to set options using a file or environment variables: https://docs.locust.io/en/stable/configuration.html