diff --git a/tests/load/Dockerfile b/tests/load/Dockerfile index a8287adbd..87ab12475 100644 --- a/tests/load/Dockerfile +++ b/tests/load/Dockerfile @@ -29,10 +29,10 @@ RUN poetry install --without dev --no-interaction --no-ansi RUN useradd --create-home locust WORKDIR /home/locust -COPY ./locustfile.py locustfile.py +COPY ./locustfiles ./locustfiles # Expose ports for the web UI and the locust master EXPOSE 8089 5557 USER locust -ENTRYPOINT locust -f locustfile.py --websocket_url ${SERVER_URL} --endpoint_url ${ENDPOINT_URL} +ENTRYPOINT locust -f "locustfiles/locustfile.py,locustfiles/load.py" --websocket_url ${SERVER_URL} --endpoint_url ${ENDPOINT_URL} diff --git a/tests/load/README.md b/tests/load/README.md index a3ac2e996..874666927 100644 --- a/tests/load/README.md +++ b/tests/load/README.md @@ -62,6 +62,7 @@ make load * In a browser navigate to `http://localhost:8089/` * Set up the load test parameters: + * ShapeClass: Default * Number of users: 1 * Spawn rate: 1 * Host: 'https://updates-autopush.stage.mozaws.net' @@ -148,10 +149,8 @@ The load tests can be executed from the [contextual-services-test-eng cloud shel ``` * Set up the load test parameters: - * Number of users: 83300 - * Spawn rate: 694 + * ShapeClass: 'AutopushLoadTestShape' * Host: 'https://updates-autopush.stage.mozaws.net' - * Duration (Optional): 10m * Websocket URL: 'wss://autopush.stage.mozaws.net' * Endpoint URL: 'https://updates-autopush.stage.mozaws.net' * Select "Start Swarming" diff --git a/tests/load/docker-compose.yml b/tests/load/docker-compose.yml index a114abe27..cb51202b6 100644 --- a/tests/load/docker-compose.yml +++ b/tests/load/docker-compose.yml @@ -7,10 +7,8 @@ services: ports: - "8089:8089" environment: + LOCUST_USERCLASS_PICKER: true LOCUST_HOST: ${AUTOPUSH_HOST} - LOCUST_USERS: "1" - LOCUST_SPAWN_RATE: "1" - LOCUST_RUN_TIME: "10m" LOCUST_LOGLEVEL: "INFO" SERVER_URL: ${SERVER_URL} ENDPOINT_URL: ${ENDPOINT_URL} diff --git a/tests/load/kubernetes-config/locust-master-controller.yml b/tests/load/kubernetes-config/locust-master-controller.yml index d6d8ad9d7..bfe24da3f 100644 --- a/tests/load/kubernetes-config/locust-master-controller.yml +++ b/tests/load/kubernetes-config/locust-master-controller.yml @@ -24,12 +24,6 @@ spec: value: - name: LOCUST_CSV value: - - name: LOCUST_USERS - value: - - name: LOCUST_SPAWN_RATE - value: - - name: LOCUST_RUN_TIME - value: - name: LOCUST_LOGLEVEL value: - name: LOCUST_LOGFILE @@ -54,4 +48,4 @@ spec: memory: 2Gi requests: cpu: 1 - memory: 1Gi \ No newline at end of file + memory: 1Gi diff --git a/tests/load/locustfiles/load.py b/tests/load/locustfiles/load.py new file mode 100644 index 000000000..a945f55e9 --- /dev/null +++ b/tests/load/locustfiles/load.py @@ -0,0 +1,81 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +"""Load test shape module.""" +import math +from typing import Callable, Type + +import numpy +from locust import LoadTestShape, User +from locustfile import AutopushUser + +TickTuple = tuple[int, float, list[Type[User]] | None] + + +class QuadraticTrend: + """A class that defines a quadratic LoadTestShape trend.""" + + a: float + b: float + c: float + + def __init__(self, max_run_time: int, max_users: int): + self.a, self.b, self.c = numpy.polyfit( + [0, (max_run_time / 2), max_run_time], [0, max_users, 0], 2 + ) + + def calculate_users(self, run_time: int) -> int: + """Determined the number of active users given a run time. + + Returns: + int: The number of users + """ + return int(round((self.a * math.pow(run_time, 2)) + (self.b * run_time) + self.c)) + + +class AutopushLoadTestShape(LoadTestShape): + """A load test shape class for Autopush (Duration: 10 minutes, Users: 55500). + Note: The Shape class assumes that the workers can support the generated spawn rates. + """ + + MAX_RUN_TIME: int = 600 # 10 minutes + MAX_USERS: int = 83300 + trend: QuadraticTrend + user_classes: list[Type[User]] = [AutopushUser] + + def __init__(self): + super(LoadTestShape, self).__init__() + + self.trend = QuadraticTrend(self.MAX_RUN_TIME, self.MAX_USERS) + + def _get_calculate_users_function(self) -> Callable[[int], int]: + a, b, c = numpy.polyfit( + [0, (self.MAX_RUN_TIME / 2), self.MAX_RUN_TIME], [0, self.MAX_USERS, 0], 2 + ) + + def calculation(x: int) -> int: + return int(round((a * math.pow(x, 2)) + (b * x) + c)) + + return calculation + + def tick(self) -> TickTuple | None: + """Override defining the desired distribution for Autopush load testing. + + Returns: + TickTuple: Distribution parameters + user_count: Total user count + spawn_rate: Number of users to start/stop per second when changing + number of users + user_classes: None or a List of user classes to be spawned + None: Instruction to stop the load test + """ + run_time: int = self.get_run_time() + if run_time > self.MAX_RUN_TIME: + return None + + users: int = self.trend.calculate_users(run_time) + # The spawn_rate minimum value is 1 + spawn_rate: int = max(abs(users - self.get_current_user_count()), 1) + + return users, spawn_rate, self.user_classes diff --git a/tests/load/locustfile.py b/tests/load/locustfiles/locustfile.py similarity index 100% rename from tests/load/locustfile.py rename to tests/load/locustfiles/locustfile.py diff --git a/tests/load/poetry.lock b/tests/load/poetry.lock index 8a8f64f7e..b02f7d867 100644 --- a/tests/load/poetry.lock +++ b/tests/load/poetry.lock @@ -962,6 +962,40 @@ files = [ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] +[[package]] +name = "numpy" +version = "1.25.2" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "numpy-1.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db3ccc4e37a6873045580d413fe79b68e47a681af8db2e046f1dacfa11f86eb3"}, + {file = "numpy-1.25.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:90319e4f002795ccfc9050110bbbaa16c944b1c37c0baeea43c5fb881693ae1f"}, + {file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe4a913e29b418d096e696ddd422d8a5d13ffba4ea91f9f60440a3b759b0187"}, + {file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f08f2e037bba04e707eebf4bc934f1972a315c883a9e0ebfa8a7756eabf9e357"}, + {file = "numpy-1.25.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bec1e7213c7cb00d67093247f8c4db156fd03075f49876957dca4711306d39c9"}, + {file = "numpy-1.25.2-cp310-cp310-win32.whl", hash = "sha256:7dc869c0c75988e1c693d0e2d5b26034644399dd929bc049db55395b1379e044"}, + {file = "numpy-1.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:834b386f2b8210dca38c71a6e0f4fd6922f7d3fcff935dbe3a570945acb1b545"}, + {file = "numpy-1.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5462d19336db4560041517dbb7759c21d181a67cb01b36ca109b2ae37d32418"}, + {file = "numpy-1.25.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5652ea24d33585ea39eb6a6a15dac87a1206a692719ff45d53c5282e66d4a8f"}, + {file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d60fbae8e0019865fc4784745814cff1c421df5afee233db6d88ab4f14655a2"}, + {file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60e7f0f7f6d0eee8364b9a6304c2845b9c491ac706048c7e8cf47b83123b8dbf"}, + {file = "numpy-1.25.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bb33d5a1cf360304754913a350edda36d5b8c5331a8237268c48f91253c3a364"}, + {file = "numpy-1.25.2-cp311-cp311-win32.whl", hash = "sha256:5883c06bb92f2e6c8181df7b39971a5fb436288db58b5a1c3967702d4278691d"}, + {file = "numpy-1.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:5c97325a0ba6f9d041feb9390924614b60b99209a71a69c876f71052521d42a4"}, + {file = "numpy-1.25.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b79e513d7aac42ae918db3ad1341a015488530d0bb2a6abcbdd10a3a829ccfd3"}, + {file = "numpy-1.25.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eb942bfb6f84df5ce05dbf4b46673ffed0d3da59f13635ea9b926af3deb76926"}, + {file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e0746410e73384e70d286f93abf2520035250aad8c5714240b0492a7302fdca"}, + {file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7806500e4f5bdd04095e849265e55de20d8cc4b661b038957354327f6d9b295"}, + {file = "numpy-1.25.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8b77775f4b7df768967a7c8b3567e309f617dd5e99aeb886fa14dc1a0791141f"}, + {file = "numpy-1.25.2-cp39-cp39-win32.whl", hash = "sha256:2792d23d62ec51e50ce4d4b7d73de8f67a2fd3ea710dcbc8563a51a03fb07b01"}, + {file = "numpy-1.25.2-cp39-cp39-win_amd64.whl", hash = "sha256:76b4115d42a7dfc5d485d358728cdd8719be33cc5ec6ec08632a5d6fca2ed380"}, + {file = "numpy-1.25.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1a1329e26f46230bf77b02cc19e900db9b52f398d6722ca853349a782d4cff55"}, + {file = "numpy-1.25.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c3abc71e8b6edba80a01a52e66d83c5d14433cbcd26a40c329ec7ed09f37901"}, + {file = "numpy-1.25.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1b9735c27cea5d995496f46a8b1cd7b408b3f34b6d50459d9ac8fe3a20cc17bf"}, + {file = "numpy-1.25.2.tar.gz", hash = "sha256:fd608e19c8d7c55021dffd43bfe5492fab8cc105cc8986f813f8c3c048b38760"}, +] + [[package]] name = "packaging" version = "23.1" @@ -1368,4 +1402,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "c231d321e7a1dad8baf255e947d1c3b35f05c12d02b144a72c17d70d4ebdbfc1" +content-hash = "4b3de0486bed96ef508a2314542ba3efdfa84daf7e2fee43d92b517e9a6ee555" diff --git a/tests/load/pyproject.toml b/tests/load/pyproject.toml index 8860689b9..d75733884 100644 --- a/tests/load/pyproject.toml +++ b/tests/load/pyproject.toml @@ -30,6 +30,7 @@ license = "Mozilla Public License Version 2.0" [tool.poetry.dependencies] python = "^3.10" locust = "^2.15.1" +numpy = "^1.25.2" websocket-client = "1.5.2" [tool.poetry.group.dev.dependencies] diff --git a/tests/load/setup_k8s.sh b/tests/load/setup_k8s.sh index 0003cc397..4e92f7342 100644 --- a/tests/load/setup_k8s.sh +++ b/tests/load/setup_k8s.sh @@ -28,9 +28,6 @@ echo "Image tag for locust is set to: ${LOCUST_IMAGE_TAG}" ENVIRONMENT_VARIABLES=( "TARGET_HOST,$TARGET" 'LOCUST_CSV,autopush' - 'LOCUST_USERS,"83300"' - 'LOCUST_SPAWN_RATE,"694"' - 'LOCUST_RUN_TIME,"600"' # 10 minutes 'LOCUST_LOGLEVEL,INFO' 'LOCUST_LOGFILE,autopush.log' 'SERVER_URL,wss://autopush.stage.mozaws.net'