Skip to content

🎨 Improve load test framework (yet again) #6051

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 14 additions & 9 deletions tests/performance/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,6 @@ test: ## runs osparc locust. Locust and test configuration are specified in ENV_
fi
docker compose --file docker-compose.yml up --scale worker=4 --exit-code-from=master

.PHONY: install-ci install-dev install

install-dev: install
install-ci: install

install: _check_venv_active ## install python tools
@uv pip install -r requirements.txt


.PHONY: dashboards-up dashboards-down

dashboards-up: ## Create Grafana dashboard for inspecting locust results. See dashboard on localhost:3000
Expand All @@ -70,3 +61,17 @@ dashboards-up: ## Create Grafana dashboard for inspecting locust results. See da

dashboards-down:
@locust-compose down

.PHONY: install-ci install-dev

install-dev:
@uv pip install -r requirements/requirements-dev.txt

install-ci: install
@uv pip install -r requirements/requirements-ci.txt


.PHONY: config
config:
@$(call check_defined, input, please define inputs when calling $@ - e.g. ```make $@ input="--help"```)
@python locust_settings.py $(input) | tee .env
13 changes: 9 additions & 4 deletions tests/performance/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@ In the [locust_files] folder are located the test files.

## Usage

1. Generate a `.env` file with setting for your test. After running `make install-dev` you can execute your test script in python and set settings as arguments. Once your settings are validated you pipe them to `.env`. E.g. if your testscript is `locust_files/platform_ping_test.py` you could run
1. All settings are passed to the locust container as environment variables in `.env`. To generate locust env vars, run `make config` with appropriate `input`. To see what the possible settings are, run `make config input="--help"`. E.g. you could run
```bash
python locust_files/platform_ping_test.py --LOCUST_HOST=https://api.osparc-master.speag.com \
--LOCUST_USERS=100 --LOCUST_RUN_TIME=0:10:00 --SC_USER_NAME=myname --SC_PASSWORD=mypassword > .env
make config input="--LOCUST_HOST=https://api.osparc-master.speag.com
--LOCUST_USERS=100 --LOCUST_RUN_TIME=0:10:00 --LOCUST_LOCUSTFILE=locust_files/platform_ping_test.py"
```
2. Run your test script using the Make `test` recipe, e.g.
This will validate your settings and you should be good to go once you see a the settings printed in your terminal.
2. Add settings related to your locust file. E.g. if your file expects to find an environment variable `MYENVVAR` you add it to `.env`:
```bash
echo "MYENVVAR=thisismyenvvar" >> .env
```
3. Once you have all settings setup you uun your test script using the Make `test` recipe:
```bash
make test
```

Expand Down
11 changes: 0 additions & 11 deletions tests/performance/locust_files/catalog_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
#

import logging
from pathlib import Path
from time import time

import faker
Expand Down Expand Up @@ -56,13 +55,3 @@ def on_start(self):

def on_stop(self):
print("Stopping", self.email)


if __name__ == "__main__":
from locust_settings import LocustSettings, dump_dotenv

dump_dotenv(
LocustSettings(
LOCUST_LOCUSTFILE=Path(__file__).relative_to(Path(__file__).parent.parent)
)
)
11 changes: 0 additions & 11 deletions tests/performance/locust_files/director_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
#

import logging
from pathlib import Path

from locust import task
from locust.contrib.fasthttp import FastHttpUser
Expand All @@ -30,13 +29,3 @@ def on_start(self): # pylint: disable=no-self-use

def on_stop(self): # pylint: disable=no-self-use
print("Stopping")


if __name__ == "__main__":
from locust_settings import LocustSettings, dump_dotenv

dump_dotenv(
LocustSettings(
LOCUST_LOCUSTFILE=Path(__file__).relative_to(Path(__file__).parent.parent)
)
)
14 changes: 0 additions & 14 deletions tests/performance/locust_files/locustfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
#

import logging
from pathlib import Path
from uuid import UUID

import faker
Expand Down Expand Up @@ -75,16 +74,3 @@ def on_start(self):
def on_stop(self):
self.client.post("/v0/auth/logout")
print("Stopping", self.email)


if __name__ == "__main__":
from locust_settings import LocustSettings, dump_dotenv

class LoadTestSettings(TemplateSettings, LocustSettings):
pass

dump_dotenv(
LoadTestSettings(
LOCUST_LOCUSTFILE=Path(__file__).relative_to(Path(__file__).parent.parent)
)
)
15 changes: 0 additions & 15 deletions tests/performance/locust_files/metamodeling/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,18 +163,3 @@ def upload_file(self, file: Path) -> UUID:
file_uuid = response.json().get("id")
assert file_uuid is not None
return UUID(file_uuid)


if __name__ == "__main__":
from locust_settings import LocustSettings, dump_dotenv

class MetaModelingSettings(UserSettings, LocustSettings):
pass

dump_dotenv(
MetaModelingSettings(
LOCUST_LOCUSTFILE=Path(__file__).relative_to(
Path(__file__).parent.parent.parent
)
)
)
14 changes: 0 additions & 14 deletions tests/performance/locust_files/platform_ping_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
#

import logging
from pathlib import Path

import locust_plugins
from locust import task
Expand Down Expand Up @@ -51,16 +50,3 @@ def on_start(self): # pylint: disable=no-self-use

def on_stop(self): # pylint: disable=no-self-use
print("Stopping locust user")


if __name__ == "__main__":
from locust_settings import LocustSettings, dump_dotenv

class LoadTestSettings(LocustAuth, LocustSettings):
pass

dump_dotenv(
LoadTestSettings(
LOCUST_LOCUSTFILE=Path(__file__).relative_to(Path(__file__).parent.parent)
)
)
11 changes: 0 additions & 11 deletions tests/performance/locust_files/user_basic_calls.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import logging
import time
from pathlib import Path

import faker
from locust import task
Expand Down Expand Up @@ -92,13 +91,3 @@ def on_start(self):

def on_stop(self):
self.logout(self.email)


if __name__ == "__main__":
from locust_settings import LocustSettings, dump_dotenv

dump_dotenv(
LocustSettings(
LOCUST_LOCUSTFILE=Path(__file__).relative_to(Path(__file__).parent.parent)
)
)
11 changes: 0 additions & 11 deletions tests/performance/locust_files/webserver_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
#

import logging
from pathlib import Path

import faker
from dotenv import load_dotenv
Expand Down Expand Up @@ -50,13 +49,3 @@ def on_start(self):
def on_stop(self):
self.client.post("/v0/auth/logout")
print("Stopping", self.email)


if __name__ == "__main__":
from locust_settings import LocustSettings, dump_dotenv

dump_dotenv(
LocustSettings(
LOCUST_LOCUSTFILE=Path(__file__).relative_to(Path(__file__).parent.parent)
)
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
# pylint: disable=no-self-use
# pylint: disable=no-name-in-module

import json
from datetime import timedelta
from pathlib import Path
from typing import Final

from parse import Result, parse
from pydantic import (
Expand All @@ -18,7 +20,10 @@
)
from pydantic_settings import BaseSettings, SettingsConfigDict

from ._dump_dotenv import dump_dotenv
_TEST_DIR: Final[Path] = Path(__file__).parent.resolve()
_LOCUST_FILES_DIR: Final[Path] = _TEST_DIR / "locust_files"
assert _TEST_DIR.is_dir()
assert _LOCUST_FILES_DIR.is_dir()


class LocustSettings(BaseSettings):
Expand All @@ -30,12 +35,22 @@ class LocustSettings(BaseSettings):
LOCUST_HOST: AnyHttpUrl = Field(
default=..., examples=["https://api.osparc-master.speag.com"]
)
LOCUST_LOCUSTFILE: Path = Field(default=...)
LOCUST_LOCUSTFILE: Path = Field(
default=...,
description="Test file. Path should be relative to `locust_files` dir",
)
LOCUST_PRINT_STATS: bool = Field(default=True)
LOCUST_RUN_TIME: timedelta = Field(default=...)
LOCUST_SPAWN_RATE: PositiveInt = Field(default=20)
LOCUST_TIMESCALE: NonNegativeInt = Field(default=1, ge=0, le=1)
LOCUST_USERS: PositiveInt = Field(default=...)
LOCUST_TIMESCALE: NonNegativeInt = Field(
default=1,
ge=0,
le=1,
description="Send locust data to Timescale db for reading in Grafana dashboards",
)
LOCUST_USERS: PositiveInt = Field(
default=..., description="Number of locust users you want to spawn"
)

PGHOST: str = Field(default="postgres")
PGPASSWORD: str = Field(default="password")
Expand All @@ -55,6 +70,16 @@ def validate_run_time(cls, v: str) -> str | timedelta:
raise ValueError("Could not parse time")
return timedelta(hours=hour, minutes=_min, seconds=sec)

@field_validator("LOCUST_LOCUSTFILE", mode="after")
@classmethod
def validate_locust_file(cls, v: Path) -> Path:
v = v.resolve()
if not v.is_file():
raise ValueError(f"{v} must be an existing file")
if not v.is_relative_to(_LOCUST_FILES_DIR):
raise ValueError(f"{v} must be a test file relative to {_LOCUST_FILES_DIR}")
return v.relative_to(_TEST_DIR)

@field_serializer("LOCUST_RUN_TIME")
def serialize_run_time(self, td: timedelta, info: SerializationInfo) -> str:
total_seconds = int(td.total_seconds())
Expand All @@ -70,4 +95,8 @@ def serialize_host(self, url: AnyHttpUrl, info: SerializationInfo) -> str:


if __name__ == "__main__":
dump_dotenv(LocustSettings())
settings = LocustSettings()
env_vars = [
f"{key}={val}" for key, val in json.loads(settings.model_dump_json()).items()
]
print("\n".join(env_vars))
8 changes: 0 additions & 8 deletions tests/performance/requirements.txt

This file was deleted.

3 changes: 3 additions & 0 deletions tests/performance/requirements/_base.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
parse
pydantic
pydantic-settings
1 change: 1 addition & 0 deletions tests/performance/requirements/requirements-ci.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--requirement _base.txt
2 changes: 2 additions & 0 deletions tests/performance/requirements/requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
--requirement _base.txt
locust-plugins
2 changes: 0 additions & 2 deletions tests/performance/settings/locust_settings/__init__.py

This file was deleted.

5 changes: 0 additions & 5 deletions tests/performance/settings/locust_settings/__main__.py

This file was deleted.

13 changes: 0 additions & 13 deletions tests/performance/settings/locust_settings/_dump_dotenv.py

This file was deleted.

7 changes: 0 additions & 7 deletions tests/performance/settings/pyproject.toml

This file was deleted.

Loading