Skip to content

Commit b0999b1

Browse files
committed
Add benches to nats-client
Signed-off-by: Casper Beyer <[email protected]>
1 parent 1730f0f commit b0999b1

File tree

7 files changed

+625
-5
lines changed

7 files changed

+625
-5
lines changed

.github/workflows/bench.yml

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
name: bench
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- "nats-client/**"
7+
push:
8+
branches:
9+
- main
10+
paths:
11+
- "nats-client/**"
12+
workflow_dispatch:
13+
14+
jobs:
15+
head:
16+
name: Head
17+
runs-on: ubuntu-latest
18+
strategy:
19+
matrix:
20+
python-version: ["3.11", "3.12", "3.13", "pypy-3.10"]
21+
steps:
22+
- name: Checkout head branch
23+
uses: actions/checkout@v5
24+
25+
- name: Set up Python
26+
uses: actions/setup-python@v6
27+
with:
28+
python-version: ${{ matrix.python-version }}
29+
30+
- name: Install uv
31+
uses: astral-sh/setup-uv@v6
32+
33+
- name: Install dependencies
34+
run: uv sync --dev
35+
working-directory: nats-client
36+
37+
- name: Run benchmarks
38+
run: |
39+
uv run pytest benches/ -k bench_ --benchmark-json=../head-bench-${{ matrix.python-version }}.json -q
40+
working-directory: nats-client
41+
42+
- name: Upload head benchmark results
43+
uses: actions/upload-artifact@v4
44+
with:
45+
name: head-benchmark-${{ matrix.python-version }}
46+
path: head-bench-${{ matrix.python-version }}.json
47+
48+
base:
49+
name: Base
50+
runs-on: ubuntu-latest
51+
strategy:
52+
matrix:
53+
python-version: ["3.11", "3.12", "3.13", "pypy-3.10"]
54+
steps:
55+
- name: Checkout base branch
56+
uses: actions/checkout@v5
57+
with:
58+
ref: ${{ github.base_ref || 'main' }}
59+
60+
- name: Set up Python
61+
uses: actions/setup-python@v6
62+
with:
63+
python-version: ${{ matrix.python-version }}
64+
65+
- name: Install uv
66+
uses: astral-sh/setup-uv@v6
67+
68+
- name: Install dependencies
69+
run: uv sync --dev
70+
working-directory: nats-client
71+
72+
- name: Run benchmarks
73+
run: |
74+
uv run pytest benches/ -k bench_ --benchmark-json=../base-bench-${{ matrix.python-version }}.json -q
75+
working-directory: nats-client
76+
77+
- name: Upload base benchmark results
78+
uses: actions/upload-artifact@v4
79+
with:
80+
name: base-benchmark-${{ matrix.python-version }}
81+
path: base-bench-${{ matrix.python-version }}.json
82+
83+
compare:
84+
name: Compare
85+
runs-on: ubuntu-latest
86+
needs: [head, base]
87+
strategy:
88+
matrix:
89+
python-version: ["3.11", "3.12", "3.13", "pypy-3.10"]
90+
steps:
91+
- name: Checkout code
92+
uses: actions/checkout@v5
93+
94+
- name: Set up Python
95+
uses: actions/setup-python@v6
96+
with:
97+
python-version: ${{ matrix.python-version }}
98+
99+
- name: Install uv
100+
uses: astral-sh/setup-uv@v6
101+
102+
- name: Install dependencies
103+
run: uv sync --dev
104+
working-directory: nats-client
105+
106+
- name: Download head benchmark results
107+
uses: actions/download-artifact@v4
108+
with:
109+
name: head-benchmark-${{ matrix.python-version }}
110+
111+
- name: Download base benchmark results
112+
uses: actions/download-artifact@v4
113+
with:
114+
name: base-benchmark-${{ matrix.python-version }}
115+
116+
- name: Compare benchmarks
117+
run: |
118+
uv run pytest-benchmark compare base-bench-${{ matrix.python-version }}.json head-bench-${{ matrix.python-version }}.json --group-by=name --sort=mean --columns=mean,median

.github/workflows/test.yml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,8 @@ jobs:
4242
4343
- name: Run tests
4444
run: |
45-
<<<<<<< HEAD
4645
uv run flake8 --ignore="W391, W503, W504, E501" ./nats/src/nats/js/
4746
uv run pytest -x -vv -s --continue-on-collection-errors ./nats/tests
48-
=======
49-
pipenv run flake8 --ignore="W391, W503, W504" ./nats/js/
50-
pipenv run pytest -x -vv -s --continue-on-collection-errors ./tests
51-
>>>>>>> 44fb982 (Add nats-server package)
5247
env:
5348
PATH: $HOME/nats-server:$PATH
5449

nats-client/benches/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Benchmarks for nats-client package."""
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
"""Benchmarks for NATS protocol parsing operations."""
2+
3+
import asyncio
4+
5+
import pytest
6+
from nats.client.protocol.message import parse, parse_headers
7+
8+
9+
class MockReader:
10+
"""Mock reader for testing protocol parsing."""
11+
12+
def __init__(self, data: bytes):
13+
self.data = data
14+
self.pos = 0
15+
16+
async def readline(self) -> bytes:
17+
"""Read a line from the data."""
18+
if self.pos >= len(self.data):
19+
return b""
20+
21+
start = self.pos
22+
while self.pos < len(self.data) and self.data[self.pos:self.pos + 1] != b"\n":
23+
self.pos += 1
24+
25+
if self.pos < len(self.data):
26+
self.pos += 1
27+
28+
return self.data[start:self.pos]
29+
30+
async def readexactly(self, n: int) -> bytes:
31+
"""Read exactly n bytes from the data."""
32+
if self.pos + n > len(self.data):
33+
raise asyncio.IncompleteReadError(
34+
self.data[self.pos:], n - (len(self.data) - self.pos)
35+
)
36+
37+
result = self.data[self.pos:self.pos + n]
38+
self.pos += n
39+
return result
40+
41+
42+
def bench_parse_msg(benchmark):
43+
"""Benchmark parsing MSG protocol message."""
44+
msg_data = b"MSG test.subject 1 5\r\nhello\r\n"
45+
46+
def run_parse():
47+
async def _parse():
48+
reader = MockReader(msg_data)
49+
return await parse(reader)
50+
return asyncio.run(_parse())
51+
52+
benchmark(run_parse)
53+
54+
55+
def bench_parse_hmsg(benchmark):
56+
"""Benchmark parsing HMSG protocol message."""
57+
msg_data = b"HMSG test.subject 1 reply 44 49\r\nNATS/1.0\r\nContent-Type: application/json\r\n\r\nhello\r\n"
58+
59+
def run_parse():
60+
async def _parse():
61+
reader = MockReader(msg_data)
62+
return await parse(reader)
63+
return asyncio.run(_parse())
64+
65+
benchmark(run_parse)
66+
67+
68+
def bench_parse_headers_simple(benchmark):
69+
"""Benchmark parsing simple headers."""
70+
header_data = b"NATS/1.0\r\nContent-Type: application/json\r\n\r\n"
71+
72+
def run_parse():
73+
return parse_headers(header_data)
74+
75+
benchmark(run_parse)
76+
77+
78+
def bench_parse_headers_with_status(benchmark):
79+
"""Benchmark parsing headers with status code."""
80+
header_data = b"NATS/1.0 503 No Responders\r\n\r\n"
81+
82+
def run_parse():
83+
return parse_headers(header_data)
84+
85+
benchmark(run_parse)
86+
87+
88+
def bench_parse_headers_multiple(benchmark):
89+
"""Benchmark parsing multiple headers."""
90+
header_data = (
91+
b"NATS/1.0\r\n"
92+
b"Content-Type: application/json\r\n"
93+
b"X-Custom-Header: value1\r\n"
94+
b"X-Another-Header: value2\r\n"
95+
b"X-Multi-Value: a\r\n"
96+
b"X-Multi-Value: b\r\n"
97+
b"\r\n"
98+
)
99+
100+
def run_parse():
101+
return parse_headers(header_data)
102+
103+
benchmark(run_parse)

0 commit comments

Comments
 (0)