Locust stress test

Keywords: Python Testing performance locust

(the catalog is on the right →

Official website

file

configuration parameter

Code file address of this article

The preparation section is of personal interest. You can directly see the use section of locust. You can find a public interface test, such as https://www.baidu.com

prepare

database

Start a database

# Lifting container
docker run -itd --name test_db -p 3396:3306 -e MYSQL_ROOT_PASSWORD=123456 mariadb
# Sign in
mysql -h192.168.1.105 -P3396 -uroot -p123456
# Create database
MariaDB [(none)]> create database tdb;

Make some data

import pymysql
import numpy as np

HOST = '192.168.1.105'
PORT = 3396
USER = 'root'
PWD = '123456'
DB = 'tdb'
TABLE = 'employee'

def getEmployee(bit=8):
    chars = [chr(i) for i in range(65, 91)] + [chr(i) for i in range(97, 123)]
    name = ''.join(np.random.choice(chars, bit))
    age = np.random.randint(18, 60)
    sex = np.random.choice(['0', '1'])
    return str((0, name, age, sex))

def dataGen():
    database = pymysql.connect(user=USER, password=PWD, host=HOST, port=PORT, database=DB, charset='utf8')
    cursor = database.cursor()

    # Create table
    sql_ct = "CREATE TABLE IF NOT EXISTS {} ( " \
             "eid INT AUTO_INCREMENT, " \
             "ename  VARCHAR(20) NOT NULL, " \
             "age INT, " \
             "sex VARCHAR(1), " \
             "PRIMARY KEY(eid))".format(TABLE)
    cursor.execute(sql_ct)

    for i in range(100000):

        employee = getEmployee()
        sql_i = "INSERT INTO {} VALUE {}".format(TABLE, employee)
        cursor.execute(sql_i)
        if (i+1)%100==0:
            print('\r[{}/100000]'.format(i+1), end='')
            database.commit()
    database.commit()

    database.close()

if __name__ == '__main__':
    dataGen()

The constructed data are as follows

Start a service

Make an http service, dbaserver.py

import pymysql
from socketserver import ThreadingMixIn
from http.server import HTTPServer
from http.server import SimpleHTTPRequestHandler
from sys import argv
import logging

HOST = '192.168.5.217'
PORT = 3396
USER = 'root'
PWD = '123456'
DB = 'tdb'
TABLE = 'employee'

def dataSelect(eid):
    database = pymysql.connect(user=USER, password=PWD, host=HOST, port=PORT, database=DB, charset='utf8')
    cursor = database.cursor()

    sql_s = "SELECT * FROM {} WHERE eid={}".format(TABLE, eid)
    cursor.execute(sql_s)
    res = cursor.fetchall()  # Obtain results
    database.close()
    if res:
        ee = res[0]
        return {'name': ee[1], 'age': ee[2], 'sex': 'female' if ee[3]=='0' else 'male'}
    return 'no this employee by eid={}'.format(eid)

class ThreadingServer(ThreadingMixIn, HTTPServer):
    pass

class RequestHandler(SimpleHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-type', 'text/plain;charset=utf-8')
        try:
            eid = int(self.path[1:])
        except:
            eid = -1
        response = dataSelect(eid)
        logging.info('request of {} by eid={}'.format(response, eid))
        self.end_headers()
        self.wfile.write(str(response).encode())

def run(server_class=ThreadingServer, handler_class=RequestHandler, port=8888):
    server_address = ('0.0.0.0', port)
    httpd = server_class(server_address, handler_class)
    logging.info('server start in http://{}:{}'.format(*server_address))
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        pass
    httpd.server_close()

def main():
    logging.basicConfig(level=logging.INFO, format='%(levelname)-8s %(asctime)s: %(message)s',
                        datefmt='%m-%d %H:%M')
    if len(argv) == 2:
        run(port=int(argv[1]))
    else:
        run()

if __name__ == '__main__':
    main()

Run the python script to access the service

locust pressure test

install

pip install locust

python based test script

from locust import HttpUser, between, task
import numpy as np

class StressHandler(HttpUser):							# HttpUser class that inherits locust
    wait_time = between(0, 0)   						# Send a random delay p1-p2 seconds before the request

    @task   											# The function to which this annotation is added will be executed
    def testApi(self):
	      # Random id
        eid = np.random.randint(1, 100000)	
        path = '/{}'.format(eid)
        # Just pass path here, and the domain name will be passed in from the web front end
        self.client.get(path)
        
    # @task
    def testBaidu(self):
        path = '/'
        self.client.get(path)

Run locust

locust -f stresshandler.py

The operation effect is as follows

Then, you can access it from the front end. Replace 0.0.0.0 with the ip of the machine that starts locust or 127.0.0.1. If you don't make it yourself, the default port is 8089.

Create a new task. Note that if host is not a domain name, you need to specify a port.

After startup

Further (multi process)

In a real scenario, the pressure generation capacity will be limited by the current machine performance. CPU and bandwidth are common constraints, and it may not be easy to improve the bandwidth. Rational use of multi-core CPU can further improve the pressure generation intensity. Lockus also provides such support, and even distributed pressure generation can be realized through multiple machines.

You can use the top command to observe CPU and memory usage

iftop observes bandwidth usage

iostat can observe the disk status (not used here)

locust -f stresshandler.py --master		# master
# Start another console
locust -f stresshandler.py --worker		# worker
# You can restart the console to start multiple worker s


There are two workers here. The number of users and the speed of user increase will be divided equally into two workers.

Further (profile)

Can be added to configure There are many items, which can be configured uniformly through the configuration file.

# master.conf
# https://docs.locust.io/en/stable/configuration.html
locustfile = stresshandler.py
master = true
web-port = 6789
print-stats = true
only-summary = true
locust --config master.conf
# worker.conf
# https://docs.locust.io/en/stable/configuration.html
locustfile = stresshandler.py
headless = true
worker = true
# master-host = 127.0.0.1
# master-port = 5557
# Need a new command line window to start
locust --config salve.conf

The interaction port between master and worker is 5557. If the ports conflict, please refer to the configuration document to modify the ports.

Go further (skip the front-end task)

It can be configured to write the parameters directly in the configuration file without passing in the parameters through the web, and the results will be obtained after startup.

  # master.conf
  locustfile = stresshandler.py
  master = true
  web-port = 6789
  host = https://www.baidu.com
  users = 9
  spawn-rate = 3
  run-time = 20s
  headless = true                 		# It is not started at the front end, and the test parameters need to be passed in with the above configuration
  csv = ./data/csv_prefix		        # Generate CSV result files, _stats.csv, _stats_history.csv and _failures.csv
  print-stats = true			    	# Print status on console
  html = ./data/web_report.html			# html front end report
  print-stats = true
  only-summary = true
  # worker.conf
  locustfile = stresshandler.py
  headless = true
  worker = true
  # master-host = 127.0.0.1
  # master-port = 5557
locust --conf worker.conf

Improve the script (multiple worker s)

The startup process can also be started with the help of python's multithreading.

import os
import subprocess

N_WORKER = 3
WORK_DIR = os.path.abspath(os.path.dirname(__file__))

def runMaster():
    print('start Yes master')
    subprocess.run('locust --config ./data/master.conf', shell=True, cwd=WORK_DIR)

def runWorker():
    print('start One worker')
    subprocess.Popen('locust --config ./data/worker.conf', shell=True, cwd=WORK_DIR)

def clear():
    # Clean up the tasks with 5557, which is the default interaction port between master and worker
    subprocess.run("kill -9 $(lsof -i:5557 | awk 'NR>1{print $2}')", shell=True)
    print('Cleared the task')

def main():
    for i in range(N_WORKER):
        runWorker()
    runMaster()


if __name__ == '__main__':
    clear()
    main()

A complete demo

Finally, how can I use this to start a pressure test( Code file address of this article)

Take this url as an example

https://www.baidu.com/s?wd=test 		#  The function is Baidu search "test"
Host:	 https://www.baidu.com
path:	 /s
params:	 wd=test

Modify test script

  • Add such a task in stresshandler.py

    	@task
        def testSearch(self):
            path = '/s?wd=test'
            self.client.get(path)
    
  • Modify the configuration files (data/master.conf, data/worker.conf). If not from the front end, the configuration files here use the configuration files in "further"

  • Modify the startup file start.py, mainly to modify the number of worker processes. The default is 3,

    N_WORKER = 3
    
  • Start start.py and python3 start.py, and the report will be generated in the data directory

Posted by CodeBuddy on Tue, 23 Nov 2021 13:27:38 -0800