Skip to content
Draft
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
48 changes: 34 additions & 14 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ WORK_DIR=$(shell pwd)/work
server-sentinel-start server-sentinel-stop \
server-start server-stop \
java-build java-test java-run java-clean \
python-build python-test python-run python-clean \
python-build python-test python-run python-run-async python-run-sync python-clean python-info \
ruby-build ruby-test ruby-run ruby-clean ruby-info \
csharp-build csharp-test csharp-run csharp-clean csharp-info \
config-editor-build config-editor-dev
Expand All @@ -56,10 +56,11 @@ help:
@echo " make java-run Run Java benchmark (requires DRIVER and WORKLOAD)"
@echo " make java-clean Clean Java build artifacts"
@echo ""
@echo "Python Engine (placeholder):"
@echo " make python-build Build Python benchmark engine"
@echo "Python Engine:"
@echo " make python-build Install Python benchmark engine"
@echo " make python-test Run Python tests"
@echo " make python-run Run Python benchmark"
@echo " make python-run Run Python benchmark (requires DRIVER and WORKLOAD)"
@echo " make python-info Show supported Python drivers and commands"
@echo ""
@echo "Ruby Engine:"
@echo " make ruby-build Install Ruby dependencies"
Expand Down Expand Up @@ -305,24 +306,43 @@ java-info: java-build
java -jar $(JAVA_JAR) --info

# ============================================================================
# Python Engine (Placeholder)
# Python Engine
# ============================================================================

python-build:
@echo "Python engine not yet implemented"
@echo "Placeholder for: cd python && pip install -e ."
cd python && pip install -e .

python-test:
@echo "Python engine not yet implemented"
@echo "Placeholder for: cd python && pytest"
cd python && python -m pytest

python-run:
@echo "Python engine not yet implemented"
@echo "Placeholder for: python -m resp_bench --server $(SERVER) --driver $(DRIVER) --workload $(WORKLOAD)"
python-run: python-build
cd python && python -m resp_bench \
--server $(SERVER) \
--driver ../$(DRIVER) \
--workload ../$(WORKLOAD) \
--metrics ../$(METRICS_OUTPUT)

python-run-async: python-build
cd python && python -m resp_bench \
--server $(SERVER) \
--driver ../$(DRIVER) \
--workload ../$(WORKLOAD) \
--metrics ../$(METRICS_OUTPUT) \
--mode async

python-run-sync: python-build
cd python && python -m resp_bench \
--server $(SERVER) \
--driver ../$(DRIVER) \
--workload ../$(WORKLOAD) \
--metrics ../$(METRICS_OUTPUT) \
--mode sync

python-clean:
@echo "Python engine not yet implemented"
@echo "Placeholder for: cd python && rm -rf __pycache__ *.egg-info dist build"
cd python && rm -rf __pycache__ *.egg-info dist build src/*.egg-info

python-info: python-build
cd python && python -m resp_bench --info

# ============================================================================
# Ruby Engine
Expand Down
7 changes: 7 additions & 0 deletions configs/drivers/default/redis-py.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"schema_version": "1.0",
"description": "redis-py client - default configuration",
"driver_id": "redis-py",
"mode": "standalone",
"specific_driver_config": {}
}
7 changes: 7 additions & 0 deletions configs/drivers/default/valkey-glide-python.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"schema_version": "1.0",
"description": "valkey-glide Python client - default configuration",
"driver_id": "valkey-glide-python",
"mode": "standalone",
"specific_driver_config": {}
}
7 changes: 7 additions & 0 deletions configs/drivers/default/valkey-py.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"schema_version": "1.0",
"description": "valkey-py client - default configuration",
"driver_id": "valkey-py",
"mode": "standalone",
"specific_driver_config": {}
}
7 changes: 7 additions & 0 deletions configs/drivers/example-redis-py-standalone.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"schema_version": "1.0",
"description": "redis-py client - standalone mode",
"driver_id": "redis-py",
"mode": "standalone",
"specific_driver_config": {}
}
7 changes: 7 additions & 0 deletions configs/drivers/example-valkey-glide-python-standalone.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"schema_version": "1.0",
"description": "valkey-glide Python client - standalone mode",
"driver_id": "valkey-glide-python",
"mode": "standalone",
"specific_driver_config": {}
}
7 changes: 7 additions & 0 deletions configs/drivers/example-valkey-py-standalone.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"schema_version": "1.0",
"description": "valkey-py client - standalone mode",
"driver_id": "valkey-py",
"mode": "standalone",
"specific_driver_config": {}
}
8 changes: 8 additions & 0 deletions configs/drivers/high-throughput/redis-py.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"schema_version": "1.0",
"description": "redis-py client - high-throughput configuration",
"driver_id": "redis-py",
"mode": "standalone",
"specific_driver_config": {},
"command_timeout_ms": 10000
}
8 changes: 8 additions & 0 deletions configs/drivers/high-throughput/valkey-glide-python.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"schema_version": "1.0",
"description": "valkey-glide Python client - high-throughput configuration",
"driver_id": "valkey-glide-python",
"mode": "standalone",
"specific_driver_config": {},
"command_timeout_ms": 10000
}
8 changes: 8 additions & 0 deletions configs/drivers/high-throughput/valkey-py.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"schema_version": "1.0",
"description": "valkey-py client - high-throughput configuration",
"driver_id": "valkey-py",
"mode": "standalone",
"specific_driver_config": {},
"command_timeout_ms": 10000
}
117 changes: 69 additions & 48 deletions python/README.md
Original file line number Diff line number Diff line change
@@ -1,67 +1,88 @@
# resp-bench Python Engine

🚧 **This engine is planned but not yet implemented.**
Python implementation of the resp-bench benchmark suite, supporting both sync and async execution modes.

## Overview
## Supported Drivers

Python implementation of the resp-bench benchmark suite.
| Driver | Package | Sync | Async |
|--------|---------|------|-------|
| redis-py | `redis` | ✅ | ✅ |
| valkey-py | `valkey` | ✅ | ✅ |
| valkey-glide | `valkey-glide` | ✅ | ✅ |

## Planned Drivers
## Installation

| Driver | Package | Status |
|--------|---------|--------|
| redis-py | `redis` | 📋 Planned |
| redis-py-async | `redis[hiredis]` | 📋 Planned |
| valkey-glide | `valkey-glide` | 📋 Planned |

## Planned Features

- Full parity with Java engine
- Async/await based execution using `asyncio`
- HdrHistogram for latency collection
- NDJSON metrics output

## Contributing

We welcome contributions to implement the Python engine! Please see:
- [Architecture Documentation](../docs/ARCHITECTURE.md)
- [Adding a Language Guide](../docs/ADDING_LANGUAGE.md)

## Directory Structure (Planned)

```
python/
├── README.md
├── pyproject.toml
├── requirements.txt
└── src/
└── resp_bench/
├── __init__.py
├── __main__.py
├── client/
│ ├── __init__.py
│ ├── interface.py
│ └── impl/
│ └── redis_py.py
├── command/
├── config/
├── engine/
└── metrics/
```bash
cd python
pip install -e .
```

## Usage (Future)
## Usage

```bash
# Install
pip install -e .

# Run benchmark
# Run benchmark (async mode, default)
python -m resp_bench \
--server localhost:6379 \
--driver ../configs/drivers/example-redis-py-standalone.json \
--workload ../configs/workloads/example-workload.json \
--metrics output.ndjson

# Run in sync mode
python -m resp_bench \
--server localhost:6379 \
--driver ../configs/drivers/example-redis-py-standalone.json \
--workload ../configs/workloads/example-workload.json \
--metrics output.ndjson \
--mode sync

# Show supported drivers
python -m resp_bench --info
```

## Using with Makefile

```bash
# Default (async)
make python-run DRIVER=configs/drivers/example-redis-py-standalone.json \
WORKLOAD=configs/workloads/example-workload.json

# Explicit sync
make python-run-sync DRIVER=configs/drivers/example-redis-py-standalone.json \
WORKLOAD=configs/workloads/example-workload.json

# Explicit async
make python-run-async DRIVER=configs/drivers/example-redis-py-standalone.json \
WORKLOAD=configs/workloads/example-workload.json
```

## Testing valkey-glide PRs

To benchmark a specific valkey-glide PR branch:

```bash
# Install the PR branch
pip install git+https://github.com/valkey-io/valkey-glide.git@<branch-name>#subdirectory=python

# Run benchmark
make python-run \
DRIVER=configs/drivers/example-valkey-glide-python-standalone.json \
WORKLOAD=configs/workloads/example-workload.json
```

## Architecture

- **Async mode**: Uses `asyncio` with one coroutine per connection. Best for I/O-bound workloads with many connections.
- **Sync mode**: Uses `ThreadPoolExecutor` with one thread per connection. Each thread has its own `MetricsCollector` to avoid lock contention, merged after the phase completes.

Both modes support:
- HdrHistogram latency collection (1µs–600s, 3 significant figures)
- NDJSON output compatible with the resp-bench graph generator
- Java-compatible deterministic key generation (LCG PRNG)
- Token bucket rate limiting (CPS/RPS)
- Request-count and duration-based completion criteria

## Supported Commands

- `get` — GET key
- `set` — SET key value
- `ping` — PING
25 changes: 25 additions & 0 deletions python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[build-system]
requires = ["setuptools>=68.0"]
build-backend = "setuptools.build_meta"

[project]
name = "resp-bench"
version = "1.0.0"
description = "Python implementation of resp-bench benchmark suite"
requires-python = ">=3.9"
license = "Apache-2.0"
dependencies = [
"redis>=5.0",
"valkey-glide>=1.0",
"hdrhistogram>=0.10",
]

[project.optional-dependencies]
async = ["redis>=5.0", "valkey-glide>=1.0"]
dev = ["pytest>=7.0"]

[project.scripts]
resp-bench = "resp_bench.__main__:main"

[tool.setuptools.packages.find]
where = ["src"]
3 changes: 3 additions & 0 deletions python/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
redis>=5.0
valkey-glide>=1.0
hdrhistogram>=0.10
1 change: 1 addition & 0 deletions python/src/resp_bench/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

73 changes: 73 additions & 0 deletions python/src/resp_bench/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""resp-bench Python engine CLI entry point."""

import argparse
import asyncio
import json
import sys

from .client.benchmark_client_factory import BenchmarkClientFactory
from .config.config_loader import ConfigLoader
from .engine.benchmark_engine import BenchmarkEngine


def print_info():
drivers = BenchmarkClientFactory.supported_drivers()
print("resp-bench Python Engine")
print()
print("Supported drivers:")
for d in drivers:
print(f" - {d}")
print()
print("Supported commands: get, set, ping")


def main():
parser = argparse.ArgumentParser(description="resp-bench Python engine")
parser.add_argument("--server", help="Server address (host:port)")
parser.add_argument("--driver", help="Driver config JSON file")
parser.add_argument("--workload", help="Workload config JSON file")
parser.add_argument("--metrics", help="Metrics output file (NDJSON)")
parser.add_argument("--commit-id", help="Git commit ID for metadata")
parser.add_argument(
"--mode",
choices=["sync", "async"],
default="async",
help="Execution mode (default: async)",
)
parser.add_argument(
"--info", action="store_true", help="Show supported drivers"
)

args = parser.parse_args()

if args.info:
print_info()
return

if not all([args.server, args.driver, args.workload, args.metrics]):
parser.error("--server, --driver, --workload, and --metrics are required")

driver_config = ConfigLoader.load_driver_config(args.driver)
workload_config = ConfigLoader.load_workload_config(args.workload)

host, _, port_str = args.server.partition(":")
port = int(port_str) if port_str else 6379

engine = BenchmarkEngine(
host=host,
port=port,
driver_config=driver_config,
workload_config=workload_config,
metrics_path=args.metrics,
commit_id=args.commit_id,
mode=args.mode,
)

if args.mode == "async":
asyncio.run(engine.run_async())
else:
engine.run_sync()


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions python/src/resp_bench/client/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Loading