Skip to content

Commit 429c9cc

Browse files
Copilottoby-coleman
andcommitted
ci: Replace pytest-benchmark with pytest-codspeed and improve benchmarks
- Replace pytest-benchmark with pytest-codspeed in test dependencies - Rewrite benchmark tests to use @pytest.mark.benchmark decorator - Add full process lifecycle benchmark (init + run + destroy) - Parameterize benchmarks across AsyncioConnector and ZMQConnector - Replace GitHub Actions workflow with CodSpeed integration Co-authored-by: toby-coleman <13170610+toby-coleman@users.noreply.github.com> Agent-Logs-Url: https://github.com/plugboard-dev/plugboard/sessions/b8d70f1d-7f6f-412b-9452-e70611a349de
1 parent 0c9475b commit 429c9cc

4 files changed

Lines changed: 98 additions & 104 deletions

File tree

.github/workflows/benchmarks.yaml

Lines changed: 17 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,45 @@
11
name: Benchmarks
22

33
on:
4+
push:
5+
branches:
6+
- main
47
pull_request:
58
types:
69
- opened
710
- synchronize
11+
# `workflow_dispatch` allows CodSpeed to trigger backtest
12+
# performance analysis in order to generate initial data.
13+
workflow_dispatch:
814

915
jobs:
1016
benchmark:
11-
name: Benchmark tests
17+
name: Run benchmarks
1218
runs-on: ubuntu-latest
1319
permissions:
1420
contents: read
15-
pull-requests: write
16-
strategy:
17-
matrix:
18-
python_version: [3.12]
21+
id-token: write
1922
steps:
20-
- name: Checkout branch
23+
- name: Checkout
2124
uses: actions/checkout@v4
22-
with:
23-
path: pr
24-
25-
- name: Checkout main
26-
uses: actions/checkout@v4
27-
with:
28-
ref: main
29-
path: main
3025

3126
- name: Install python
3227
uses: actions/setup-python@v5
3328
with:
34-
python-version: ${{matrix.python_version}}
29+
python-version: "3.12"
3530

3631
- name: Install uv
3732
uses: astral-sh/setup-uv@v4
3833
with:
3934
enable-cache: true
40-
cache-dependency-glob: "main/uv.lock"
41-
42-
- name: Setup benchmarks
43-
run: |
44-
echo "BASE_SHA=$(echo ${{ github.event.pull_request.base.sha }} | cut -c1-8)" >> $GITHUB_ENV
45-
echo "HEAD_SHA=$(echo ${{ github.event.pull_request.head.sha }} | cut -c1-8)" >> $GITHUB_ENV
46-
echo "PR_COMMENT=$(mktemp)" >> $GITHUB_ENV
47-
48-
- name: Run benchmarks on PR
49-
working-directory: ./pr
50-
run: |
51-
uv sync --group test
52-
uv run pytest --benchmark-only --benchmark-save=pr
53-
54-
- name: Run benchmarks on main
55-
working-directory: ./main
56-
continue-on-error: true
57-
run: |
58-
uv sync --group test
59-
uv run pytest --benchmark-only --benchmark-save=base
35+
cache-dependency-glob: "uv.lock"
6036

61-
- name: Compare results
62-
continue-on-error: false
63-
run: |
64-
uvx pytest-benchmark compare **/.benchmarks/**/*.json | tee cmp_results
37+
- name: Install project
38+
run: uv sync --group test
6539

66-
echo 'Benchmark comparison for [`${{ env.BASE_SHA }}`](${{ github.event.repository.html_url }}/commit/${{ github.event.pull_request.base.sha }}) (base) vs [`${{ env.HEAD_SHA }}`](${{ github.event.repository.html_url }}/commit/${{ github.event.pull_request.head.sha }}) (PR)' >> pr_comment
67-
echo '```' >> pr_comment
68-
cat cmp_results >> pr_comment
69-
echo '```' >> pr_comment
70-
cat pr_comment > ${{ env.PR_COMMENT }}
71-
72-
- name: Comment on PR
73-
uses: actions/github-script@v7
40+
- name: Run benchmarks
41+
uses: CodSpeedHQ/action@v4
7442
with:
75-
github-token: ${{ secrets.GITHUB_TOKEN }}
76-
script: |
77-
github.rest.issues.createComment({
78-
issue_number: context.issue.number,
79-
owner: context.repo.owner,
80-
repo: context.repo.repo,
81-
body: require('fs').readFileSync('${{ env.PR_COMMENT }}').toString()
82-
});
43+
mode: walltime
44+
run: uv run pytest tests/benchmark/ --codspeed
8345

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ test = [
8080
"optuna>=3.0,<5",
8181
"pytest>=8.3,<10",
8282
"pytest-asyncio>=1.0,<2",
83-
"pytest-benchmark>=5.1.0",
83+
"pytest-codspeed>=4.3.0",
8484
"pytest-cases>=3.8,<4",
8585
"pytest-env>=1.1,<2",
8686
"pytest-rerunfailures>=15.0,<17",
Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,61 @@
1-
"""Simple benchmark tests for Plugboard models."""
1+
"""Benchmark tests for Plugboard processes."""
22

33
import asyncio
44

5-
from pytest_benchmark.fixture import BenchmarkFixture
5+
import pytest
66

7-
from plugboard.connector import AsyncioConnector
8-
from plugboard.process import LocalProcess, Process
7+
from plugboard.connector import AsyncioConnector, Connector, ZMQConnector
8+
from plugboard.process import LocalProcess
99
from plugboard.schemas import ConnectorSpec
1010
from tests.integration.test_process_with_components_run import A, B
1111

1212

13-
def _setup_process() -> tuple[tuple[Process], dict]:
14-
comp_a = A(name="comp_a", iters=1000)
13+
ITERS = 1000
14+
15+
16+
def _build_process(connector_cls: type[Connector]) -> LocalProcess:
17+
"""Build a process with the given connector class."""
18+
comp_a = A(name="comp_a", iters=ITERS)
1519
comp_b1 = B(name="comp_b1", factor=1)
1620
comp_b2 = B(name="comp_b2", factor=2)
1721
components = [comp_a, comp_b1, comp_b2]
1822
connectors = [
19-
AsyncioConnector(spec=ConnectorSpec(source="comp_a.out_1", target="comp_b1.in_1")),
20-
AsyncioConnector(spec=ConnectorSpec(source="comp_b1.out_1", target="comp_b2.in_1")),
23+
connector_cls(spec=ConnectorSpec(source="comp_a.out_1", target="comp_b1.in_1")),
24+
connector_cls(spec=ConnectorSpec(source="comp_b1.out_1", target="comp_b2.in_1")),
2125
]
22-
process = LocalProcess(components=components, connectors=connectors)
23-
# Initialise process so that this is excluded from the benchmark timing
24-
asyncio.run(process.init())
25-
# Return args and kwargs tuple for benchmark.pedantic
26-
return (process,), {}
27-
28-
29-
def _run_process(process: Process) -> None:
30-
asyncio.run(process.run())
31-
32-
33-
def test_benchmark_process_run(benchmark: BenchmarkFixture) -> None:
34-
"""Benchmark the running of a Plugboard Process."""
35-
benchmark.pedantic(_run_process, setup=_setup_process, rounds=5)
26+
return LocalProcess(components=components, connectors=connectors)
27+
28+
29+
@pytest.mark.benchmark
30+
@pytest.mark.parametrize(
31+
"connector_cls",
32+
[AsyncioConnector, ZMQConnector],
33+
ids=["asyncio", "zmq"],
34+
)
35+
def test_benchmark_process_run(connector_cls: type[Connector]) -> None:
36+
"""Benchmark the init and run of a Plugboard Process."""
37+
38+
async def _run() -> None:
39+
process = _build_process(connector_cls)
40+
await process.init()
41+
await process.run()
42+
43+
asyncio.run(_run())
44+
45+
46+
@pytest.mark.benchmark
47+
@pytest.mark.parametrize(
48+
"connector_cls",
49+
[AsyncioConnector, ZMQConnector],
50+
ids=["asyncio", "zmq"],
51+
)
52+
def test_benchmark_process_lifecycle(connector_cls: type[Connector]) -> None:
53+
"""Benchmark the full lifecycle (init, run, destroy) of a Plugboard Process."""
54+
55+
async def _lifecycle() -> None:
56+
process = _build_process(connector_cls)
57+
await process.init()
58+
await process.run()
59+
await process.destroy()
60+
61+
asyncio.run(_lifecycle())

uv.lock

Lines changed: 32 additions & 26 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)