CLOVERšŸ€

That was when it all began.

č² č·ćƒ†ć‚¹ćƒˆćƒ„ćƒ¼ćƒ«ć€Locust恧遊恶

恓悌ćÆ态ćŖć«ć‚’ć—ćŸćć¦ę›øć„ćŸć‚‚ć®ļ¼Ÿ

  • LocustćØć„ć†č² č·ćƒ†ć‚¹ćƒˆćƒ„ćƒ¼ćƒ«ćŒć‚ć‚‹ćØčžćć€ćƒ†ć‚¹ćƒˆć‚·ćƒŠćƒŖć‚Ŗć‚’ćƒ—ćƒ­ć‚°ćƒ©ćƒ ć§ę›ø恑悋恝恆ćŖ恮恧試恗恦ćæ悈恆恋ćØ

Locust - A modern load testing framework

Locust コトハジメ - Qiita

今回ćÆ态恩悓ćŖćƒ„ćƒ¼ćƒ«ć‹ęŠŠę”ć™ć‚‹ćØ恓悍悒ē›®ęØ™ć«č©¦ć—ć¦ćæć¾ć™ć€‚

Locustļ¼Ÿ

Python恧ę›øć‹ć‚ŒćŸć€č² č·ćƒ†ć‚¹ćƒˆćƒ„ćƒ¼ćƒ«ć§ć™ć€‚

Locust - A modern load testing framework

GitHubäøŠć®starę•°ć‚‚å¤šćć¦ć€å‰²ćØäŗŗę°—ć®ćƒ„ćƒ¼ćƒ«ć®ć‚ˆć†ć«č¦‹ćˆć¾ć™ć€‚

GitHub - locustio/locust: Scalable user load testing tool written in Python

ć‚Ŗćƒ•ć‚£ć‚·ćƒ£ćƒ«ć‚µć‚¤ćƒˆć‚„GitHubć«ć‚ˆć‚‹ćØ态仄äø‹ć®ć‚ˆć†ćŖē‰¹å¾“悒ꌁ恤悈恆恧恙怂
ā€»GitHubć®å†…å®¹ć®ę–¹ćŒē“°ć‹ć„恧恙恭

  • ćƒ†ć‚¹ćƒˆć‚·ćƒŠćƒŖć‚Ŗ悒Pythonć‚³ćƒ¼ćƒ‰ć§ę›ø恏恓ćØ恌åÆčƒ½
  • ć‚¹ć‚±ćƒ¼ćƒ©ćƒ–ćƒ«ć§ć€åˆ†ę•£å®Ÿč”ŒćŒåÆčƒ½
  • Web UIä»˜ć
  • ć©ć®ć‚ˆć†ćŖć‚·ć‚¹ćƒ†ćƒ ć§ć‚‚ćƒ†ć‚¹ćƒˆćŒć§ćć‚‹ļ¼ˆćŸć ć—态Locustć®ć‚³ć‚¢ę©Ÿčƒ½ćÆWeb悒ć‚æćƒ¼ć‚²ćƒƒćƒˆć«ć—ć¦ć„ć‚‹ļ¼‰
  • å°ć•ćä½œć‚‰ć‚Œć¦ć„ć‚‹ć®ć§ć€ćƒćƒƒć‚ÆćŒå®¹ę˜“

LocustćÆ态IOć«é–¢ć™ć‚‹éƒØåˆ†ć«geventćØć„ć†ćƒ©ć‚¤ćƒ–ćƒ©ćƒŖ悒ä½æē”Øć—ć¦ć„ć¾ć™ć€‚

What is gevent? — gevent 1.4.1.dev0 documentation

Gevent チュートリアル

恔ćŖćæ恫态LocustćÆć€Œć‚¤ćƒŠć‚“ć€ć‚’ę„å‘³ć™ć‚‹ć‚“ć§ć™ć­ć€‚ćƒ­ć‚“ć‚’č¦‹ć‚‹ćØ态ē¢ŗ恋恫ā€¦ć€‚

f:id:Kazuhira:20190111205727p:plain

ę©Ÿčƒ½ēš„恫ćÆć€ć„ć‚ć„ć‚č¦‹ć¦ć„ććØApache JMeterćŖć©ć®ę–¹ćŒå¤šę©Ÿčƒ½ć«č¦‹ćˆćŸć‚Šć‚‚ć™ć‚‹ć®ć§ć™ćŒć€ę‰‹ę®µć®ć²ćØ恤ćØć—ć¦ć”ć‚‡ć£ćØ
ęŠ¼ć•ćˆć¦ćæ悋ćØć—ć¾ć—ć‚‡ć†ć€‚

Locustć®ć‚¤ćƒ³ć‚¹ćƒˆćƒ¼ćƒ«

恧ćÆć€ć¾ćšęœ€åˆć«Locustć‚’ć‚¤ćƒ³ć‚¹ćƒˆćƒ¼ćƒ«ć—ć¾ć™ć€‚

Installation — Locust 0.9.0 documentation

Locustć§ć‚µćƒćƒ¼ćƒˆć•ć‚Œć¦ć„ć‚‹Pythonć®ćƒćƒ¼ć‚øćƒ§ćƒ³ćÆ态恓恔悉怂

Installation / Supported Python Versions

今回ćÆ态Ubuntu Linux 18.04 LTSäøŠć§ć€Python 3.6恧ä½æć£ć¦ćæć¾ć™ć€‚

pipć§ć‚¤ćƒ³ć‚¹ćƒˆćƒ¼ćƒ«ć™ć‚‹ć‚‰ć—ć„ć®ć§ć€ćƒćƒ¼ć‚øćƒ§ćƒ³ē¢ŗčŖć€‚

$ python3 -V
Python 3.6.7


$ pip3 -V
pip 9.0.1 from /usr/lib/python3/dist-packages (python 3.6)

ć‚¤ćƒ³ć‚¹ćƒˆćƒ¼ćƒ«ć€‚

$ python3 -m pip install locustio

ć‚·ć‚§ćƒ«ć‚’čµ·å‹•ć—ćŖ恊恙ćØ态locustćŒå‹•ć‹ć›ć‚‹ć‚ˆć†ć«ćŖć£ć¦ć„ć¾ć™ć€‚

$ locust --version
[2019-01-03 04:30:31,327] bdbddbe84a18/INFO/stdout: Locust 0.9.0
[2019-01-03 04:30:31,327] bdbddbe84a18/INFO/stdout:

恂ćØćÆ态Max open fiiles恫ćÆę³Øę„ć—ć¦ćŠć„ćŸę–¹ćŒć„ć„ć§ć™ć‚ˆć€ćØ怂

Installation / Increasing Maximum Number of Open Files Limit

恓悌恧态ęŗ–å‚™ćÆ完äŗ†ć§ć™ć€‚

ä»Šå›žć®ćƒ†ć‚¹ćƒˆć‚æćƒ¼ć‚²ćƒƒćƒˆ

恕恦态Locust悒ä½æć£ćŸćƒ†ć‚¹ćƒˆć‚·ćƒŠćƒŖć‚Ŗ悒ę›ø恍恟恄ćØć“ć‚ć§ć™ćŒć€ćƒ†ć‚¹ćƒˆåÆ¾č±”ćŒćŖ恄ćØ恩恆恫悂ćŖć‚Šć¾ć›ć‚“ć€‚

ćƒ­ć‚°ć‚¤ćƒ³ćØć‹ć‚ć£ćŸę–¹ćŒć„ć„ć—ā€¦ć§ć‚‚ć€ä½œć‚‹ć®é¢å€’ć ć—ā€¦ćØꂩ悓恠ēµęžœć€Redmine悒ä½æ恆恓ćØć«ć—ć¾ć—ćŸć€‚

Docker Composeć§ć€ć•ć£ćć‚ŠćØē”Øꄏ怂
docker-compose.yml

version: '3'
services:
  redmine:
    image: redmine:4.0.0
    ports:
      - "3000:3000"
    restart: always
    environment:
      REDMINE_DB_MYSQL: "mysql"
      REDMINE_DB_DATABASE: "redmine"
      REDMINE_DB_USERNAME: "user"
      REDMINE_DB_PASSWORD: "password"
  mysql:
    image: mysql:5.7.24
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: "secret"
      MYSQL_DATABASE: "redmine"
      MYSQL_USER: "user"
      MYSQL_PASSWORD: "password"

DockerHub / Redmine

DockerHub / MySQL

čµ·å‹•ć€‚

$ docker-compose up

ćƒ†ć‚¹ćƒˆåÆ¾č±”ć®ćƒ¦ćƒ¼ć‚¶ćƒ¼ćÆć€ćƒ‡ćƒ•ć‚©ćƒ«ćƒˆć®ć€Œadminć€ć‚’ćć®ć¾ć¾ä½æ恆恓ćØć«ć—ć¾ć™ć€‚ćƒ‘ć‚¹ćƒÆćƒ¼ćƒ‰ćÆ态怌admin-passwordć€ć«
å¤‰ę›“ć—ć¾ć—ćŸć€‚

恓恮RedminećøćÆ态怌http://192.168.0.3:3000ć€ć§ć‚¢ć‚Æć‚»ć‚¹ć™ć‚‹ć‚‚ć®ćØć—ć¾ć™ć€‚

Locustć§ćƒ†ć‚¹ćƒˆć‚’ę›ø恏

恧ćÆć€ć„ć‚ˆć„ć‚ˆćƒ†ć‚¹ćƒˆć‚’ę›ø恄恦ćæ悋ćØć—ć¾ć—ć‚‡ć†ć€‚

ć“ć®ć‚ćŸć‚Šć‚’č¦‹ćŖ恌悉ā€¦

Quick start — Locust 0.9.0 documentation

Writing a locustfile — Locust 0.9.0 documentation

https://github.com/locustio/locust/tree/master/examples

ć¾ćšćÆę›ø恄恦ćæćŸć®ćŒć€ć“ć”ć‚‰ć€‚
locustfile.py

from locust import HttpLocust, TaskSet, task

import re

class UserBehavior(TaskSet):
    def on_start(self):
        self.login()

    def on_stop(self):
        self.logout()

    def login(self):
        response = self.client.get("/login")

        csrf_param = re.search("<meta name=\"csrf-param\" content=\"([^\"]+)\" />", response.text).group(1)
        csrf_token = re.search("<meta name=\"csrf-token\" content=\"([^\"]+)\" />", response.text).group(1)

        self.client.post("/login", {"username": "admin", "password": "admin-password", csrf_param: csrf_token})
    
    def logout(self):
        response = self.client.get("/")

        csrf_param = re.search("<meta name=\"csrf-param\" content=\"([^\"]+)\" />", response.text).group(1)
        csrf_token = re.search("<meta name=\"csrf-token\" content=\"([^\"]+)\" />", response.text).group(1)

        self.client.post("/logout", {csrf_param: csrf_token})

    @task
    def top(self):
        self.client.get("/")

    @task(2)
    def mypage(self):
        with self.client.get("/my/page", catch_response = True) as response:
            if response.status_code != 200:
                response.failure("not authenticated???")

    @task
    def projects(self):
        self.client.get("/projects")

class RedmineUser(HttpLocust):
    task_set = UserBehavior
    min_wait = 500
    max_wait = 1000

ęœ€åˆć«ć€HttpLocustļ¼ˆLocustć®ć‚µćƒ–ć‚Æćƒ©ć‚¹ļ¼‰ć‚’ē¶™ę‰æ恗恟ć‚Æćƒ©ć‚¹ć‚’ä½œęˆć—ć¾ć™ć€‚

class RedmineUser(HttpLocust):
    task_set = UserBehavior
    min_wait = 500
    max_wait = 1000

task_set恫ćÆć€å®Ÿéš›ć«ćƒ†ć‚¹ćƒˆć‚’čØ˜č¼‰ć—ćŸć‚Æćƒ©ć‚¹ć‚’ęø”ć—ć¾ć™ć€‚

min_waitćØmax_waitćÆ态ć‚æć‚¹ć‚Æļ¼ˆå¾Œčæ°ļ¼‰ć®é–“ć®å¾…ę©Ÿę™‚é–“ć®ęœ€å°ć€ęœ€å¤§ć‚’ćć‚Œćžć‚ŒęŒ‡å®šć—ć¾ć™ļ¼ˆćƒŸćƒŖē§’ļ¼‰ć€‚恓恮間恮Ꙃ間恧态ć‚æć‚¹ć‚Æ間恧
ćƒ©ćƒ³ćƒ€ćƒ ć«waitć—ć¾ć™ć€‚

wait_functionć«é–¢ę•°ć‚’ęŒ‡å®šć™ć‚‹ć“ćØć§ć€ć‚‚ć£ćØē“°ć‹ćć‚³ćƒ³ćƒˆćƒ­ćƒ¼ćƒ«ć™ć‚‹ć“ćØ悂åÆčƒ½ćŖ悈恆恧恙怂

Locust class

ē¶šć„ć¦ć€ćƒ†ć‚¹ćƒˆć®å†…å®¹ć‚’å®šē¾©ć—ćŸć®ćŒć€ć“ć”悉恮TaskSet悒ē¶™ę‰æ恗恟ć‚Æćƒ©ć‚¹ć€‚

class UserBehavior(TaskSet):

今回ćÆć€ćƒ¦ćƒ¼ć‚¶ćƒ¼ć®ć‚·ćƒŠćƒŖć‚Ŗć®é–‹å§‹ć€ēµ‚äŗ†ę™‚ć«ćć‚Œćžć‚Œćƒ­ć‚°ć‚¤ćƒ³ć€ćƒ­ć‚°ć‚¢ć‚¦ćƒˆć™ć‚‹ć‚ˆć†ć«ä½œęˆć—ć¾ć—ćŸć€‚
ā€»ćƒ­ć‚°ć‚¤ćƒ³ć€ćƒ­ć‚°ć‚¢ć‚¦ćƒˆć«ćÆCSRFåƾē­–ćø恮åƾåæœć‚’å…„ć‚Œć¦ćŠćć¾ć—ćŸā€¦

    def on_start(self):
        self.login()

    def on_stop(self):
        self.logout()

    def login(self):
        response = self.client.get("/login")

        csrf_param = re.search("<meta name=\"csrf-param\" content=\"([^\"]+)\" />", response.text).group(1)
        csrf_token = re.search("<meta name=\"csrf-token\" content=\"([^\"]+)\" />", response.text).group(1)

        self.client.post("/login", {"username": "admin", "password": "admin-password", csrf_param: csrf_token})
    
    def logout(self):
        response = self.client.get("/")

        csrf_param = re.search("<meta name=\"csrf-param\" content=\"([^\"]+)\" />", response.text).group(1)
        csrf_token = re.search("<meta name=\"csrf-token\" content=\"([^\"]+)\" />", response.text).group(1)

        self.client.post("/logout", {csrf_param: csrf_token})

å®Ÿč”Œć™ć‚‹ćØć‚ć‹ć‚Šć¾ć™ćŒć€ć“ć®ę›øćę–¹ć ćØćƒ¦ćƒ¼ć‚¶ćƒ¼ć‚ćŸć‚Šć€1å›žć—ć‹ćƒ­ć‚°ć‚¤ćƒ³ć€ćƒ­ć‚°ć‚¢ć‚¦ćƒˆć‚’č”Œć„ć¾ć›ć‚“ć€‚

恂ćØćÆ态ć‚æć‚¹ć‚Æć®å®šē¾©ć§ć™ć€‚@taskćƒ‡ć‚³ćƒ¬ćƒ¼ć‚æćƒ¼ć§ć‚æć‚¹ć‚Æć‚’å®šē¾©ć—ć¾ć™ć€‚

task decorator

    @task
    def top(self):
        self.client.get("/")

    @task(2)
    def mypage(self):
        with self.client.get("/my/page", catch_response = True) as response:
            if response.status_code != 200:
                response.failure("not authenticated???")

    @task
    def projects(self):
        self.client.get("/projects")

@taskćƒ‡ć‚³ćƒ¬ćƒ¼ć‚æćƒ¼ć®å¼•ę•°ć«ćÆweightć‚’ęŒ‡å®šć§ćć€ć‚æć‚¹ć‚Æå®Ÿč”Œć®å‰²åˆć‚’čŖæę•“ć§ćć¾ć™ć€‚ēœē•„ć—ćŸę™‚ć®weightćÆ1ćŖć®ć§ć€ä»Šå›žć®ä¾‹ć§ćÆ
mypageć‚æć‚¹ć‚ÆćÆtopćŠć‚ˆć³projects恫ęÆ”ć¹ć‚‹ćØ2å€å®Ÿč”Œć•ć‚Œć‚‹ć“ćØ恫ćŖć‚Šć¾ć™ć€‚

恂ćØ态ē‰¹ć«čŖ¬ę˜ŽćŖ恏ä½æć£ć¦ćć¾ć—ćŸćŒć€self.clientćØę›ø恄恦恄悋恮ćÆLocust恮HTTPć‚Æćƒ©ć‚¤ć‚¢ćƒ³ćƒˆć§ć™ć€‚å®Ÿä½“ćÆHttpSessionćØ恄恆
ć‚Æćƒ©ć‚¹ć§ć€HTTP恮GET悄POST态PUTćŖ恩恫åƾåæœć™ć‚‹ćƒ”ć‚½ćƒƒćƒ‰ć‚’å®Ÿč”Œć§ćć¾ć™ć€‚

HttpSession class

    @task
    def top(self):
        self.client.get("/")

ć¾ćŸć€catch_response恫True悒čØ­å®šć™ć‚‹ćØ态通åøøćÆResponsećØ恄恆ć‚Æćƒ©ć‚¹ćŒčæ”ć‚‹ćØ恓悍恌态ResponseContextManager恌
čæ”ć‚‹ć‚ˆć†ć«ćŖ悊态successļ¼failurećØć„ć£ćŸć€ćƒ†ć‚¹ćƒˆć®ęˆåŠŸć€å¤±ę•—ć‚’åˆ¶å¾”ć™ć‚‹ć“ćØćŒć§ćć‚‹ć‚ˆć†ć«ćŖć‚Šć¾ć™ć€‚

    @task(2)
    def mypage(self):
        with self.client.get("/my/page", catch_response = True) as response:
            if response.status_code != 200:
                response.failure("not authenticated???")

今回ćÆć€ćƒžć‚¤ćƒšćƒ¼ć‚øćøć®ć‚¢ć‚Æć‚»ć‚¹ćÆ态ęœŖćƒ­ć‚°ć‚¤ćƒ³ēŠ¶ę…‹ć ćØćƒ­ć‚°ć‚¤ćƒ³ćƒšćƒ¼ć‚øćøćƒŖćƒ€ć‚¤ćƒ¬ć‚Æ惈恗悈恆ćØć™ć‚‹ć®ć§ć€ćƒ­ć‚°ć‚¤ćƒ³
ć§ćć¦ć„ć‚‹ć“ćØ悒ē¢ŗčŖć™ć‚‹ćŸć‚ć«ć€ć²ćØę‰‹é–“åŠ ćˆć¾ć—ćŸć€‚

Response class

ResponseContextManager class

å®Ÿč”Œć—ć¦ćæ悋

ć“ć“ć¾ć§ć§ććŸćØć“ć‚ć§ć€å®Ÿč”Œć—ć¦ćæć¾ć—ć‚‡ć†ć€‚

ä½œęˆć—ćŸlocustfile.pyćŒć‚ć‚‹ćƒ‡ć‚£ćƒ¬ć‚Æ惈ćƒŖ恧态仄äø‹ć®ć‚³ćƒžćƒ³ćƒ‰ć‚’å®Ÿč”Œć—ć¾ć™ć€‚

$ locust --host=http://192.168.0.3:3000
[2019-01-11 12:34:02,388] be01ed106c53/INFO/locust.main: Starting web monitor at *:8089
[2019-01-11 12:34:02,388] be01ed106c53/INFO/locust.main: Starting Locust 0.9.0

怌--hostć€ć®ę„å‘³ćÆć€ćƒ†ć‚¹ćƒˆåÆ¾č±”ć®ćƒ›ć‚¹ćƒˆć‚’ęŒ‡ć—ć¾ć™ć€‚ä¾‹ć®ć‚ˆć†ć«ć€ć€Œhttp://ć€œć€ć®å½¢å¼ć§ęŒ‡å®šć—ć¾ć™ć€‚

ćƒ†ć‚¹ćƒˆćŒę›øć‹ć‚ŒćŸćƒ•ć‚”ć‚¤ćƒ«ć®åå‰ćÆćƒ‡ćƒ•ć‚©ćƒ«ćƒˆć§ć€Œlocustfile.py怍ćØćŖć‚Šć€åå‰ćŒē•°ćŖć‚‹å “åˆćÆ怌-fć€ć§ęŒ‡å®šć™ć‚‹ć‚ˆć†ć§ć™ć€‚

ć‚³ćƒ³ć‚½ćƒ¼ćƒ«ć«ć‚‚å‡ŗć¦ć„ć¾ć™ćŒć€8089ćƒćƒ¼ćƒˆć§Web UI恌ćƒŖćƒƒć‚¹ćƒ³ć—ć¦ć„ć¾ć™ć®ć§ć€ć€Œhttp://[LocustćŒå‹•ć„ć¦ć„ć‚‹ć‚µćƒ¼ćƒćƒ¼]:8089ć€ć«
ć‚¢ć‚Æć‚»ć‚¹ć—ć¦ćæć¾ć™ć€‚

f:id:Kazuhira:20190111213806p:plain

恙悋ćØć€å›³ć®ć‚ˆć†ć«Web UI恌ē¾ć‚Œć€ć€ŒNumber of users to simulateļ¼ˆćƒ¦ćƒ¼ć‚¶ćƒ¼ę•°ļ¼‰ć€ćØ怌Hatch rateļ¼ˆē§’ć‚ćŸć‚Šä½•ćƒ¦ćƒ¼ć‚¶ćƒ¼å¢—ć‚„ć—ć¦ć„ćć‹ļ¼‰ć€ć‚’
ę±‚ć‚ć‚‰ć‚Œć‚‹ć®ć§ć€ć“ć”ć‚‰ć‚’å…„åŠ›ć—ć¦ć€ŒStart swarmingć€ć‚’ęŠ¼ć™ćØć€ćƒ†ć‚¹ćƒˆćŒå§‹ć¾ć‚Šć¾ć™ć€‚

今回ćÆ怌Number of users to simulate怍悒20态怌Hatch rate怍悒2ć«ć—ć¦ćƒ†ć‚¹ćƒˆé–‹å§‹ć€‚

ćƒ†ć‚¹ćƒˆå®Ÿč”Œäø­ć€ēµęžœćŒćƒŖć‚¢ćƒ«ć‚æć‚¤ćƒ ć§ę›“ę–°ć•ć‚Œć¦ć„ćć¾ć™ć€‚

f:id:Kazuhira:20190111215030p:plain

ćƒ†ć‚¹ćƒˆć‚’ēµ‚äŗ†ć•ć›ć‚‹ć«ćÆć€å³äøŠć«ć‚ć‚‹ć€ŒSTOPć€ć‚’ęŠ¼ć—ć¾ć™ć€‚

f:id:Kazuhira:20190111215056p:plain

STATUS恌怌STOPPEDć€ć«ćŖć‚Šć€ćć®äø‹ć«ć‚ć‚‹ć€ŒNew test怍ćƒŖćƒ³ć‚Æć‚’ęŠ¼ć™ćØć€ć¾ćŸę–°ć—ć„ćƒ†ć‚¹ćƒˆć‚’å§‹ć‚ć‚‹ć“ćØćŒć§ćć¾ć™ć€‚
ā€»ć€ŒNumber of users to simulate怍ćØ怌Hatch rateć€ć®å…„åŠ›ć‚’ę±‚ć‚ć‚‰ć‚Œć¾ć™

ēµęžœć®čŖ­ćæę–¹ćÆ态恂悋ē؋åŗ¦ć¾ć‚“ć¾ć§ć™ćŒć€

  • requests ā€¦ ćƒŖć‚Æć‚Øć‚¹ćƒˆć—ćŸå›žę•°
  • fails ā€¦ å¤±ę•—ć—ćŸćƒŖć‚Æć‚Øć‚¹ćƒˆć®å›žę•°
  • Median (ms) ā€¦ äø­å¤®å€¤
  • Average (ms) ā€¦ 平均値
  • Min (ms) / Max (ms) ā€¦ ęœ€å°ć€ęœ€å¤§
  • Content Size (byte) ā€¦ ć‚³ćƒ³ćƒ†ćƒ³ćƒ„ć®ć‚µć‚¤ć‚ŗ
  • reqs/sec ā€¦ ē§’ć‚ćŸć‚Šć«å®Ÿč”Œć—ćŸćƒŖć‚Æć‚Øć‚¹ćƒˆę•°

各項ē›®ć®åē§°ć‚’ć‚ÆćƒŖ惃ć‚Æ恙悋恓ćØć§ć€ć‚½ćƒ¼ćƒˆć™ć‚‹ć“ćØćŒć§ććŸć‚Šć‚‚ć—ć¾ć™ć€‚

ęœ€å¾Œć®č”Œć«ćÆ怌Totalć€ćŒć‚ć‚Šć€ć“ć”ć‚‰ć®ć€Œreqs/sec怍ćØćƒšćƒ¼ć‚øć®å³äøŠéƒØć«ć‚ć‚‹ć€ŒRPSć€ć®å€¤ćŒäø€č‡“ć—ć¾ć™ć€‚

ć¾ćŸć€ć“ć“ć¾ć§č¦‹ć¦ć„ćŸć®ćÆ怌Statics怍ćŖć®ć§ć™ćŒć€éš£ć®ć€ŒChartć€ć‚’ęŠ¼ć™ćØć‚°ćƒ©ćƒ•ćŒč¦‹ć‚ŒćŸć‚Š

f:id:Kazuhira:20190111215647p:plain

f:id:Kazuhira:20190111215718p:plain

怌Failuresć€ć‚’ęŠ¼ć™ćØ态ćƒŖć‚Æć‚Øć‚¹ćƒˆć«å¤±ę•—ć—ćŸå “åˆć«ć€ćć®ē†ē”±ćŒč¦‹ć‚ŒćŸć‚Šć—ć¾ć™ć€‚

f:id:Kazuhira:20190111215747p:plain

č‡Ŗ分ćÆć€ćƒ­ć‚°ć‚¤ćƒ³ć«ęœ€åˆćšć£ćØå¤±ę•—ć—ć¦ć„ć¦ć€ć“ć“ć«ćšć£ćØć‚Øćƒ©ćƒ¼ćŒå‡ŗć¦ć„ć¾ć—ćŸā€¦ć€‚

恂ćØć€ä¾‹å¤–ć®ęƒ…å ±ć‚’č¦‹ćŸć‚Šć€ēµęžœć®ćƒ€ć‚¦ćƒ³ćƒ­ćƒ¼ćƒ‰ćŒć§ććŸć‚Šć‚‚ć—ć¾ć™ć€‚

f:id:Kazuhira:20190111215859p:plain

ę¬”ć®ćƒ†ć‚¹ćƒˆć‚’å®Ÿč”Œć—ćŸć‚Šć€Locust悒ēµ‚äŗ†ć™ć‚‹ćØ态ēµęžœćÆćŖ恏ćŖć£ć¦ć—ć¾ć†ć®ć§å›žåŽć—ć¦ćŠćć¾ć—ć‚‡ć†ć€‚

ćŖ恊态Locustēµ‚äŗ†ę™‚恫悂态čæ‘ć„ęƒ…å ±ć‚’å¾—ć‚‹ć“ćØćŒć§ćć¾ć™ć€‚

[2019-01-11 13:00:20,978] be01ed106c53/INFO/locust.main: Running teardowns...
 Name                                                          # reqs      # fails     Avg     Min     Max  |  Median   req/s
--------------------------------------------------------------------------------------------------------------------------------------------
 GET /                                                            532     0(0.00%)     112      21     568  |      97    5.40
 GET /login                                                        20     0(0.00%)      47      18     154  |      27    0.00
 POST /login                                                       20     0(0.00%)     365     247     542  |     320    0.00
 POST /logout                                                      20     0(0.00%)     838     623    1012  |     840    0.00
 GET /my/page                                                    1152     0(0.00%)     162      28     602  |     140   12.40
 GET /projects                                                    518     0(0.00%)     116      25     342  |     110    4.50
--------------------------------------------------------------------------------------------------------------------------------------------
 Total                                                           2262     0(0.00%)                                      22.30

Percentage of the requests completed within given times
 Name                                                           # reqs    50%    66%    75%    80%    90%    95%    98%    99%   100%
--------------------------------------------------------------------------------------------------------------------------------------------
 GET /                                                             532     97    130    150    160    200    250    340    430    570
 GET /login                                                         20     28     57     79     85     96    150    150    150    150
 POST /login                                                        20    360    380    480    490    540    540    540    540    540
 POST /logout                                                       20    840    880    880    960   1000   1000   1000   1000   1000
 GET /my/page                                                     1152    140    180    210    240    300    350    410    440    600
 GET /projects                                                     518    110    140    150    170    200    240    270    280    340
--------------------------------------------------------------------------------------------------------------------------------------------
 Total                                                            2262    120    160    180    200    270    330    430    570   1000

ćƒ†ć‚¹ćƒˆć‚·ćƒŠćƒŖć‚Ŗ恮čŖ¬ę˜Žć®ę™‚恫悂ę›øćć¾ć—ćŸćŒć€on_startćŠć‚ˆć³on_stopć§å®Ÿč”Œć™ć‚‹ć‚ˆć†ć«ęŒ‡å®šć—ćŸć€ćƒ­ć‚°ć‚¤ćƒ³ćŠć‚ˆć³ćƒ­ć‚°ć‚¢ć‚¦ćƒˆćÆ
20回ļ¼ˆļ¼ćƒ¦ćƒ¼ć‚¶ćƒ¼ę•°ļ¼‰ć—ć‹å®Ÿč”Œć•ć‚Œć¦ć„ć¾ć›ć‚“ć€‚

ć“ć®ć‚ćŸć‚ŠćÆ态ć‚æć‚¹ć‚Æć‚’å®šē¾©ć—ćŸć‚Æćƒ©ć‚¹ć®ćƒ©ć‚¤ćƒ•ć‚µć‚¤ć‚Æćƒ«ć®é–¢äæ‚恧恗悇恆恭恇怂

Web UIćŖć—ć§å®Ÿč”Œć™ć‚‹

恓恆ę›ø恏ćØ态UIć‹ć‚‰å®Ÿč”Œć—ć¦åœę­¢ć‚‚ć—ćŖćć¦ćÆ恄恑ćŖ恄ā€¦ļ¼ŸćØę€ć†ć‹ć‚‚ć—ć‚Œć¾ć›ć‚“ćŒć€ć€Œ--no-web怍ć‚Ŗćƒ—ć‚·ćƒ§ćƒ³ć‚’ä½æ恆恓ćØ恧态
Web UIćŖć—ć§ć‚‚å®Ÿč”Œć™ć‚‹ć“ćØćŒć§ćć¾ć™ć€‚

Running Locust without the web UI — Locust 0.9.0 documentation

ć“ć®å “åˆć€ć€Œ-c怍恧怌Number of users to simulate怍态怌-r怍恧怌Hatch rateć€ć‚’ęŒ‡å®šć™ć‚‹åæ…č¦ćŒć‚ć‚Šć¾ć™ć€‚

$ locust --host=http://192.168.0.3:3000 --no-web -c 20 -r 2 -t 180s

ć¾ćŸć€å®Ÿč”Œę™‚é–“ć‚’ę±ŗć‚ć‚‹å “åˆćÆ态怌-tć€ć§ęŒ‡å®šć™ć‚‹ć“ćØćŒć§ćć¾ć™ć€‚
ā€»ęŒ‡å®šć—ćŖ恄ćØć€å»¶ć€…ćØå®Ÿč”Œć—ē¶šć‘悋恮恧态Ctrl-cć§ę­¢ć‚ć¾ć—ć‚‡ć†

怌locust -hć€ć§ć€ćƒ˜ćƒ«ćƒ—ć‚’č¦‹ć¦ćæć¾ć—ć‚‡ć†ć€‚

ēµęžœćŒćƒ•ć‚”ć‚¤ćƒ«ćØć—ć¦ę¬²ć—ć„å “åˆćÆ态怌--csv怍恧ēµęžœć‚’å‡ŗåŠ›ć—ć¾ć™ć€‚

Retrieve test statistics in CSV format — Locust 0.9.0 documentation

$ locust --host=http://192.168.0.3:3000 --no-web -c 20 -r 2 -t 180s --csv perf-test

怌--csv怍恧äøŽćˆćŸå€¤ć‚’prefix恫恗恦态requests.csvćØdistribution.csv恮2ć¤ć®ćƒ•ć‚”ć‚¤ćƒ«ćŒć§ćć‚ćŒć‚Šć¾ć™ć€‚

恓悌ćÆ态Web UIć‹ć‚‰ćƒ€ć‚¦ćƒ³ćƒ­ćƒ¼ćƒ‰ć§ććŸć‚‚ć®ćØåŒć˜ć§ć™ć€‚

perf-test_requests.csv

"Method","Name","# requests","# failures","Median response time","Average response time","Min response time","Max response time","Average Content Size","Requests/s"
"GET","/",1011,0,99,110,22,631,4618,5.60
"GET","/login",20,0,26,39,15,100,4805,0.11
"POST","/login",20,0,320,343,238,505,16570,0.11
"POST","/logout",20,0,730,750,572,950,4377,0.11
"GET","/my/page",1967,0,140,158,27,598,10468,10.90
"GET","/projects",1012,0,98,108,25,560,5665,5.61
"None","Total",4050,0,120,137,15,950,7780,22.43

perf-test_distribution.csv

"Name","# requests","50%","66%","75%","80%","90%","95%","98%","99%","100%"
"GET /",1011,99,120,140,150,190,230,310,460,630
"GET /login",20,26,49,59,74,83,100,100,100,100
"POST /login",20,330,370,400,420,490,510,510,510,510
"POST /logout",20,750,790,890,890,930,950,950,950,950
"GET /my/page",1967,140,170,210,230,290,340,390,440,600
"GET /projects",1012,98,120,140,150,190,220,250,290,560
"Total",4050,120,150,170,190,250,310,390,490,950

ć‚‚ć£ćØć‚·ćƒŠćƒŖć‚Ŗć£ć½ć„ć‚‚ć®ć‚’ę›ø恄恦ćæ悋

å…ˆć»ć©ä½œęˆć—ćŸć‚·ćƒŠćƒŖć‚ŖćÆ态ć‚æć‚¹ć‚Æć®é‡ćæä»˜ć‘ć ć‘ę±ŗć‚ćŸć‚‚ć®ć®ć€ē‰¹ć«å®Ÿč”Œé †ćÆęŒ‡å®šć—ć¦ć„ć¾ć›ć‚“ć§ć—ćŸć€‚

ć“ć®ć‚ćŸć‚Šć‚’ć”ć‚ƒć‚“ćØ定ē¾©ć™ć‚‹å “合ćÆ态TaskSequencećØ@seq_taskćƒ‡ć‚³ćƒ¬ćƒ¼ć‚æćƒ¼ć‚’ä½æć„ć¾ć™ć€‚

TaskSequence class

seq_task decorator

seq-senario-locustfile.py

from locust import HttpLocust, TaskSequence, seq_task

import re

class UserBehavior(TaskSequence):
    @seq_task(1)
    def login(self):
        response = self.client.get("/login")

        csrf_param = re.search("<meta name=\"csrf-param\" content=\"([^\"]+)\" />", response.text).group(1)
        csrf_token = re.search("<meta name=\"csrf-token\" content=\"([^\"]+)\" />", response.text).group(1)

        self.client.post("/login", {"username": "admin", "password": "admin-password", csrf_param: csrf_token})
    
    @seq_task(99)
    def logout(self):
        response = self.client.get("/")

        csrf_param = re.search("<meta name=\"csrf-param\" content=\"([^\"]+)\" />", response.text).group(1)
        csrf_token = re.search("<meta name=\"csrf-token\" content=\"([^\"]+)\" />", response.text).group(1)

        self.client.post("/logout", {csrf_param: csrf_token})

    @seq_task(2)
    def top(self):
        self.client.get("/")

    @seq_task(3)
    def mypage(self):
        with self.client.get("/my/page", catch_response = True) as response:
            if response.status_code != 200:
                response.failure("not authenticated???")

    @seq_task(4)
    def projects(self):
        self.client.get("/projects")

class RedmineUser(HttpLocust):
    task_set = UserBehavior
    min_wait = 500
    max_wait = 1000

ć‚·ćƒŠćƒŖć‚Ŗć‚’å®šē¾©ć™ć‚‹ć‚Æćƒ©ć‚¹ć®ē¶™ę‰æå…ƒćŒTaskSequencećØćŖ悊态

class UserBehavior(TaskSequence):

ć‚æć‚¹ć‚Æć®å®Ÿč”Œé †ćÆ@seq_taskć§ęŒ‡å®šć—ć¾ć™ć€‚

    @seq_task(2)
    def top(self):
        self.client.get("/")

    @seq_task(3)
    def mypage(self):
        with self.client.get("/my/page", catch_response = True) as response:
            if response.status_code != 200:
                response.failure("not authenticated???")

    @seq_task(4)
    def projects(self):
        self.client.get("/projects")

今回ćÆć€ćƒ­ć‚°ć‚¤ćƒ³ć€ćƒ­ć‚°ć‚¢ć‚¦ćƒˆć‚‚ć‚æć‚¹ć‚Æ恫ēµ„ćæč¾¼ćæć¾ć—ćŸć€‚

ē¢ŗčŖć€‚ēŸ­ć‚ć«ć€30ē§’é–“å®Ÿč”Œć€‚

$ locust --host=http://192.168.0.3:3000 -f seq-senario-locustfile.py --no-web -c 20 -r 2 -t 30s

今回ćÆć€ćƒ­ć‚°ć‚¤ćƒ³ć€ćƒ­ć‚°ć‚¢ć‚¦ćƒˆćÆćƒ¦ćƒ¼ć‚¶ćƒ¼ć®ę•°ć ć‘å®Ÿč”Œć€ć§ćÆć‚ć‚Šć¾ć›ć‚“ć­ć€‚

[2019-01-11 13:17:27,596] be01ed106c53/INFO/locust.main: Running teardowns...
 Name                                                          # reqs      # fails     Avg     Min     Max  |  Median   req/s
--------------------------------------------------------------------------------------------------------------------------------------------
 GET /                                                            191     0(0.00%)     143      24     420  |     130    7.80
 GET /login                                                       107     0(0.00%)      57      15     207  |      40    3.80
 POST /login                                                      105     0(0.00%)     467     226     806  |     460    3.80
 POST /logout                                                      90     0(0.00%)     274      85     561  |     260    3.70
 GET /my/page                                                      96     0(0.00%)     220      34     509  |     190    3.80
 GET /projects                                                     94     0(0.00%)     152      28     388  |     150    3.80
--------------------------------------------------------------------------------------------------------------------------------------------
 Total                                                            683     0(0.00%)                                      26.70

Percentage of the requests completed within given times
 Name                                                           # reqs    50%    66%    75%    80%    90%    95%    98%    99%   100%
--------------------------------------------------------------------------------------------------------------------------------------------
 GET /                                                             191    130    160    190    200    230    270    310    380    420
 GET /login                                                        107     40     56     80     86    120    140    180    190    210
 POST /login                                                       105    460    500    560    580    630    680    750    770    810
 POST /logout                                                       90    270    330    360    390    440    510    560    560    560
 GET /my/page                                                       96    190    250    300    320    390    440    480    510    510
 GET /projects                                                      94    150    170    170    190    240    280    360    390    390
--------------------------------------------------------------------------------------------------------------------------------------------
 Total                                                             683    160    230    290    350    450    530    620    670    810

åˆ†ę•£å®Ÿč”Œć—ć¦ćæ悈恆

ęœ€å¾Œć«ć€åˆ†ę•£å®Ÿč”Œć—ć¦ćæć¾ć™ć€‚

Running Locust distributed — Locust 0.9.0 documentation

Locustć‚’å®Ÿč”Œć™ć‚‹ćƒŽćƒ¼ćƒ‰ćÆ态ćØć‚Šć‚ćˆćšå‹•ć‹ć—ć¦ćæ悋ćØ恄恆恓ćØ恧态Docker恧3恤ē”Øꄏ怂172.17.0.2怜4ćØć—ć¾ć™ć€‚

ć‚·ćƒŠćƒŖć‚ŖćÆć€å…ˆć»ć©ć®ć‚æć‚¹ć‚Æć®å®Ÿč”Œé †ć‚’ęŒ‡å®šć—ćŸć‚‚ć®ć‚’ä½æć£ć¦å®Ÿč”Œć€‚

åˆ†ę•£å®Ÿč”Œę™‚ć«ćÆ态masterļ¼ˆć²ćØ恤ļ¼‰ćØslavećØćŖć‚‹ćƒŽćƒ¼ćƒ‰ļ¼ˆ2恤ļ¼‰ć‚’éøć³ć¾ć™ć€‚Web UIćÆ态master恧恮ćæå‹•ćć¾ć™ć€‚

master恧ćÆ态怌--masterć€ć‚’ęŒ‡å®šć—ć¦å®Ÿč”Œć€‚masterćÆ态172.17.0.2恮悂恮悒ä½æć„ć¾ć™ć€‚

$ locust --host=http://192.168.0.3:3000 -f seq-senario-locustfile.py --master

slave恧ćÆ态仄äø‹ć®ć‚³ćƒžćƒ³ćƒ‰ć‚’å®Ÿč”Œć€‚2ć¤ć®ćƒŽćƒ¼ćƒ‰ćØć‚‚ć€å®Ÿč”Œć—ć¾ć™ć€‚

$ locust --host=http://192.168.0.3:3000 -f seq-senario-locustfile.py --slave --master-host 172.17.0.2

ć¤ć¾ć‚Šć€slaveć«ć‚‚ć‚·ćƒŠćƒŖć‚ŖćÆé…ć£ć¦ćŠćåæ…č¦ćŒć‚ć‚Šć¾ć™ć€ćØ怂

恓恮ēŠ¶ę…‹ć§master恮Web UI悒見悋ćØć€å³äøŠć«ć€ŒSLAVESć€ć®č”Øē¤ŗ恌čæ½åŠ ć•ć‚Œć¦ć„ć¾ć™ć€‚

f:id:Kazuhira:20190111222910p:plain

ć§ć€å®Ÿč”Œć€‚ę“ä½œę–¹ę³•č‡Ŗ体ćÆ态通åøøćØåŒć˜ć§ć™ć€‚

f:id:Kazuhira:20190111222939p:plain

今回ćÆ态怌Number of users to simulate怍悒20ć«ć€ć€ŒHatch rateć€ć«2ć‚’ęŒ‡å®šć—ć¾ć—ćŸć€‚

ćć—ć¦ć€slaveć²ćØć¤ć‚ćŸć‚Šć€ä»„äø‹ć®ć‚ˆć†ćŖ惭悰恌å‡ŗåŠ›ć•ć‚Œć¾ć™ć€‚

[2019-01-11 13:28:23,783] dce69b8cc910/INFO/locust.runners: Hatching and swarming 10 clients at the rate 1 clients/s...
[2019-01-11 13:28:33,793] dce69b8cc910/INFO/locust.runners: All locusts hatched: RedmineUser: 10

ćƒ¦ćƒ¼ć‚¶ćƒ¼ć®åŠåˆ†ćŒå‰²ć‚Šå½“ćŸć£ć¦ć„ć¾ć™ć­ć€‚

ćØ恄恆恓ćØćÆć€ćƒ†ć‚¹ćƒˆå®Ÿč”Œę™‚ć«ęŒ‡å®šć—ćŸćƒ¦ćƒ¼ć‚¶ćƒ¼ćÆslaveć«åˆ†é…ć•ć‚Œć¦å®Ÿč”Œć•ć‚Œć‚‹ļ¼ˆmasterćÆč² č·ć‚’ć‹ć‘ć‚‹ä½œę„­ćÆ恗ćŖ恄ļ¼‰ćØ恄恆
恓ćØ恧恙恭怂

Web UIć«ć‚‚ć€ŒSlaves怍ć‚æćƒ–ćŒå¢—ćˆć¦ć„ć¦ć€čŖč­˜ć—恦恄悋slave恮ē¢ŗčŖćØć€ćƒ†ć‚¹ćƒˆć®å®Ÿč”Œäø­ć§ć‚ć‚Œć°ä½•ćƒ¦ćƒ¼ć‚¶ćƒ¼ćŒćć‚Œćžć‚Œć®
slaveć«å‰²ć‚Šå½“ć¦ć‚‰ć‚Œć¦ć„ć‚‹ć‹ć€ē¢ŗčŖć™ć‚‹ć“ćØćŒć§ćć¾ć™ć€‚

f:id:Kazuhira:20190111223331p:plain

恔ćŖćæ恫态slavećÆmasterć‚’åœę­¢ć™ć‚‹ćØč‡Ŗ動ēš„ć«åœę­¢ć—ć¾ć™ć€‚

ęœ€å¾Œć«ć€ć“ć®åˆ†ę•£å®Ÿč”Œć‚’Web UIćŖć—ć§č”Œć£ć¦ćæć¾ć—ć‚‡ć†ć€‚

ć“ć®å “åˆć€å…ˆć«slave悒åæ…要ćŖę•°ć ć‘čµ·å‹•ć—ć¦ćŠćć¾ć™ć€‚

$ locust --host=http://192.168.0.3:3000 -f seq-senario-locustfile.py --slave --master-host 172.17.0.2

ćć®å¾Œć€masterć‚’čµ·å‹•ć—ć¾ć™ć€‚ć€Œ--masterć€ć‚’ä»˜ć‘ć‚‹ä»„å¤–ćÆć€ć‚¹ć‚æćƒ³ćƒ‰ć‚¢ćƒ­ćƒ³ć§Web UIćŖć—ć§å®Ÿč”Œć—ć¦ć„ćŸå “åˆćØåŒć˜ć§ć™ć€‚

$ locust --host=http://192.168.0.3:3000 -f seq-senario-locustfile.py --master --no-web -c 20 -r 2 -t 30s --csv perf-test-dist

恓恮順ē•Ŗ悒逆恫恙悋ćØ态masterćÆslave恌恄ćŖ恄間ćÆå¾…ę©ŸēŠ¶ę…‹ć«ćŖć‚Šć¾ć™ćŒć€ć²ćØ恤恧悂slave悒čŖč­˜ć™ć‚‹ćØć™ćć«ćƒ†ć‚¹ćƒˆć‚’
å§‹ć‚ć¦ć—ć¾ć„ć¾ć™ā€¦ć€‚

ć¾ćØ悁

Python恧ę›øć‹ć‚ŒćŸč² č·ćƒ†ć‚¹ćƒˆćƒ„ćƒ¼ćƒ«ć€Locust悒ä½æć£ć¦ćæć¾ć—ćŸć€‚

Python恧ē°”å˜ć«ć‚·ćƒŠćƒŖć‚Ŗ悒ę›ø恑态悏恋悊悄恙恄UIćŒć‚ć‚Šć€åˆ†ę•£å®Ÿč”Œć‚‚åÆčƒ½ćØ恄悍恄悍ä¾æåˆ©ć§ć™ć€‚

ćć®åé¢ć€Apache JMeterć»ć©ć®ę©Ÿčƒ½ćÆćŖć•ćć†ć ć£ćŸć‚Šļ¼ˆćƒ¬ć‚³ćƒ¼ćƒ€ćƒ¼ćØ恋悂ā€¦ļ¼‰ć—ć¾ć™ć€‚

ćƒ‘ćƒ©ćƒ”ćƒ¼ć‚æćƒ¼ćØć‹ćƒ­ć‚°ć‚¤ćƒ³ć§ä½æć†ćƒ¦ćƒ¼ć‚¶ćƒ¼ć‚’ć€CSVćØć‹ć«ć§ććŖ恄恮ļ¼ŸćØ恄恆恮悂čŖæć¹ć¦ćæćŸć®ć§ć™ćŒć€č‡Ŗåˆ†ć§ę›ø恓恆ļ¼
ćØ恄恆ꄟ恘ćæ恟恄恧恙恭怂

How to Run Locust with Different Users | BlazeMeter

ć¾ć‚ć€ć‚³ćƒ³ć‚»ćƒ—ćƒˆēš„恫ćÆ恝恆恧恗悇恆恭恇态ćØ怂

ć‚«ć‚¹ć‚æ惠ć‚Æćƒ©ć‚¤ć‚¢ćƒ³ćƒˆć®ä½œć‚Šę–¹ć‚‚ć€‚

Testing other systems using custom clients — Locust 0.9.0 documentation

ꦂ恭态åŸŗęœ¬ēš„ćŖēÆ„å›²ć§ę°—ć«ćŖ悋ćØ恓悍ćÆć–ć£ćØč§¦ć‚ŒćŸć®ć§ćÆćŖ恄恧恗悇恆恋怂ä½æ恈悋ćØ恓悍恧ćÆ态ꓻē”Øć—ć¦ć„ććŸć„ć§ć™ć­ć€‚