BenchSSD is a macOS command-line benchmark tool for measuring storage performance on internal and external drives.
The Swift CLI is now the primary benchmarker. It supports sequential throughput tests, sustained write testing, random I/O testing, device inspection, JSON output, and profile-driven workflows intended for external SSD evaluation.
The Swift CLI currently supports:
- sequential write throughput
- sequential read throughput
- sustained sequential write throughput
- random read IOPS and latency
- random write IOPS and latency
- best-effort device and volume inspection
The benchmark is file-based and runs in user space. It does not write to raw devices.
This tool is designed to be credible and practical, not magical. Results may still be affected by:
- filesystem cache behavior
- APFS or ExFAT overhead
- USB enclosure and cable limits
- hub bandwidth sharing
- thermal throttling
- flash burst-cache exhaustion
- the limits of user-space benchmarking on macOS
The tool tries to make those limits explicit in its output instead of hiding them.
Build the Swift CLI with:
swift buildPrint the current version:
.build/debug/benchssd versionRun help:
.build/debug/benchssd helpInspect a target volume:
.build/debug/benchssd inspect --path /Volumes/MySSDRun the benchmark workflow:
.build/debug/benchssd benchmark --path /Volumes/MySSD --profile external-ssdRun sequential tests only:
.build/debug/benchssd seq --path /Volumes/MySSD --profile quickRun random I/O tests only:
.build/debug/benchssd rand --path /Volumes/MySSD --profile quickEmit JSON instead of text:
.build/debug/benchssd benchmark --path /Volumes/MySSD --profile full --jsonThe benchmark workflow uses three profiles.
Purpose:
- fast validation
Behavior:
- sequential benchmark only
- no sustained phase
- no random phase inside
benchmark
Best for:
- sanity checks
- quick manual verification
Purpose:
- realistic external SSD testing
Behavior:
- sequential benchmark
- sustained write phase
- no random phase inside
benchmark
Best for:
- checking whether an external SSD falls off after burst cache is exhausted
Purpose:
- deeper characterization
Behavior:
- sequential benchmark
- sustained write phase
- random I/O benchmark included inside
benchmark
Best for:
- more complete storage profiling
Explicit flags such as --test-size-gb, --passes, and --block-size still override the profile defaults.
Runs the profile-driven workflow. Depending on profile, this may include:
- sequential read and write
- sustained write
- random read and write
Runs sequential tests only. This is useful when you want faster iteration or to isolate large-block throughput from other workload types.
Runs random I/O tests only. By default it uses 4 KiB and 128 KiB blocks unless --block-size overrides that with a single size.
Prints mount, filesystem, capacity, and best-effort device metadata for the target path.
Text output includes:
- target path and mount point
- filesystem and capacity information
- chosen profile and configuration
- per-pass or per-slice results
- summary statistics
- warnings and caveats
JSON output exposes the same benchmark facts in a structured form for automation.
Text mode:
Benchmark workflow: benchmark
Phase: Phase 11 benchmark profiles
Profile
Name: quick
Summary: Fast validation profile with shorter sequential coverage and no sustained or random benchmark phases.
Benchmark command: benchmark
Phase: Phases 3-9 benchmark
Sequential write
pass 1: 2343.60 MiB/s, 0.437 s, durable
Sequential read
pass 1: 2138.14 MiB/s, 0.479 s, cold-ish
JSON mode:
{
"command": "benchmark",
"phase": "Phase 11 benchmark profiles",
"profile": {
"name": "full",
"randomEnabled": true,
"sequentialEnabled": true,
"sustainedEnabled": true
}
}Start with inspection:
.build/debug/benchssd inspect --path "/Volumes/MySSD"For a fast manual run:
.build/debug/benchssd benchmark --path "/Volumes/MySSD" --profile quick --passes 1 --test-size-gb 1For a more realistic external SSD run:
.build/debug/benchssd benchmark --path "/Volumes/MySSD" --profile external-ssd --passes 1 --test-size-gb 1If you want random I/O too:
.build/debug/benchssd rand --path "/Volumes/MySSD" --profile quick- The tool creates temporary files inside the target directory.
- It does not write to raw block devices.
- It reserves extra free space before starting.
- It uses a lock file to prevent overlapping runs on the same target path.
Ctrl-Cshould trigger cleanup, but it is still wise to check for leftover.benchssd-*files after interruption.- Sustained mode may stop before its full target duration if the profile safety cap is reached.
If a benchmark seems to run for a long time:
external-ssdandfullcan perform a large amount of I/O by default- slower USB links, hubs, enclosures, or filesystems can make the run much longer
- a safety cap may still allow several GiB of writes before the command finishes
If you want a much faster run, use:
.build/debug/benchssd benchmark --path "/Volumes/MySSD" --profile quick --passes 1 --test-size-gb 1If the tool reports a lock conflict, another benchmark is already using that target path. Wait for it to finish or stop it before starting a second run.
BenchSSD now uses tag-driven versioning.
Versioning rules:
- real releases come from Git tags such as
v0.2.0 - development builds derive their version from the latest matching tag, commit count, and short SHA
- example dev version:
0.2.0+25.gabc1234
The release workflow lives at release.yml.
On pushes to main or master, it:
- checks out the full Git history
- generates embedded version metadata from Git
- runs
swift test - builds the project
On pushes of tags matching v*, it also:
- builds a release binary with
swift build -c release - packages the binary as a zip archive
- publishes a GitHub Release for that tag
To prepare a release manually:
git tag v0.2.0
git push origin v0.2.0The CI workflow embeds the tag-derived version into the release binary using generate_version_swift.sh.