
> pip install locustio
Some feature / deficiency of Locust:
- You can write your test case in Python
- You can define the user time consumption on a page with min_wait and max_wait as milliseconds in locust class so that get a real user simulation.
- You can give weight for each test cases so you can simulate real user behaviours like during a specific period of time: 1 person signs up, 10 persons login, 60 persons visit pages, ...
- You can make http request as you do in your code such as get, post, put, delete, head, patch and options. So you can send request directly to your api, like: self.client.post("/login", {"username":"testuser", "password":"secret"})
- While Jmeter is thread based so it uses a separate thread to create a user but Locust id co-routine and work asynchronously as you see after running the number of user for each case. This means that you can create more user in Locust than in Jmeter.
- With the PyQuery library, you can query throughout your interface. For example you can find all the links in html and then send requests to respondent pages.
- There is some problem with single page application like if there is "#" in your url it behaves as there is no url.
- There is no graphic option but you can hack everything.
You can check the script below which is written for www.myhabit.com to run performance testing for the link on "/my-account" page. With the script, we are loging on the page and then we can open the my account page. The result may not be different when you want to test because it depends on many factors.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
parser.add_option( | |
'-H', '--host', | |
dest="host", | |
default=None, | |
help="Host to load test in the following format: http://10.21.32.33" | |
) | |
parser.add_option( | |
'--web-host', | |
dest="web_host", | |
default="", | |
help="Host to bind the web interface to. Defaults to '' (all interfaces)" | |
) | |
parser.add_option( | |
'-P', '--port', '--web-port', | |
type="int", | |
dest="port", | |
default=8089, | |
help="Port on which to run web host" | |
) | |
parser.add_option( | |
'-f', '--locustfile', | |
dest='locustfile', | |
default='locustfile', | |
help="Python module file to import, e.g. '../other.py'. Default: locustfile" | |
) | |
# if locust should be run in distributed mode as master | |
parser.add_option( | |
'--master', | |
action='store_true', | |
dest='master', | |
default=False, | |
help="Set locust to run in distributed mode with this process as master" | |
) | |
# if locust should be run in distributed mode as slave | |
parser.add_option( | |
'--slave', | |
action='store_true', | |
dest='slave', | |
default=False, | |
help="Set locust to run in distributed mode with this process as slave" | |
) | |
# master host options | |
parser.add_option( | |
'--master-host', | |
action='store', | |
type='str', | |
dest='master_host', | |
default="127.0.0.1", | |
help="Host or IP address of locust master for distributed load testing. Only used when running with --slave. Defaults to 127.0.0.1." | |
) | |
parser.add_option( | |
'--master-port', | |
action='store', | |
type='int', | |
dest='master_port', | |
default=5557, | |
help="The port to connect to that is used by the locust master for distributed load testing. Only used when running with --slave. Defaults to 5557. Note that slaves will also connect to the master node on this port + 1." | |
) | |
parser.add_option( | |
'--master-bind-host', | |
action='store', | |
type='str', | |
dest='master_bind_host', | |
default="*", | |
help="Interfaces (hostname, ip) that locust master should bind to. Only used when running with --master. Defaults to * (all available interfaces)." | |
) | |
parser.add_option( | |
'--master-bind-port', | |
action='store', | |
type='int', | |
dest='master_bind_port', | |
default=5557, | |
help="Port that locust master should bind to. Only used when running with --master. Defaults to 5557. Note that Locust will also use this port + 1, so by default the master node will bind to 5557 and 5558." | |
) | |
# if we should print stats in the console | |
parser.add_option( | |
'--no-web', | |
action='store_true', | |
dest='no_web', | |
default=False, | |
help="Disable the web interface, and instead start running the test immediately. Requires -c and -r to be specified." | |
) | |
# Number of clients | |
parser.add_option( | |
'-c', '--clients', | |
action='store', | |
type='int', | |
dest='num_clients', | |
default=1, | |
help="Number of concurrent clients. Only used together with --no-web" | |
) | |
# Client hatch rate | |
parser.add_option( | |
'-r', '--hatch-rate', | |
action='store', | |
type='float', | |
dest='hatch_rate', | |
default=1, | |
help="The rate per second in which clients are spawned. Only used together with --no-web" | |
) | |
# Number of requests | |
parser.add_option( | |
'-n', '--num-request', | |
action='store', | |
type='int', | |
dest='num_requests', | |
default=None, | |
help="Number of requests to perform. Only used together with --no-web" | |
) | |
# log level | |
parser.add_option( | |
'--loglevel', '-L', | |
action='store', | |
type='str', | |
dest='loglevel', | |
default='INFO', | |
help="Choose between DEBUG/INFO/WARNING/ERROR/CRITICAL. Default is INFO.", | |
) | |
# log file | |
parser.add_option( | |
'--logfile', | |
action='store', | |
type='str', | |
dest='logfile', | |
default=None, | |
help="Path to log file. If not set, log will go to stdout/stderr", | |
) | |
# if we should print stats in the console | |
parser.add_option( | |
'--print-stats', | |
action='store_true', | |
dest='print_stats', | |
default=False, | |
help="Print stats in the console" | |
) | |
# only print summary stats | |
parser.add_option( | |
'--only-summary', | |
action='store_true', | |
dest='only_summary', | |
default=False, | |
help='Only print the summary stats' | |
) | |
# List locust commands found in loaded locust files/source files | |
parser.add_option( | |
'-l', '--list', | |
action='store_true', | |
dest='list_commands', | |
default=False, | |
help="Show list of possible locust classes and exit" | |
) | |
# Display ratio table of all tasks | |
parser.add_option( | |
'--show-task-ratio', | |
action='store_true', | |
dest='show_task_ratio', | |
default=False, | |
help="print table of the locust classes' task execution ratio" | |
) | |
# Display ratio table of all tasks in JSON format | |
parser.add_option( | |
'--show-task-ratio-json', | |
action='store_true', | |
dest='show_task_ratio_json', | |
default=False, | |
help="print json data of the locust classes' task execution ratio" | |
) | |
# Version number (optparse gives you --version but we have to do it | |
# ourselves to get -V too. sigh) | |
parser.add_option( | |
'-V', '--version', | |
action='store_true', | |
dest='show_version', | |
default=False, | |
help="show program's version number and exit" | |
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from locust import HttpLocust, TaskSet, task | |
from pyquery import PyQuery | |
import random | |
class UserBehaviour(TaskSet): | |
urls_from_page = [] | |
def on_start(self): | |
# on starting, we will login and get session then | |
# we wil be able to send request to login required page "/my-account" | |
self.client.post("/", {"email": "test@testrisk.com", "password": "Passw0rd"}) | |
request = self.client.get("/my-account") | |
# from content of the page, we will take all links and then store them to a variable | |
pq = PyQuery(request.content) | |
# PyQuery can optain data from the page by jQuery | |
link_elements = pq(".link > a") | |
for url in link_elements: | |
self.urls_from_page.append(url.attrib["href"]) | |
@task | |
def load_page(self): | |
# this is a task to run performance testing | |
# we will send http request the url taken from "my-account" page | |
try: | |
url = random.choice(self.urls_from_page) | |
self.client.get(url) | |
except IndexError: | |
print "... something wrong check, pq!" | |
pass | |
class User(HttpLocust): | |
host = "http://www.myhabit.com" | |
task_set = UserBehaviour | |
# stop the test after 120 seconds | |
stop_timeout = 120 | |
# time for user behaviour | |
# we can assume that they wait 0.5 to 6 seconds on a page | |
min_wait = 500 | |
max_wait = 6000 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Percentage of the requests completed within given times | |
Name # reqs 50% 66% 75% 80% 90% 95% 98% 99% 100% | |
----------------------------------------------------------------------------------------------------------------------------- | |
/ 92 720 820 1000 1300 3800 7500 18000 20000 903 | |
/help/200644950 14 500 510 510 520 530 940 940 940 936 | |
/my-account 92 310 320 360 360 640 1000 4400 5600 5609 | |
/orc 14 860 910 920 930 1300 4500 4500 4500 4453 | |
/yacontactus 20 850 930 980 1100 1300 1300 1300 1300 1349 | |
-------------------------------------------------------------------------------------------------------------------------------------------- |