SBK is a Java framework for measuring the throughput and latency of storage systems with one common workload engine. Its drivers cover object stores, message systems, databases, file systems, caches, and local queues. The harness controls concurrency, duration, rate, payloads, timestamps, and reporting; each driver only adapts those operations to a backend.
Repository: https://github.com/kmgowda/SBK
Choose the guide that matches your task:
| Goal | Documentation |
|---|---|
| Build and run SBK | This README |
| Understand modules and runtime flow | Architecture and code flow |
| Browse all documentation | Documentation index |
| View live graphs without Docker | WebLogger guide |
| Retain live metrics with Prometheus and Grafana | PrometheusLogger and SBK Dashboard guide |
| Add or modify a storage driver | Driver guide |
| Make a code contribution | Contributing guide |
| Follow a task-specific procedure | Engineering recipes |
| Work as a coding agent | Agent guide |
| Study measurement internals in depth | Internal design |
flowchart LR
CLI[SBK CLI or YML] --> BOOT[Sbk bootstrap]
BOOT --> DRIVER[Storage driver]
BOOT --> BENCH[SbkBenchmark]
BENCH --> WORKERS[Writer and reader workers]
WORKERS --> DRIVER
WORKERS --> CHANNEL[PerL channels]
CHANNEL --> RECORDER[Latency recorder]
RECORDER --> OUTPUT[Console / CSV / Local Web Console / Prometheus / gRPC]
OUTPUT -->|gRPC mode| SBM[SBM aggregator]
GEM[SBK-GEM] -->|SSH orchestration| CLI
GEM --> SBM
The main runtime path is:
io.sbk.main.SbkMaindelegates toio.sbk.api.impl.Sbk.Sbkdiscovers the requested driver and logger by class name, builds the combined CLI, parses it, and constructsSbkBenchmark.SbkBenchmarkopens the storage, creates driver readers and writers, starts PerL recorders, and schedules worker execution.- Driver operations are timed by the
WriterandReaderdefault methods and submitted to aPerlChannel. - PerL processes every latency record away from the worker threads and publishes periodic and total results through the selected logger.
See docs/ARCHITECTURE.md for source links, lifecycle details, concurrency boundaries, and distributed execution.
- JDK 25, either preinstalled or downloaded automatically by the wrapper
- Git
- The Gradle wrapper included in the repository; a separate Gradle installation is not required
- A real backend only when exercising a remote-storage driver
Confirm the active JVM before building:
java -version
./gradlew --versionThe Gradle wrappers resolve a complete JDK 25 in this order:
SBK_JAVA_HOME;JAVA_HOME;- a JDK 25 available through
PATH; and - the SBK-managed JDK cache.
If none is available, gradlew downloads the pinned OpenJDK 25 release,
verifies its SHA-256 checksum, and installs it without administrator access in
${XDG_CACHE_HOME:-$HOME/.cache}/sbk/jdks on Unix or
%LOCALAPPDATA%\SBK\jdks on Windows. Later wrapper invocations reuse that
installation. Set SBK_JAVA_CACHE_DIR to select a different cache directory.
Automatic installation supports Linux x64/AArch64, macOS x64/AArch64, and
Windows x64. Other platforms can use a manually installed JDK through
SBK_JAVA_HOME.
The pinned version, platform checksums, and bootstrap network/lock timeouts
have one authoritative source:
gradle/sbk-java-bootstrap.properties.
An explicitly configured but invalid SBK_JAVA_HOME or JAVA_HOME is treated
as an error so a configuration mistake is never hidden by an automatic
download.
Generated launchers for sbk, sbk-yal, sbm, sbk-gem, sbk-gem-yal, and sbk-web-console
use the same selection order and managed cache. They do not download Java at
application startup; run the Gradle wrapper once to populate the cache, or set
SBK_JAVA_HOME/JAVA_HOME. The standalone ./install-java command uses the
same resolver when an explicit installation step is preferred.
Every generated SBK launcher uses one consolidated JDK 25 runtime profile from
gradle/java.gradle:
-XX:+UseZGC
-XX:+UseCompactObjectHeaders
-XX:MaxRAMPercentage=50.0
-XX:+DisableExplicitGC
-XX:+ExitOnOutOfMemoryError
- Generational ZGC performs expensive collection work concurrently and dynamically scales generation sizes and GC threads. This keeps GC pauses small as benchmark concurrency, CPU count, and live heap grow.
- Compact object headers reduce per-object heap overhead and improve cache density for queues and measurement records.
- 50% maximum RAM gives large benchmark processes more heap than the JVM's default server sizing while retaining half of detected memory for SDK native buffers, direct buffers, thread stacks, filesystem cache, and the operating system.
- Disabled explicit GC prevents a driver or dependency from injecting a
benchmark-distorting
System.gc()collection. Necessary collections still run normally. - Exit on OOM prevents a memory-exhausted benchmark from continuing and publishing misleading results.
The same options are applied to sbk, sbk-yal, sbm, sbk-gem,
sbk-gem-yal, module launchers, and the SbkWebConsoleMain process.
ZGC reserves address space using many memory mappings. On large-memory Linux
hosts, check sysctl vm.max_map_count; if it is too small, JDK 25 prints the
minimum required value at startup. Configure that operating-system limit
before a production benchmark.
These are safe cross-host defaults, not a substitute for capacity planning.
For a dedicated benchmark host, JAVA_OPTS can override heap sizing. For
example, a fixed heap removes resizing and commit/uncommit variation:
export JAVA_OPTS='-Xms32g -Xmx32g -XX:+AlwaysPreTouch'Use fixed pre-touched heaps only when that memory is reserved for the process. Large pages are not enabled automatically because they require operating-system provisioning. To compare G1 with ZGC, explicitly disable ZGC before selecting G1:
export JAVA_OPTS='-XX:-UseZGC -XX:+UseG1GC'Clone and build the project:
git clone https://github.com/kmgowda/SBK.git
cd SBK
./gradlew check
./gradlew installDistThe installed launcher is created at:
build/install/sbk/bin/sbk
Useful development commands:
# Compile, check style, and run tests for the whole build
./gradlew check
# Iterate on one driver
./gradlew :drivers:minio:check
# Generate launch scripts and runtime libraries
./gradlew installDist
# Rebuild the pathing JAR after dependency changes
./gradlew clean :pathingJar installDist --rerun-tasksHaloDB and Ignite are present in the source tree but are not enabled in the aggregate build. HaloDB depends on a GitHub Packages artifact that may require credentials. The sbktemplate directory is a scaffold, not a runtime driver.
List drivers and common options:
./build/install/sbk/bin/sbk -helpWrite to a local file for 30 seconds:
./build/install/sbk/bin/sbk \
-class file \
-file /tmp/sbk.bin \
-writers 1 \
-size 1048576 \
-seconds 30Read the same file:
./build/install/sbk/bin/sbk \
-class file \
-file /tmp/sbk.bin \
-readers 1 \
-size 1048576 \
-seconds 30Driver-specific options are added after SBK discovers -class. Use the selected driver with -help to see the merged option set:
./build/install/sbk/bin/sbk -class minio -help| Option | Meaning |
|---|---|
-class NAME |
Driver simple class name, matched case-insensitively |
-writers N |
Number of writer workers |
-readers N |
Number of reader workers |
-size BYTES |
Payload size per record |
-seconds N |
Time-based run duration |
-records N |
Total records in count mode, or the per-second target in timed mode |
-throughput MBPS |
Throughput target; -1 requests maximum throughput |
-mpscqueue true|false |
Select intrusive TimeStampMpscQueue or the JDK ConcurrentLinkedQueue fallback; default comes from sbk.properties |
-sync N |
Records per flush/sync or transaction |
-ro true |
With readers and writers configured, read without writing new records |
-thread p|f|v |
Platform, fork-join, or virtual worker executor; default: virtual (v) |
-out NAME |
Output logger, such as SystemLogger, CSVLogger, WebLogger, PrometheusLogger, or GrpcLogger |
Always treat -help as authoritative because drivers and loggers add their own options at runtime.
| Path | Responsibility |
|---|---|
perl/ |
Performance Logger library: queues, latency windows, histograms, percentiles, and metrics |
sbk-web-console/ |
Independent Local Web Console server/client runtime, protocol DTOs, and browser resources |
sbk-api/ |
Storage and logger SPIs, CLI parsing, payload types, workers, and benchmark lifecycle |
drivers/<name>/ |
Backend-specific adapters |
sbm/ |
gRPC service that aggregates measurements from SBK clients |
sbk-gem/ |
SSH-based multi-host launcher that embeds SBM |
sbk-yal/ |
YML-to-SBK argument adapter |
sbk-gem-yal/ |
YML-to-SBK-GEM argument adapter |
The dependency direction is perl <- sbk-api <- drivers and sbk-web-console <- sbk-api. SBM depends on
sbk-api; SBK-GEM depends on SBM. The YML launchers wrap their corresponding programmatic APIs.
Enabled drivers are registered in both settings-drivers.gradle and build-drivers.gradle. The complete categorized inventory and the driver contract are in docs/DRIVER_GUIDE.md.
Use the synthetic PerlBench driver to compare
the intrusive PerL timestamp queue with the JDK fallback under exact-count,
timed-saturation, and rate-controlled workloads. It performs no storage I/O,
so its results describe harness and measurement-pipeline behavior rather than
a storage device. Do not confuse it with the Null driver:
Null's default operation deliberately remains pending for idle, timeout, and
shutdown tests, while PerlBench completes every operation immediately.
SBK currently ships these logger implementations:
SystemLogger: human-readable periodic and final output.Sl4jLogger: SLF4J-backed output.CSVLogger: results written in CSV form.WebLogger: console/CSV output plus the dependency-free SBK Local Web Console.PrometheusLogger: CSV behavior plus Prometheus metrics exposure.GrpcLogger: forwards measurements to SBM for distributed aggregation.
Prometheus metrics include stable component, class, and action labels.
component="sbk" identifies a direct SBK exporter, while component="sbm"
identifies SBM aggregation, including benchmarks orchestrated by SBK-GEM.
SBK-GEM is not a separate metrics component because SBM owns its metrics
endpoint and aggregated measurements.
Use PrometheusLogger when Prometheus should retain periodic results and Grafana should display a full historical
dashboard:
./build/install/sbk/bin/sbk \
-class file -file /tmp/sbk-prometheus.dat \
-writers 1 -size 4096 -seconds 60 \
-out PrometheusLoggerDirect SBK metrics use http://<sbk-host>:9718/metrics by default. SBM and SBK-GEM aggregate metrics use
http://<coordinator-host>:9719/metrics. Startup prints copy-paste scrape URLs for localhost, loopback, hostname,
and usable host IPv4 addresses. Change the exporter with -context PORT/PATH, for example
-context 19718/sbk-metrics.
The separately deployed SBK Dashboard manages official Prometheus and
Grafana processes, persistent time-series history, and one endpoint-scoped dashboard per registered host:port.
Its management UI defaults to port 9721 and Grafana to port 3000. See the
PrometheusLogger and SBK Dashboard guide for direct and distributed commands, Docker
Compose and native installation, endpoint registration, metrics semantics, retention, security, and troubleshooting.
Use WebLogger when you want live graphs without Docker, Prometheus, or Grafana:
./build/install/sbk/bin/sbk -class file -file /tmp/sbk.bin \
-writers 4 -size 4096 -seconds 60 -out WebLoggerFor a filesystem read benchmark, first create an input file and then read it with WebLogger. The record size must
match between preparation and reading:
# Prepare a 1 GiB file: 1,048,576 records x 1,024 bytes.
./build/install/sbk/bin/sbk \
-class file -file /tmp/sbk-weblogger.dat \
-writers 1 -size 1024 -records 1048576
# Measure filesystem reads for 60 seconds and display live graphs.
./build/install/sbk/bin/sbk \
-class file -file /tmp/sbk-weblogger.dat \
-readers 1 -size 1024 -seconds 60 \
-out WebLoggerSBK opens the run URL in the default browser. The lightweight Local Web Console accepts plain HTTP on all IPv4
interfaces at port 9720, retains the latest 180 minutes of snapshots in memory, and streams new summaries with
server-sent events. At benchmark start and completion, SBK prints run-specific URLs for localhost, IPv4 loopback,
the hostname, and every usable private or public IPv4 address discovered on the console host. Change the retention
duration with -websnapshotminutes N.
A later SBK process reuses a compatible server already on that port. Multiple SBK, SBM, and SBK-GEM WebLogger
benchmarks can publish concurrently to the same server; each receives a unique run URL and remains independently
selectable in the browser. Completed graphs remain available while a browser is connected; after all benchmarks
have finished and no browser has been connected for one minute by default, the server exits automatically.
WebLogger reports whether it starts a new console or uses the existing process. Automatically started consoles append
lifecycle, WebLogger count, browser/client count, and exit diagnostics to
$HOME/.sbk/logs/sbk-web-console-<port>.log.
Change the idle grace period with -webtimeoutminutes N. Snapshots and 15-second logger heartbeats
renew the active-run lease. If a benchmark is killed without completing, the configured idle timeout
without either signal marks only that run abandoned; other active runs continue unaffected. Use -webopen false on
headless hosts and -webport PORT to select another port. Boards default to <application> <storage>, such as
SBK File, SBM MinIO, or SBK-GEM Kafka; use -boardname NAME to override that display name. The timeout supplied by
the process that starts a web console remains in effect when later benchmarks reuse it. A remote browser can use a
printed hostname or IP URL when routing and firewall rules permit. Because the console has no authentication or TLS,
expose it only on a trusted benchmark network or use an SSH tunnel.
Run sbk -out WebLogger -help for the complete option set.
See the WebLogger guide for every option, Local Web Console lifecycle, distributed usage, security,
and troubleshooting.
Distributed monitoring uses the same Local Web Console and data model:
# Standalone SBM aggregate Local Web Console
sbm -out SbmWebLogger -class file -action r
# SBK-GEM aggregate Local Web Console; remote nodes continue sending results through GrpcLogger
sbk-gem -out GemWebLogger -class file -nodes host1,host2 -writers 2 -size 4096 -seconds 60The Local Web Console binds to 0.0.0.0, while benchmark clients discover and reuse it through 127.0.0.1.
- SBM accepts SBP/gRPC measurements and aggregates them.
- SBK-GEM copies and launches SBK on remote hosts over SSH while running an embedded SBM instance.
- SBK-YAL loads single-node SBK arguments from YML.
- SBK-GEM-YAL loads distributed SBK-GEM arguments from YML.
The default SBM gRPC port is 9717; Prometheus/JMX endpoints use separate configured ports. Review the component help before exposing any port outside a trusted benchmark network.
SBK records operation latency without sampling. Worker threads submit (start, end, records, bytes) measurements through PerL channels to concurrent queues. Dedicated recorder logic calculates window and total statistics and invokes the logger. This keeps histogram work out of the driver operation path.
Results are meaningful only when the experiment is controlled. Record at least:
- SBK version and commit SHA.
- Driver and vendor-client versions.
- Full command line and non-default properties.
- JVM, CPU, memory, network, and operating-system details.
- Storage topology and durability settings.
- Warm-up policy and whether results are cold-cache or warm-cache.
See the reproducibility section for a longer checklist.
Before creating a release candidate, run the one-command automated release
gate documented in SBK Release Qualification.
Use its local-docker profile for automatic two-node SBK-GEM and
SBK-GEM-YAL functional testing without a permanent SSH host. The release
profile verifies installed launchers, logger contracts, PerL
concurrency and performance, YAL mapping, SBM/gRPC aggregation, remote
SBK-GEM execution, generated documentation, publications, and archive
checksums.
Before publishing, run the non-mutating publication assembly locally:
./gradlew clean releasePublicationDryRun \
--no-daemon --rerun-tasksThe separate guarded release publication workflow
does not execute or depend on releasecheck. It builds the current version's
reproducible archives, validates native AMD64/ARM64 containers, and can publish
the distributions, SBOMs, manifest, and checksums to a detailed GitHub
Release, the Java modules to GitHub Packages, and the signed
multi-architecture GHCR image plus the digest-identical Docker Hub image. The
root publish task uses
local Docker Hub environment credentials without sending them to GitHub, then
dispatches the workflow with only the immutable public image digest. See the
publication guide for the required confirmation, credentials, and recovery
controls.
For authorized maintainers, the final publication entry point is:
VERSION=$(sed -n 's/^sbkVersion=//p' gradle.properties)
DOCKER_USERNAME="<docker-user>" \
DOCKER_PASSWORD="<docker-access-token>" \
GITHUB_TOKEN="<actions-workflow-token>" \
./gradlew publish "-PreleaseConfirm=RELEASE-${VERSION}" --no-daemonRun it only from a clean, synchronized master after separately qualifying
the same commit. The command publishes Docker Hub and dispatches the
asynchronous GitHub workflow; monitor that workflow and complete the
post-release checks in the publication guide.
Read CONTRIBUTING.md before changing code. The minimum verification sequence is normally:
./gradlew :<module>:check
./gradlew check
./gradlew installDistDriver changes also require a smoke test against the relevant backend. Pull requests target master. Do not re-enable HaloDB or upgrade the intentionally pinned MinIO SDK without discussing the compatibility implications.
SBK is licensed under the Apache License 2.0. Use GitHub Issues for bugs and feature requests and GitHub Discussions for usage and design questions.