Storage Benchmark Kit
Universal entry point for all AI agents. This file is the standard entry point for AI coding agents (Devin, Claude Code, Cursor, GitHub Copilot, Continue, Aider, OpenAI Codex, Windsurf, etc.) working in this repository. It tells the agent what SBK is, how to build and verify it, what conventions to follow, where things live, and what the common gotchas are.
Agent-specific configurations:
- Devin: See
.devin/skills/for executable skills- Cursor: See
.cursorrulesfor Cursor-specific rules- Aider: See
.aider.conf.ymlfor Aider configuration- Benchmark execution: any agent may use the portable
.devin/skills/sbk-benchmark-runner/and.devin/skills/sbk-distributed-benchmark-runner/knowledge packsHumans: see
for the end-user manual, and for the internal design.
SBK (Storage Benchmark Kit) is a Java framework for benchmarking any S3-compatible / message-queue / file-system / database storage. It runs identical measurement code against any backend via a small pluggable driver SPI.
./gradlew). JDK 25 required.master.| Module | Role | When you edit it |
|---|---|---|
perl/ |
PerL — Performance Logger library (lock-free queues, latency windows, percentile math). Heart of the framework. | Rarely. Only when changing core measurement behaviour. |
sbk-api/ |
The benchmark harness. Defines Storage<T> SPI, RWLogger SPI, SbkBenchmark, Sbk main bootstrap. |
When changing the harness, CLI flags, the SPI, or how loggers work. |
sbm/ |
SBM — Storage Benchmark Monitor (gRPC aggregator on port 9717). | When changing distributed aggregation. |
sbk-yal/ |
YML-driven launcher (single-node). | Rarely. |
sbk-gem/ |
SBK-GEM — SSH-based distributed launcher. | When changing the multi-host orchestration. |
sbk-gem-yal/ |
YML-driven SBK-GEM. | Rarely. |
drivers/<name>/ |
One subdirectory per storage backend. 53 are enabled in the aggregate build; disabled drivers and a template also remain in tree. | When adding or fixing a driver. This is the most common change. |
**For new drivers, see
(“Add a storage driver” recipe).**
# Default build — runs compile + checkstyle + tests on every module.
./gradlew check
# Build the launchable scripts at ./build/install/sbk/bin/sbk
./gradlew installDist
# Build a single driver (much faster while iterating)
./gradlew :drivers:minio:check
./gradlew :drivers:minio:compileJava
# Smoke-test against the public MinIO sandbox (no credentials needed)
./build/install/sbk/bin/sbk -class minio -writers 1 -size 100 -seconds 30
# Local file-system write benchmark (1 MiB records, 8 writers, 60 s)
./build/install/sbk/bin/sbk -class file -file /tmp/sbk.bin \
-writers 8 -size 1048576 -seconds 60
# Help / list available drivers
./build/install/sbk/bin/sbk -help
A change is done only after all of these succeed:
./gradlew :drivers:<your-driver>:check — module-level./gradlew check — full project./gradlew installDist — produces a working sbk scriptDriver changes: also run the driver’s CLI against a real backend (or
play.min.iofor S3) before declaring success. Compile-clean is necessary but not sufficient.
| Path | Convention |
|---|---|
drivers/<name>/build.gradle |
Subproject build script. Declares the driver’s vendor-SDK dependency. |
drivers/<name>/src/main/java/io/sbk/driver/<Name>/<Name>.java |
The Storage<T> impl. Class name = PascalCase of the driver dir name. Match the package case (e.g. MinIO driver dir → io.sbk.driver.MinIO package → MinIO.java). |
drivers/<name>/src/main/java/io/sbk/driver/<Name>/<Name>Writer.java |
The Writer<T> impl. |
drivers/<name>/src/main/java/io/sbk/driver/<Name>/<Name>Reader.java |
The Reader<T> impl. |
drivers/<name>/src/main/java/io/sbk/driver/<Name>/<Name>Config.java |
POJO holding driver-specific config. Bound from the properties file by Jackson. |
drivers/<name>/src/main/resources/<name>.properties |
Default values for every config field. |
Driver discovery is by simple class name, case-insensitive:
-class minio resolves to io.sbk.driver.MinIO.MinIO. The class name
must match the file/directory name (modulo case).
@Synchronized, @SuppressFBWarnings, etc.).
See if blocks must have braces.@param, @return, @throws.. If a new dependency brings in a new top-level package, add it there.
synchronized blocks or Lock use in the driver hot path.
The harness’s lock-free property depends on the driver also not
blocking. The vendor SDK is fine; your wrapper code shouldn’t add
synchronization.TimeStamp per record; your driver should not
allocate big arrays or maps per writeAsync() call.FileTabCharacter).RegexpHeader).
Copy from any existing driver.master.halodb driver build failureThe HaloDB artifact is hosted on GitHub Packages, which routinely
exceeds its bandwidth quota. The build will fail with a 405 / 403 from
maven.pkg.github.com unless valid credentials are in
~/.gradle/gradle.properties. For agent work, halodb is currently
commented out in
and
. Do not re-enable it without confirming the user wants to deal with the GitHub Packages credentials.
When introducing a new driver subdirectory, you must edit:
settings.gradle — no, you don’t. The driver projects are
listed in settings-drivers.gradle.— add
include 'drivers:<name>'.
— add
api project(':drivers:<name>') so the driver is bundled into the
installDist distribution.
Forgetting either of these is the #1 source of “I added the driver
but -class <name> doesn’t find it” issues.
uses
io.minio:minio:8.5.17. Do not upgrade to 9.x without testing — the
9.x SDK sends an x-amz-sdk-checksum-algorithm header on every
PutObject that older S3 backends (Dell ECS / ObjectScale, older Ceph
RGW) reject with HTTP 400 InvalidRequest. The comment in the
build.gradle explains.
The bin/sbk script puts only the versioned pathing JAR and main SBK JAR
on the classpath; everything else (your driver’s vendor SDK, transitive
deps) is reached through the pathing jar’s Class-Path: manifest. When
you change dependencies (especially driver vendor SDK versions),
Gradle’s incremental build can leave a stale pathing manifest.
Fix:
rm -rf build && ./gradlew clean :pathingJar installDist --rerun-tasks
Symptom you’d hit otherwise: NoClassDefFoundError on a class that is
clearly present in build/install/sbk/lib/.
checkstyle/import-control.xml
The checkstyle rule ImportControl enforces an allow-list of top-level
packages. If your new driver pulls in a vendor SDK at a new
top-level package (e.g. software.amazon, org.apache.solr,
okhttp3), you must add
<allow pkg="package.name" /> to
or checkstyleMain will fail.
docs/If you edit mermaid diagrams in mmdc (mermaid-cli v11+ on Node 18+). Common pitfalls:
[ / < are rendered literally in some
versions. Use plain ASCII inside [" "] node labels.++ is a reserved token in sequence-diagram messages. Use
increment count instead.—) and Unicode arrow (→) inside sequence-diagram messages
cause parse errors. Use -- and then/->.participant X as Some (Name) with unquoted parens fails — quote the
alias: participant X as "Some (Name)".InterruptedIOExceptionWhen the benchmark duration expires, the SBK framework tears down the
SDK’s HTTP dispatcher mid-call. Your driver should treat
InterruptedIOException and RejectedExecutionException as clean
shutdowns, not errors. The MinIO driver
(
The default Writer<T> and Reader<T> interface methods record
start/end timestamps and call perlChannel.send(...) for you. A
driver’s writeAsync(data) only needs to perform the operation; the
harness handles timing. Do not add your own System.nanoTime()
calls in the hot path unless you have a specific reason — and if you
do, document it in the driver’s README.
| Topic | Read |
|---|---|
| End-user manual | |
| Internal design / why is SBK fast / mermaid diagrams | |
| Step-by-step recipes (add a driver, add a logger, debug failures) | |
| Driver spec template for spec-driven development | |
| Original design papers | docs/sbk.pdf, docs/sbp.pdf, docs/sbk-slc.pdf |
This repository works equally well for both styles of AI-assisted development:
For quick fixes, single-file edits, debugging:
AGENTS.md + the relevant
AGENT_RECIPES.md recipe../gradlew :drivers:<name>:check.Loop is small. No spec document. Suitable for: bugfixes, logging tweaks, small refactors, README updates.
For larger work (a new driver, a new feature in sbk-api, a new logger
backend):
(for drivers) or a similar markdown template.
AGENTS.md + AGENT_RECIPES.md.Loop is larger but produces auditable artefacts.
The spec template explicitly cross-references the recipes, so the agent has a single deterministic path from spec → working code.
The following actions require explicit user confirmation for every specific action (not blanket approval):
git push, git tag, or any operation that publishes to a
remote.LICENSE file.or in the
root build.gradle.
perl/, sbm/, etc.). New drivers under drivers/ are fine.halodb (see §4.1).For everything else inside drivers/, sbk-api/, perl/, sbm/,
docs/, and the build files, normal edit-and-verify flow is fine.
Before making any change, the agent should be able to answer these questions for the change at hand. If the agent can’t answer them, it should re-read this file and the relevant linked docs.
./gradlew :<module>:check command)?checkstyle/import-control.xml if I added a new
top-level dependency package?settings-drivers.gradle and build-drivers.gradle?§8 my
change must preserve (lock-free hot path, no sampling, no
synchronized blocks, etc.)?
When in doubt, prefer reading existing code over making assumptions. This codebase has more than 50 driver implementations; any specific pattern you need has almost certainly been done before.