部分内容转自https://my.oschina.net/u/1433482/blog/634047
什么是locustio?
- Locust是易于使用、分布式的用户负载测试工具。用于网站(或其他系统)的负载测试,计算出系统可以处理并发用户数。
测试时大量蝗虫会攻击你的网站。每只蝗虫(或叫测试用户)可以自定义、测试过程由web界面实时监控。这能帮助测试并确定瓶颈。
Locust 完全基于的事件,单机可以支持数千用户。它不使用回调,而是基于轻量进程gevent, 能简单地实线各种场景。
特点
- Python书写场景
- 无需笨重的UI或XML。仅仅是代码,协程而不是回调。
- 分布式,可扩展和,支持成千上万的用户
- 基于Web的用户界面(基于flask)
- Locust有整洁HTML + JS用户界面,实时展示测试细节,跨平台和易于扩展。
- 可以测试任何系统
- 可控制
- 我们研究了现有的解决方案,都不符合要求。比如Apache JMeter和Tsung。JMeter基于UI操作,容易上手,但基本上不具备编程能力。其次JMeter基于线程,要模拟数千用户几乎不可能。 Tsung基于Erlang,能模拟上千用户并易于扩展,但它它基于XML的DSL,描述场景能力弱,且需要大量的数据处理才知道测试结果。
无论如何,我们试图解决创建蝗虫,当这些问题。希望以上都不是painpoints应该存在。
我想你可以说我们真的只是想在这里从头开始自己的痒。我们希望其他人会发现,因为我们做的是有益的。安装
- github地址:https://github.com/locustio/locust 可找到最新版本,支持python2.7~3.6
- pip install locustio or easy_install locustio
校验
- 执行”locust –help”能看到如下信息表示安装成功:123456# locust --helpUsage: locust [options] [LocustClass [LocustClass2 ... ]]Options:-h, --help show this help message and exit...
分布式测试还需要安装pyzmq。尽管locustio可以在Windows运行,但是考虑效率不推荐。
快速入门
- 执行”locust –help”能看到如下信息表示安装成功:
- 直接查看官方demo
- 官方手册地址:http://docs.locust.io/en/latest/index.html
项目实战
- 下面我们对一个 socket.io(基于websocket,nodejs)的im项目进行实践
- 涉及第三方库:
- socketIO-client-2
通过locust编写socketio客户端
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159# -*- coding:utf-8 -*-import requestsimport jsonimport timeimport typesimport sysimport osimport randomimport loggingfrom socketIO_client import SocketIO, LoggingNamespace, BaseNamespacefrom locust import HttpLocust, TaskSet, task, eventslogger = logging.getLogger('requests')logging.getLogger('requests').setLevel(logging.WARNING)logging.basicConfig(level=logging.DEBUG)a = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']did = "did"+str(long(time.time()*1000)) + random.choice(a)#print diddef on_message(*args):print("msg receive:", args)hosts = 'http://talk.xxxx.com'port = 80"""流程:1、创建会话;2、发送消息;"""class WebsiteTasks(TaskSet):def on_start(self):self.conversationid = Nonetoken = self.gettoken()start = time.time()try:self.sk = SocketIO(hosts,port=port,params={'token': token})end = time.time()events.request_success.fire(request_type="Send",name="Connection",response_time=int((end - start) * 1000),response_length=0,)except Exception as err:events.request_failure.fire(request_type="Send",name="Connection",response_time=int((time.time() - start) * 1000),response_length=0,)self.sk.on('message', self.create_conversation)data = {"sn": 0,"ver": 2,"time": int(time.time()),"cmid": "".join(map(lambda xx:(hex(ord(xx))[2:]),os.urandom(16))),"confirm": 1,"type": "Conversation","payload": {"cmd": "createConversation","data": {"Id":""}}}self.sk.wait(seconds=1)#print(data)try:start = time.time()self.sk.send(data, self.create_conversation)self.sk.wait_for_callbacks(seconds=1)end = time.time()events.request_success.fire(request_type="Send",name="CreateConversation",response_time=int((end - start) * 1000),response_length=0,)except Exception as err:logger.exception("send create conversation fail:%s", err)raise Exception("send create conversation fail:{0}".format(err))if self.conversationid is None:raise Exception("create conversation failed!!")def create_conversation(self, *args):if type(args[0]) is types.DictType:rp = args[0]if rp["payload"]["cmd"] == 'createConversation':self.conversationid = rp["payload"]["data"]["conversationId"]#print "conversationid", self.conversationiddef gettoken(self):uid = 0try:# 通过flask写了一个用户id的排号器url = "http://192.168.149.34:5000/getuser"# r = requests.get(url)r = self.client.get("/getuser")uid = eval(r.text)[1]except Exception as e:raise Exception("getUid Error:{0} content:{1}".format(e, r.text))try:#print "uid:",uid# 获取登录tokenurl = 'http://im.xxxx.com/chat'payload = {....}r = requests.get(url, params=payload)token = r.json().get(u'data').get(u'token')except Exception as e:raise Exception("getToken Error:{0} content:{1}".format(e, r.content))#print tokenreturn tokendef sendmssage(self):# 参考wiki的消息体data = {"sn": 0,"ver": 2,"time": int(time.time()),"cmid": "".join(map(lambda xx:(hex(ord(xx))[2:]),os.urandom(16))),"confirm": 0,"type": "","payload": {"cmd": "chat","data": {"conversationId": self.conversationid,"msgType": 'rich',"msgContent": "load testing...."}}}#print(data)try:start = time.time()self.sk.send(data, on_message)self.sk.wait_for_callbacks(seconds=1)end = time.time()events.request_success.fire(request_type="Send",name="Chating",response_time=int((end - start) * 1000),response_length=0,)except Exception as err:logger.exception("send message fail:%s", err)raise Exception("send message fail:{0}".format(err))class WebsiteUser(HttpLocust):task_set = WebsiteTasksmin_wait = 5000max_wait = 15000一般http请求demo
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566# -*- coding:utf-8 -*-import requestsimport jsonimport timeimport sysimport osimport randomfrom locust import HttpLocust, TaskSet, task, eventsfrom requests.exceptions import (RequestException, MissingSchema,InvalidSchema, InvalidURL)class WebsiteTasks(TaskSet):def on_start(self):passdef check_response(self, response):try:response.raise_for_status()except RequestException as e:events.request_failure.fire(request_type=response.locust_request_meta["method"],name=response.locust_request_meta["name"],response_time=response.locust_request_meta["response_time"],exception=e,)else:code = response.json().get("code")if code == 1000:events.request_success.fire(request_type=response.locust_request_meta["method"],name=response.locust_request_meta["name"],response_time=response.locust_request_meta["response_time"],response_length=response.locust_request_meta["content_size"],)else:events.request_failure.fire(request_type=response.locust_request_meta["method"],name=response.locust_request_meta["name"],response_time=response.locust_request_meta["response_time"],response_length=response.locust_request_meta["content_size"],exception="Response Code Error! Code:{0}".format(code))def requests_Msgs(self):cookies= {...}url = "/business"payload = {...}headers = {'Content-Type':'application/x-www-form-urlencoded'}try:rsp = self.client.post(url, data=payload, headers=headers, catch_response=True, cookies=cookies)# print r.textself.check_response(rsp)except Exception as err:# logger.exception("request Msgs fail:%s", err)raise Exception("request Msgs fail:{0}".format(err))class WebsiteUser(HttpLocust):task_set = WebsiteTasksmin_wait = 5000max_wait = 15000host = "http://chat.xxxx.com"
HttpLocust继承自Locust,添加了client属性。client属性是HttpSession实例,可以用于生成HTTP请求。
on_start为client初始化时执行的步骤。
task表示下面方法是测试内容,里面的数字执行比例,这里about页面占三分之一,主页占三分之二。
task_set指定client执行的类。min_wait和max_wait为两次执行之间的最小和最长等待时间。
启动
- 启动locust后台,–port可自定义端口,默认80891234locust -f imlocust.py --host http://192.168.149.34:5000 --port 80[root@localhost im]# locust -f imlocust3.py --host http://192.168.149.34:5000 --port 80[2017-04-21 15:34:36,366] localhost.localdomain/INFO/locust.main: Starting web monitor at *:80[2017-04-21 15:34:39,103] localhost.localdomain/INFO/locust.main: Starting Locust 0.8a2
- 启动locust后台,–port可自定义端口,默认8089
在浏览器启动locust
- 打开http://127.0.0.1:80/,配置模拟的用户数"Number of users to simulate”和每秒发起的用户数”Hatch rate”,提交执行测试。
这时在浏览器就可以看到实时的测试结果。点击浏览器上方的”stop”即可停止测试。
- 打开http://127.0.0.1:80/,配置模拟的用户数"Number of users to simulate”和每秒发起的用户数”Hatch rate”,提交执行测试。
命令行按Ctrl + c , 可以显示一些摘要,类似下图的
1234567891011121314[2015-05-08 16:48:19,884] andrew-Hi-Fi-A88S2/INFO/locust.main: Shutting down (exit code 0), bye.Name # reqs # fails Avg Min Max | Median req/s--------------------------------------------------------------------------------------------------------------------------------------------GET / 36 0(0.00%) 260 206 411 | 250 0.90GET /about 17 0(0.00%) 199 146 519 | 170 0.10--------------------------------------------------------------------------------------------------------------------------------------------Total 53 0(0.00%) 1.00Percentage of the requests completed within given timesName # reqs 50% 66% 75% 80% 90% 95% 98% 99% 100%--------------------------------------------------------------------------------------------------------------------------------------------GET / 36 250 260 260 270 370 400 410 410 411GET /about 17 170 180 180 200 290 520 520 520 519--------------------------------------------------------------------------------------------------------------------------------------------网页上可以下载csv文件,一个是调配记录,一个是请求记录。
1234567891011# cat distribution_1431074713.45.csv"Name","# requests","50%","66%","75%","80%","90%","95%","98%","99%","100%""GET /",36,250,260,260,270,370,400,410,410,411"GET /about",17,170,180,180,200,290,520,520,520,519"None Total",53,250,250,260,260,310,400,410,520,519# cat requests_1431074710.05.csv"Method","Name","# requests","# failures","Median response time","Average response time","Min response time","Max response time","Average Content Size","Requests/s""GET","/",36,0,250,260,206,411,9055,0.76"GET","/about",17,0,170,199,146,519,4456,0.36"None","Total",53,0,250,241,146,519,7579,1.12其他:
- 指定测试文件启动:locust -f ../locust_files/my_locust_file.py –host=http://example.com
分布式测试时作为主测试机:locust -f ../locust_files/my_locust_file.py –master –host=http://example.com
分布式测试时作为从测试机:locust -f ../locust_files/my_locust_file.py –slave –master-host=192.168.0.100 host=http://example.com。master-host的默认值是127.0.0.1。
- 指定测试文件启动:locust -f ../locust_files/my_locust_file.py –host=http://example.com
- locustfile
- locustfile需要定义至少一个locust类。
Locust类
- locust类代表用户。属性如下:
- task_set属性
- task_set属性指向定义了用户的行为的TaskSet类。
- min_wait和max_wait属性
- 两次执行之间的最小和最长等待时间,单位:毫秒,即执行各任务之间等待时间。默认为1000,并且因此蝗虫永远等待1秒各任务之间如果min_wait和MAX_WAIT未声明。
- 用下面locustfile,每个用户将等待任务之间5到15秒:
- weight属性
可以同时执行同一文件的多个locust:
1# locust -f locust_file.py WebUserLocust MobileUserLocust嵌套的python代码示例:
1234567891011121314151617class ForumPage(TaskSet):def read_thread(self):passdef new_thread(self):passdef stop(self):self.interrupt()class UserBehaviour(TaskSet):tasks = {ForumPage:10}def index(self):pass类嵌套示例:
123456class MyTaskSet(TaskSet):class SubTaskSet(TaskSet):def my_task(self):passHTTP请求
- HttpLocust继承自Locust,添加了client属性。client属性是HttpSession实例,调用了requests,可以用于生成HTTP请求。 self.client设置了属性指向 self.locust.client。
get 和post示例:
123456# getresponse = self.client.get("/about")print "Response status code:", response.status_codeprint "Response content:", response.content# postresponse = self.client.post("/login", {"username":"testuser", "password":"secret"})注意失败的任何请求如连接错误,超时等不产生异常,而是返回None,status_code是0。
- 默认只要返回的不是200都是失败。也可以设置失败和成功:1234567with client.get("/", catch_response=True) as response:if response.content != "Success":response.failure("Got wrong response")with client.get("/does_not_exist/", catch_response=True) as response:if response.status_code == 404:response.success()
动态参数:
123# Statistics for these requests will be grouped under: /blog/?id=[id]for i in range(10):client.get("/blog?id=%i" % i, name="/blog?id=[id]")分布式测试
- 主机使用–master,它不会模拟任何用户。实际测试机需要–slave和–master-host参数。通常一个cpu核可以执行一个从机。
master的参数还有: “–master-bind-host=X.X.X.X”和–master-bind-host=X.X.X.X。
从机的参数有:”–master-port=5557”,或者 –slave –master-host=X.X.X.X。
- 主机使用–master,它不会模拟任何用户。实际测试机需要–slave和–master-host参数。通常一个cpu核可以执行一个从机。
- locust类代表用户。属性如下: