Skip to content
Open
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
15 changes: 15 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
# chainbench-grpc (Solana Yellowstone gRPC tool) build stage.
# Builds the Rust binary from a pinned ref so `chainbench grpc` can shell out to
# it. protoc is vendored by the crate, so no extra system deps are needed.
# NOTE: assumes the repo is reachable anonymously. If it stays private, pass a
# token via BuildKit secret (`--mount=type=secret,id=gh_token`) instead.
FROM rust:1.90-bookworm AS grpc
ARG CHAINBENCH_GRPC_REF=v0.4.0
RUN git clone --depth 1 --branch "${CHAINBENCH_GRPC_REF}" \
https://github.com/CSFeo/chainbench-grpc /src \
&& cd /src \
&& cargo build --release

# build stage
FROM python:3.10-bookworm AS venv

Expand All @@ -21,6 +33,9 @@ ENV PATH /app/venv/bin:$PATH

RUN apt-get update && apt-get install -y tini htop nano curl

# Bundle the chainbench-grpc binary on PATH (used by `chainbench grpc`).
COPY --from=grpc /src/target/release/chainbench-grpc /usr/local/bin/chainbench-grpc

WORKDIR /app
COPY . ./
RUN python -m pip install .
Expand Down
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,31 @@ chainbench list clients
```
If you don't specify the `--clients` option, the tool will default to Ethereum JSON-RPC Specification (eth).

### Solana Yellowstone gRPC benchmarks

`chainbench grpc` benchmarks Solana Yellowstone (Geyser) gRPC endpoints. It delegates
to the bundled [`chainbench-grpc`](https://github.com/CSFeo/chainbench-grpc) binary
(shipped on `PATH` in the Docker image), so flags after the mode are passed straight
through to that tool:

```shell
# absolute latency of a single endpoint
chainbench grpc latency --url https://grpc.example.com --token YOUR_TOKEN

# compare two endpoints (win rate + relative latency)
chainbench grpc race -u https://ep1 -t t1 -u https://ep2 -t t2 --transactions 5000

# stream throughput / slot lifecycle / full report
chainbench grpc throughput --url https://grpc.example.com --duration 60
chainbench grpc slots --url https://grpc.example.com --target-slots 100

# the binary renders its own help
chainbench grpc latency --help
```

Modes: `race`, `latency`, `throughput`, `slots`, `full`. When running outside the
Docker image, install the `chainbench-grpc` binary and ensure it is on `PATH`.

## License
This project is licensed under the [Apache 2.0 License](LICENSE).

Expand Down
60 changes: 60 additions & 0 deletions chainbench/grpc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""``chainbench grpc`` — Solana Yellowstone gRPC benchmarks.

Thin pass-through to the ``chainbench-grpc`` Rust binary (shipped on ``PATH`` in
the ChainBench Docker image). It forwards stdout/stderr and the process exit
code; the binary owns its own flags, validation, and ``--help``. Pass tool flags
after the mode, e.g.::

chainbench grpc latency --url https://grpc.example.com --token TOKEN
chainbench grpc race -u https://a -t t1 -u https://b -t t2 --transactions 5000
chainbench grpc latency --help # rendered by the binary
"""

import shutil
import subprocess
import sys

import click

GRPC_BIN = "chainbench-grpc"
GRPC_REPO = "https://github.com/CSFeo/chainbench-grpc"
MODES = ("race", "latency", "throughput", "slots", "full")


def _run(mode: str, args: tuple[str, ...]) -> None:
exe = shutil.which(GRPC_BIN)
if exe is None:
raise click.ClickException(
f"`{GRPC_BIN}` not found on PATH. It ships in the ChainBench Docker "
f"image; for local use install it from {GRPC_REPO}"
)
completed = subprocess.run([exe, mode, *args])
sys.exit(completed.returncode)


@click.group(
name="grpc",
help=(
"Solana Yellowstone gRPC benchmarks (delegates to the chainbench-grpc "
"binary). Flags after the mode are passed through to the binary, e.g. "
"`grpc latency --url URL --token TOKEN`."
),
)
def grpc() -> None:
pass


def _register(mode: str) -> None:
@grpc.command(
name=mode,
help=f"Run chainbench-grpc `{mode}` (flags are passed through to the binary).",
context_settings={"ignore_unknown_options": True, "allow_extra_args": True},
add_help_option=False, # let the binary render its own --help
)
@click.argument("args", nargs=-1, type=click.UNPROCESSED)
def _cmd(args: tuple[str, ...]) -> None:
_run(mode, args)


for _mode in MODES:
_register(_mode)
4 changes: 4 additions & 0 deletions chainbench/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from locust.argument_parser import parse_locustfile_paths
from locust.util.load_locustfile import load_locustfile

from chainbench.grpc import grpc as grpc_group
from chainbench.user import EvmUser, SolanaUser, get_subclass_tasks
from chainbench.user.common import all_method_classes, all_methods
from chainbench.util.cli import (
Expand Down Expand Up @@ -45,6 +46,9 @@ def cli(ctx: Context):
ctx.obj = ContextData()


cli.add_command(grpc_group)


def validate_method(ctx: Context, param: Parameter, value: str) -> str:
if value is not None:
if value not in all_methods.keys():
Expand Down