Skip to content

Add load testing based on test cases - #35

Merged
andig merged 11 commits into
evcc-io:mainfrom
lfloeer:feature/loadtests
Oct 22, 2025
Merged

Add load testing based on test cases#35
andig merged 11 commits into
evcc-io:mainfrom
lfloeer:feature/loadtests

Conversation

@lfloeer

@lfloeer lfloeer commented Oct 18, 2025

Copy link
Copy Markdown
Contributor

I've expanded the test suite to include examples read from JSON files. These files are also used to run a load test against the API.

The Makefile holds the configuration for these tests.

To run, execute make run-gunicorn in a shell.
In a second shell, run make loadtest.
There are two setups tested: One where there are as many users as workers, and one, where there are twice as many.

An example output looks like the following

uv run locust --host http://localhost:7050 --headless -t 30s -u 2 --only-summary
[2025-10-18 15:43:40,350] codespaces-d0d733/INFO/locust.main: Starting Locust 2.41.6
[2025-10-18 15:43:40,350] codespaces-d0d733/INFO/locust.main: Run time limit set to 30 seconds
[2025-10-18 15:43:40,351] codespaces-d0d733/INFO/locust.runners: Ramping to 2 users at a rate of 1.00 per second
[2025-10-18 15:43:41,353] codespaces-d0d733/INFO/locust.runners: All users spawned: {"EVCCUser": 2} (2 total users)
[2025-10-18 15:44:10,122] codespaces-d0d733/INFO/locust.main: --run-time limit reached, shutting down
[2025-10-18 15:44:10,191] codespaces-d0d733/INFO/locust.main: Shutting down (exit code 0)
Type     Name                                                                          # reqs      # fails |    Avg     Min     Max    Med |   req/s  failures/s
--------|----------------------------------------------------------------------------|-------|-------------|-------|-------|-------|-------|--------|-----------
POST     /optimize/charge-schedule                                                        128     0(0.00%) |    449      53     958    700 |    4.36        0.00
--------|----------------------------------------------------------------------------|-------|-------------|-------|-------|-------|-------|--------|-----------
         Aggregated                                                                       128     0(0.00%) |    449      53     958    700 |    4.36        0.00

Response time percentiles (approximated)
Type     Name                                                                                  50%    66%    75%    80%    90%    95%    98%    99%  99.9% 99.99%   100% # reqs
--------|--------------------------------------------------------------------------------|--------|------|------|------|------|------|------|------|------|------|------|------
POST     /optimize/charge-schedule                                                             700    740    760    780    810    850    920    920    960    960    960    128
--------|--------------------------------------------------------------------------------|--------|------|------|------|------|------|------|------|------|------|------|------
         Aggregated                                                                            700    740    760    780    810    850    920    920    960    960    960    128

uv run locust --host http://localhost:7050 --headless -t 30s -u 4 --only-summary
[2025-10-18 15:44:10,960] codespaces-d0d733/INFO/locust.main: Starting Locust 2.41.6
[2025-10-18 15:44:10,961] codespaces-d0d733/INFO/locust.main: Run time limit set to 30 seconds
[2025-10-18 15:44:10,961] codespaces-d0d733/INFO/locust.runners: Ramping to 4 users at a rate of 1.00 per second
[2025-10-18 15:44:13,967] codespaces-d0d733/INFO/locust.runners: All users spawned: {"EVCCUser": 4} (4 total users)
[2025-10-18 15:44:40,706] codespaces-d0d733/INFO/locust.main: --run-time limit reached, shutting down
[2025-10-18 15:44:40,769] codespaces-d0d733/INFO/locust.main: Shutting down (exit code 0)
Type     Name                                                                          # reqs      # fails |    Avg     Min     Max    Med |   req/s  failures/s
--------|----------------------------------------------------------------------------|-------|-------------|-------|-------|-------|-------|--------|-----------
POST     /optimize/charge-schedule                                                        141     0(0.00%) |    782      71    1608    860 |    4.81        0.00
--------|----------------------------------------------------------------------------|-------|-------------|-------|-------|-------|-------|--------|-----------
         Aggregated                                                                       141     0(0.00%) |    782      71    1608    860 |    4.81        0.00

Response time percentiles (approximated)
Type     Name                                                                                  50%    66%    75%    80%    90%    95%    98%    99%  99.9% 99.99%   100% # reqs
--------|--------------------------------------------------------------------------------|--------|------|------|------|------|------|------|------|------|------|------|------
POST     /optimize/charge-schedule                                                             860    940   1000   1100   1400   1500   1600   1600   1600   1600   1600    141
--------|--------------------------------------------------------------------------------|--------|------|------|------|------|------|------|------|------|------|------|------
         Aggregated                                                                            860    940   1000   1100   1400   1500   1600   1600   1600   1600   1600    141

You can use environment variables before executing make run-gunicorn to alter the configuration of the solver, e.g.

export OPTIMIZER_TIME_LIMIT=10
make run-gunicorn

@lfloeer

lfloeer commented Oct 18, 2025

Copy link
Copy Markdown
Contributor Author

Ah, just saw the test cases by @ekkea. I'll re-make this PR with these cases as the basis instead.

@lfloeer lfloeer changed the title Add load testing and scenarios Add load testing based on test cases Oct 18, 2025
@lfloeer

lfloeer commented Oct 18, 2025

Copy link
Copy Markdown
Contributor Author

A few observations on the load testing:

  • It is important that the optimizers don't overload the CPUs in case of multiple concurrent requests. If you have 4 CPUs on a machine, use 4 workers and 1 thread per optimizer to achieve the best requests per second.
  • Typical requests are finished in at most a few seconds, even on rather slow CPUs (I've tested this on the code-space CPUs). So setting the OPTIMIZER_TIME_LIMIT to 25 is safe to avoid worker timeouts and not block any reasonable request. Together with the threads limit, this will also avoid "stuck" optimization problems from lingering around and overloading the CPU.

Based on these observations, I've updated the Dockerfile with default configurations that should work for a four core machine. One can use the GUNICORN_CMD_ARGS environment variable to set a lower number of workers, e.g. GUNICORN_CMD_ARGS=--workers 2.

@CiNcH83

CiNcH83 commented Oct 18, 2025

Copy link
Copy Markdown
Contributor

So in case of single client and a server with 4 CPUs, best configuration would probably be 1 Worker and 4 Threads? But in this case the number of workers shouldn‘t matter a lot.

@lfloeer

lfloeer commented Oct 18, 2025

Copy link
Copy Markdown
Contributor Author

Yes, this should give you the fastest solving times in the case of a single user, but might give you large load spikes if the optimizer fails to solve the problem, as it would run for 25 seconds with all cores fully used, which might be fine, depending on what else is running on the system.

@lfloeer

lfloeer commented Oct 21, 2025

Copy link
Copy Markdown
Contributor Author

@andig Any further clarification needed or objections to adding this PR to the repository?

@andig
andig merged commit ac2eb6a into evcc-io:main Oct 22, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants