A CrystalDiskMark-style disk benchmark for the terminal.
One static binary — fio embedded as the
measurement engine, a btop-style TUI on top.
- One binary, no dependencies — fio is statically linked in; nothing is extracted or installed at runtime.
- CrystalDiskMark semantics — same default workloads (the CDM 9 "NVMe SSD" preset), same scoring (best of N runs), decimal MB/s. Each task measures read then write before moving to the next.
- Live TUI — big numbers over proportional bars, updating in real time.
Kflips between MB/s and IOPS (the IOPS view also shows mean latency per cell),Qquits. - Scriptable —
--jsonemits one NDJSON object per completed workload; non-TTY output degrades to plain lines for CI logs.
Installs into ~/.local/bin (works in Git Bash and MSYS2 on Windows too):
curl -fsSL https://iomark.dev | shInstalls into %LOCALAPPDATA%\Programs\iomark and adds it to your user PATH:
irm https://iomark.dev/install.ps1 | iexquick downloads to a temp directory, runs the benchmark and deletes the
binary again:
curl -fsSL https://iomark.dev | sh -s -- quick # current directory
curl -fsSL https://iomark.dev | sh -s -- quick /mnt/data # a specific disk& ([scriptblock]::Create((irm https://iomark.dev/install.ps1))) quick D:The installer verifies the download against the release's SHA256SUMS.
--version <tag> pins a release, --dir <path> picks the install directory,
and uninstall removes the binary again. Prebuilt archives for every platform
are also on the releases page.
iomark # benchmark the current directory's disk
iomark /path/to/mount # benchmark a specific disk
iomark --json > out.ndjson # machine-readable results| Option | Description | Default |
|---|---|---|
<TARGET_DIR> |
Directory to benchmark (the test file is created there) | . |
--tasks <LIST> |
Comma-separated workloads: (SEQ|RND)<block>Q<depth>T<threads> |
CDM 9 preset |
--type <UNIT> |
Initial display unit, MB/s or IOPS (K toggles in the TUI) |
MB/s |
--runs <N> |
Measured runs per operation, best kept | 3 |
--size <SIZE> |
Test file size, binary units | 1GiB |
--duration <TIME> |
Duration of each measured run | 5s |
--warmup <TIME> |
Unmeasured warmup per operation (0s disables) |
5s |
--interval <TIME> |
Pause between operations (0s disables) |
5s |
--color <MODE> |
auto, truecolor, 256, ansi or never |
auto |
--json |
NDJSON output, one object per workload | off |
-V, --version |
Print version (iomark + embedded fio) |
The default task list is CrystalDiskMark 9's "NVMe SSD" preset,
SEQ1MQ8T1,SEQ128KQ32T1,RND4KQ32T16,RND4KQ1T1. Custom workloads follow the
same grammar: RND8KQ4T4 is random 8 KiB blocks at queue depth 4 with 4
threads.
git clone --recurse-submodules https://github.com/feeeei/iomark
cd iomark
cargo build --releaseRequirements: Rust 1.89+, make, a C compiler, sh.
| Platform | Notes |
|---|---|
| Linux (x86_64 / ARM64) | libaio-dev and zlib1g-dev recommended |
| macOS (Apple Silicon / Intel) | works out of the box |
| Windows x86_64 | build inside an MSYS2 MINGW64 shell with the x86_64-pc-windows-gnu toolchain |
| Windows ARM64 | experimental: MSYS2 CLANGARM64 + aarch64-pc-windows-gnullvm |
A native Linux build links your distro's glibc and libaio, so it only runs on systems at least as new. The released Linux binaries are fully static musl builds instead, made inside Alpine — reproduce one with Docker:
docker run --rm -v "$PWD:/src" -w /src rust:1-alpine .github/build-musl.sh
# -> target/<arch>-unknown-linux-musl/release/iomarkTasks run in order; each task measures read first, then write (CDM instead
runs every read before any write — a deliberate difference). Each operation
runs one unscored warmup pass, then N scored runs of fixed duration
(--time_based); the best bandwidth and the lowest mean latency are
reported, exactly like CrystalDiskMark. The shared test file is written full
of data up front (no sparse-file shortcuts) and deleted on exit — including
on Ctrl-C.
Per-platform fio engines: libaio (Linux), posixaio (macOS), windowsaio
(Windows), always with direct=1 — with one exception: tmpfs (/dev/shm)
refuses O_DIRECT before Linux 6.6, so there iomark runs buffered and says
so. tmpfs lives in RAM, so there is no disk to bypass the cache for; newer
kernels accept O_DIRECT on tmpfs and do buffered I/O anyway. Any other
filesystem that refuses O_DIRECT is rejected up front, since buffered
results would measure RAM rather than the disk.
- MB/s is decimal (bytes ÷ 10⁶), matching CDM. IOPS and latency come from fio's job statistics.
- On macOS there is no
O_DIRECT; fio'sdirect=1maps toF_NOCACHE, which is a weaker cache bypass. macOS numbers are comparable to other macOS tools (AmorphousDiskMark), not bit-for-bit to Linux/Windows. - macOS also caps in-flight POSIX AIO per process (
kern.aioprocmax, default 16), so Q32 workloads effectively run at a shallower queue depth there; iomark prints a warning when a task exceeds the limit. The caps can be raised until reboot (sudo sysctl kern.aioprocmax=256 kern.aiomax=1024), but gains are modest: the kernel services POSIX AIO through a small worker-thread pool (kern.aiothreads, default 4). - CDM drives Windows' DiskSpd; iomark drives fio everywhere. Numbers on the same hardware land close to CDM's but are not guaranteed identical.
GPL-2.0-only — iomark statically links fio, which is GPL-2.0. See LICENSE.