From 173fc4cec207868d319cb20705b7a34c7d3e64a4 Mon Sep 17 00:00:00 2001 From: Yghor Kerscher Date: Thu, 26 Oct 2017 21:53:55 +0200 Subject: [PATCH 1/5] Load test with Locust.io --- etc/load-testing/locustfile.py | 91 +++++++++++++++++++++++++++++++ etc/load-testing/requirements.txt | 17 ++++++ 2 files changed, 108 insertions(+) create mode 100644 etc/load-testing/locustfile.py create mode 100644 etc/load-testing/requirements.txt diff --git a/etc/load-testing/locustfile.py b/etc/load-testing/locustfile.py new file mode 100644 index 0000000..2d781b9 --- /dev/null +++ b/etc/load-testing/locustfile.py @@ -0,0 +1,91 @@ +from locust import HttpLocust, task, TaskSet +from random import randrange + +class HoogleQueries(TaskSet): + @task + def hoogle_queries(self): + # TODO: Get actual common queries from server logs + _hoogle_queries = [ + "Ord", + "Eq", + "Num", + "pack", + "Text -> String", + "fmap", + "a -> a", + "traverse", + "bracket", + "^.", + ">>>", + "<$>", + "bimap", + "inject" + ] + for q in _hoogle_queries: + self.client.get("/lts/hoogle?q=" + q, name="/lts/hoogle?q=[:query]") + + @task + def stop(self): + self.interrupt() + +class PackageBrowser(TaskSet): + @task(50) + def list_packages(self): + self.client.get("/lts") + + @task(10) + def browse_package(self): + # TODO: Get packages to test from up-to-date listing on Stackage, move out of here + _packages = [ + "accelerate", + "adjunctions", + "aeson", + "binary", + "both", + "extensible-effects", + "hamlet", + "hdocs", + "microlens", + "range", + "sort", + "text", + "universe" + ] + self.client.get("/lts/package/" + _packages[randrange(len(_packages))], name="/lts/package/:package") + + @task(2) + def stop(self): + self.interrupt() + +class TopLevelPages(TaskSet): + @task(30) + def docs(self): + self.client.get("/docs") + + @task(20) + def install(self): + self.client.get("/install") + + @task(5) + def nightly(self): + self.client.get("/nightly") + + @task(2) + def snapshots(self): + self.client.get("/snapshots") + + @task(2) + def stop(self): + self.interrupt() + +class UserBehaviour(TaskSet): + tasks = { + HoogleQueries : 2, + PackageBrowser : 2, + TopLevelPages : 1 + } + +class WebsiteUser(HttpLocust): + task_set = UserBehaviour + min_wait = 1000 + max_wait = 9000 diff --git a/etc/load-testing/requirements.txt b/etc/load-testing/requirements.txt new file mode 100644 index 0000000..5b1fa01 --- /dev/null +++ b/etc/load-testing/requirements.txt @@ -0,0 +1,17 @@ +certifi==2017.4.17 +chardet==3.0.4 +click==6.7 +Flask==0.12.2 +gevent==1.2.2 +greenlet==0.4.12 +idna==2.5 +itsdangerous==0.24 +Jinja2==2.9.6 +-e git://github.com/locustio/locust.git@072d7752552ff32898253fcd5734c3b64995c17e#egg=locustio +MarkupSafe==1.0 +msgpack-python==0.4.8 +pyzmq==15.2.0 +requests==2.18.1 +six==1.10.0 +urllib3==1.21.1 +Werkzeug==0.12.2 From 8514b3d71073db540813b64139acfe4beff4c828 Mon Sep 17 00:00:00 2001 From: Yghor Kerscher Date: Tue, 31 Oct 2017 16:54:52 +0100 Subject: [PATCH 2/5] Load test with multiple different snapshots. --- etc/load-testing/locustfile.py | 62 +++++++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/etc/load-testing/locustfile.py b/etc/load-testing/locustfile.py index 2d781b9..86978ae 100644 --- a/etc/load-testing/locustfile.py +++ b/etc/load-testing/locustfile.py @@ -1,6 +1,25 @@ from locust import HttpLocust, task, TaskSet from random import randrange +def random_element(xs): + return xs[randrange(len(xs))] + +def select_snapshot(): + _snapshots = [ + "lts", + "nightly", + "lts-9.10", + "lts-9.7", + "lts-9.6", + "lts-9.5", + "lts-8.8", + "nightly-2017-07-05", + "nightly-2017-05-30", + "nightly-2017-03-25", + "lts-7.20", + ] + return random_element(_snapshots) + class HoogleQueries(TaskSet): @task def hoogle_queries(self): @@ -21,18 +40,25 @@ class HoogleQueries(TaskSet): "bimap", "inject" ] + _snapshot = select_snapshot() for q in _hoogle_queries: - self.client.get("/lts/hoogle?q=" + q, name="/lts/hoogle?q=[:query]") + self.client.get("/" + _snapshot + "/hoogle?q=" + q, name="/:snapshot/hoogle?q=[:query]") @task def stop(self): self.interrupt() -class PackageBrowser(TaskSet): - @task(50) - def list_packages(self): - self.client.get("/lts") +class Documentation(TaskSet): + @task + def docs(self): + _snapshot = select_snapshot() + self.client.get("/" + _snapshot + "/docs", name="/:snapshot/docs") + @task + def stop(self): + self.interrupt() + +class PackageBrowser(TaskSet): @task(10) def browse_package(self): # TODO: Get packages to test from up-to-date listing on Stackage, move out of here @@ -51,21 +77,31 @@ class PackageBrowser(TaskSet): "text", "universe" ] - self.client.get("/lts/package/" + _packages[randrange(len(_packages))], name="/lts/package/:package") + _snapshot = select_snapshot() + self.client.get("/" + _snapshot + "/package/" + random_element(_packages), name="/:snapshot/package/:package") @task(2) def stop(self): self.interrupt() +class Snapshots(TaskSet): + @task + def updateSnapshots(self): + self.client.get("/download/snapshots.json") + + @task + def stop(self): + self.interrupt() + class TopLevelPages(TaskSet): - @task(30) - def docs(self): - self.client.get("/docs") - @task(20) def install(self): self.client.get("/install") + @task(10) + def lts(self): + self.client.get("/lts") + @task(5) def nightly(self): self.client.get("/nightly") @@ -80,9 +116,11 @@ class TopLevelPages(TaskSet): class UserBehaviour(TaskSet): tasks = { - HoogleQueries : 2, + HoogleQueries : 5, PackageBrowser : 2, - TopLevelPages : 1 + Documentation : 2, + Snapshots : 1, + TopLevelPages : 1, } class WebsiteUser(HttpLocust): From 1e0019cee377ce2a8cd4deac2c918bca8eacdd6d Mon Sep 17 00:00:00 2001 From: Yghor Kerscher Date: Tue, 31 Oct 2017 17:45:39 +0100 Subject: [PATCH 3/5] Clean up load tests. --- etc/load-testing/locustfile.py | 110 +++++++++++++++++++-------------- 1 file changed, 64 insertions(+), 46 deletions(-) diff --git a/etc/load-testing/locustfile.py b/etc/load-testing/locustfile.py index 86978ae..d8592db 100644 --- a/etc/load-testing/locustfile.py +++ b/etc/load-testing/locustfile.py @@ -20,29 +20,56 @@ def select_snapshot(): ] return random_element(_snapshots) +def select_package(): + _packages = [ + "accelerate", + "adjunctions", + "aeson", + "binary", + "both", + "extensible-effects", + "hamlet", + "hdocs", + "microlens", + "range", + "sort", + "text", + "universe" + ] + return random_element(_packages) + +def select_hoogle_query(): + _hoogle_queries = [ + "Ord", + "Eq", + "Num", + "pack", + "Text -> String", + "fmap", + "a -> a", + "traverse", + "bracket", + "^.", + ">>>", + "<$>", + "bimap", + "inject" + ] + return random_element(_hoogle_queries) + class HoogleQueries(TaskSet): @task def hoogle_queries(self): - # TODO: Get actual common queries from server logs - _hoogle_queries = [ - "Ord", - "Eq", - "Num", - "pack", - "Text -> String", - "fmap", - "a -> a", - "traverse", - "bracket", - "^.", - ">>>", - "<$>", - "bimap", - "inject" - ] _snapshot = select_snapshot() - for q in _hoogle_queries: - self.client.get("/" + _snapshot + "/hoogle?q=" + q, name="/:snapshot/hoogle?q=[:query]") + _query = select_hoogle_query() + self.client.get("/" \ + + _snapshot \ + + "/hoogle?q=" + _query \ + , name="/:snapshot/hoogle?q=[:query]") + + @task + def stop(self): + self.interrupt() @task def stop(self): @@ -52,35 +79,26 @@ class Documentation(TaskSet): @task def docs(self): _snapshot = select_snapshot() - self.client.get("/" + _snapshot + "/docs", name="/:snapshot/docs") + self.client.get("/" \ + + _snapshot \ + + "/docs" \ + , name="/:snapshot/docs") @task def stop(self): self.interrupt() - -class PackageBrowser(TaskSet): - @task(10) - def browse_package(self): - # TODO: Get packages to test from up-to-date listing on Stackage, move out of here - _packages = [ - "accelerate", - "adjunctions", - "aeson", - "binary", - "both", - "extensible-effects", - "hamlet", - "hdocs", - "microlens", - "range", - "sort", - "text", - "universe" - ] - _snapshot = select_snapshot() - self.client.get("/" + _snapshot + "/package/" + random_element(_packages), name="/:snapshot/package/:package") - @task(2) +class PackageBrowser(TaskSet): + @task + def browse_package(self): + _snapshot = select_snapshot() + _package = select_package() + self.client.get("/" \ + + _snapshot \ + + "/package/" + _package \ + , name="/:snapshot/package/:package") + + @task def stop(self): self.interrupt() @@ -88,11 +106,11 @@ class Snapshots(TaskSet): @task def updateSnapshots(self): self.client.get("/download/snapshots.json") - + @task def stop(self): self.interrupt() - + class TopLevelPages(TaskSet): @task(20) def install(self): @@ -101,7 +119,7 @@ class TopLevelPages(TaskSet): @task(10) def lts(self): self.client.get("/lts") - + @task(5) def nightly(self): self.client.get("/nightly") From 7ab2d3f841e88d030380ee916dc442fccb24bca2 Mon Sep 17 00:00:00 2001 From: Yghor Kerscher Date: Tue, 31 Oct 2017 17:46:02 +0100 Subject: [PATCH 4/5] Add load tests for Haddock pages. --- etc/load-testing/locustfile.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/etc/load-testing/locustfile.py b/etc/load-testing/locustfile.py index d8592db..0089702 100644 --- a/etc/load-testing/locustfile.py +++ b/etc/load-testing/locustfile.py @@ -71,6 +71,17 @@ class HoogleQueries(TaskSet): def stop(self): self.interrupt() +class Haddock(TaskSet): + @task + def haddock(self): + _snapshot = select_snapshot() + _package = select_package() + self.client.get("/haddock/" \ + + _snapshot + "/" \ + + _package + "/" \ + + "doc-index-All.html" \ + , name="/haddock/:snapshot/:package/doc-index-All.html") + @task def stop(self): self.interrupt() @@ -134,6 +145,7 @@ class TopLevelPages(TaskSet): class UserBehaviour(TaskSet): tasks = { + Haddock : 10, HoogleQueries : 5, PackageBrowser : 2, Documentation : 2, From a11195a904234d007b1c186752a4bc69b5811b93 Mon Sep 17 00:00:00 2001 From: Yghor Kerscher Date: Thu, 2 Nov 2017 15:45:31 +0100 Subject: [PATCH 5/5] Document how to configure and run load tests. --- etc/load-testing/README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 etc/load-testing/README.md diff --git a/etc/load-testing/README.md b/etc/load-testing/README.md new file mode 100644 index 0000000..8b96c4c --- /dev/null +++ b/etc/load-testing/README.md @@ -0,0 +1,30 @@ +# `stackage-server` load tests + +This directory can be used to test an on-premises instance of `stackage-server` for quality of service metrics. Follow the instructions below to run it. + +We assume you will be using `pyenv` to handle different Python environments in a safe manner. If you prefer installing packages globally (not recommended), jump straight to the line commented with `# install dependencies`. Before doing anything, set an environment variable `STACKAGE_SERVER_REPO` on your shell to this repo’s root folder. Then copy everything and run: + +```shell +cd ~ +curl -L https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer | bash + +# add the following lines to your shell initialisation for permanent usage +export PYENV_VIRTUALENV_DISABLE_PROMPT=1 +eval "$(pyenv init -)" +eval "$(pyenv virtualenv-init -)" + +# create an environment for tests +pyenv install 3.6.3 +pyenv virtualenv 3.6.3 stackage-server-py-3.6.3 +cd "${STACKAGE_SERVER_REPO}/etc/load-testing" +pyenv local stackage-server-py-3.6.3 +pyenv activate stackage-server-py-3.6.3 # should happen automatically on previous line, but just to be sure + +# install dependencies +pip install -r requirements.txt + +# execute load tests +locust --host="http://yourlocalinstance.domain" +``` + +Then navigate to [127.0.0.1:8089](http://127.0.0.1:8089), set the number of clients to simulate, spawn rate, start it and wait a few minutes for the results to stabilise. You can then download or analyse directly on Locust’s UI.