Remove the old bash script and update README.md and CLAUDE.md
This commit is contained in:
@@ -20,7 +20,7 @@ It runs the same four tests CrystalDiskMark does, plus a dedicated **SLOG / sync
|
||||
|
||||
## Features
|
||||
|
||||
- **CrystalDiskMark‑style profile** — `SEQ1M Q8T1`, `SEQ1M Q1T1`, `RND4K Q32T1`, `RND4K Q32T16`, each measured for Read **and** Write, reported in both **MB/s** and **IOPS**.
|
||||
- **CrystalDiskMark‑style profile** — `SEQ1M Q8T1`, `SEQ1M Q1T1`, `RND4K Q32T16`, `RND4K Q1T1` (CrystalDiskMark's default profile), each measured for Read **and** Write, reported in both **MB/s** and **IOPS**.
|
||||
- **SLOG / sync‑write latency profile** (`--slog`) — synchronous 4K writes at T1/T4/T8/T16 reporting **IOPS, MB/s, and p50/p99 commit latency**. This is the load a ZFS SLOG actually sees.
|
||||
- **Auto‑detection** — picks the fastest available IO engine (`io_uring` → `libaio` → `posixaio` → `sync`) and probes whether `O_DIRECT` works on the filesystem, falling back to buffered IO when it doesn't (e.g. older OpenZFS, some NFS mounts).
|
||||
- **`dd` fallback** — if `fio` isn't present, runs a basic write/read test so you still get a number.
|
||||
@@ -30,9 +30,8 @@ It runs the same four tests CrystalDiskMark does, plus a dedicated **SLOG / sync
|
||||
|
||||
## Requirements
|
||||
|
||||
- `bash`
|
||||
- [`fio`](https://fio.readthedocs.io/) — recommended (`apt install fio` / `pacman -S fio`). Without it, the tool falls back to `dd`.
|
||||
- `python3` — **only** required for `--slog` (used to parse `fio`'s JSON output for latency percentiles). The normal profile needs only `fio` + `grep`/`awk`.
|
||||
- `python3` (3.7+) — the tool itself. It parses `fio`'s JSON output using only the standard library (no third‑party packages to install).
|
||||
- [`fio`](https://fio.readthedocs.io/) — recommended (`apt install fio` / `pacman -S fio`). Without it, the tool falls back to a basic `dd` test.
|
||||
- `sudo` — `fio` is invoked via `sudo` so it can use `O_DIRECT` and flush device caches.
|
||||
|
||||
---
|
||||
@@ -40,7 +39,8 @@ It runs the same four tests CrystalDiskMark does, plus a dedicated **SLOG / sync
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
./speedtest-hd.sh <path> [options]
|
||||
./speedtest-hd.py <path> [options]
|
||||
# or: python3 speedtest-hd.py <path> [options]
|
||||
```
|
||||
|
||||
`<path>` is the directory (or mount) to benchmark. Use `.` for the current directory. The tool creates a single test file (default 1 GiB) on the target and removes it afterward, so ensure enough free space.
|
||||
@@ -49,36 +49,44 @@ It runs the same four tests CrystalDiskMark does, plus a dedicated **SLOG / sync
|
||||
|
||||
| Invocation | What it does |
|
||||
|---|---|
|
||||
| `./speedtest-hd.sh /mnt/disk` | Auto: uses `fio` if installed, else `dd` |
|
||||
| `./speedtest-hd.sh /mnt/disk --fio` | Force the `fio` CrystalDiskMark‑style profile |
|
||||
| `./speedtest-hd.sh /mnt/disk --dd` | Force the basic `dd` test |
|
||||
| `./speedtest-hd.sh /mnt/disk --slog` | SLOG / sync‑write latency profile |
|
||||
| `./speedtest-hd.py /mnt/disk` | Auto: uses `fio` if installed, else `dd` |
|
||||
| `./speedtest-hd.py /mnt/disk --fio` | Force the `fio` CrystalDiskMark‑style profile |
|
||||
| `./speedtest-hd.py /mnt/disk --dd` | Force the basic `dd` test |
|
||||
| `./speedtest-hd.py /mnt/disk --slog` | SLOG / sync‑write latency profile |
|
||||
|
||||
`--fio`, `--dd`, and `--slog` are mutually exclusive.
|
||||
|
||||
### Tuning flags
|
||||
|
||||
| Flag | Effect |
|
||||
|---|---|
|
||||
| `--engine=io_uring\|libaio\|posixaio\|sync` | Force a specific IO engine (default: auto) |
|
||||
| `--engine {io_uring,libaio,posixaio,sync}` | Force a specific IO engine (default: auto) |
|
||||
| `--direct` | Force `O_DIRECT` (bypass page cache) |
|
||||
| `--buffered` | Force buffered IO (e.g. when `O_DIRECT` is unsupported) |
|
||||
| `--runtime=SEC` | Seconds per run (default: 5, like CrystalDiskMark) |
|
||||
| `--size=SIZE` | Test file size (default: `1g`) |
|
||||
| `--runtime SEC` | Seconds per run (default: 5, like CrystalDiskMark) |
|
||||
| `--size SIZE` | Test file size (default: `1g`) |
|
||||
| `--verbose` | Also print the full `fio` output for every run (summary table unchanged) |
|
||||
| `-y`, `--yes` | Skip the confirmation prompt (for scripting/automation) |
|
||||
|
||||
> All flags accept either `--flag value` or `--flag=value` (argparse). `--direct`/`--buffered` are mutually exclusive.
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
# CrystalDiskMark-style test of the current directory
|
||||
./speedtest-hd.sh .
|
||||
./speedtest-hd.py .
|
||||
|
||||
# Larger file, longer runs, on an NVMe pool
|
||||
./speedtest-hd.sh /mnt/nvmepool --runtime=10 --size=4g
|
||||
./speedtest-hd.py /mnt/nvmepool --runtime=10 --size=4g
|
||||
|
||||
# Buffered (e.g. an NFS share that doesn't support O_DIRECT)
|
||||
./speedtest-hd.sh /mnt/nfsshare --buffered
|
||||
./speedtest-hd.py /mnt/nfsshare --buffered
|
||||
|
||||
# SLOG / sync latency profile, 30s per run
|
||||
./speedtest-hd.sh /mnt/nvme-ultra-r10/vm-root --slog --runtime=30
|
||||
./speedtest-hd.py /mnt/nvme-ultra-r10/vm-root --slog --runtime=30
|
||||
|
||||
# Unattended (no prompt), forcing a specific engine
|
||||
./speedtest-hd.py /mnt/nvmepool --yes --engine io_uring
|
||||
```
|
||||
|
||||
> **Tip:** when running `--slog` against a ZFS dataset, watch the SLOG live in another shell:
|
||||
@@ -92,14 +100,16 @@ It runs the same four tests CrystalDiskMark does, plus a dedicated **SLOG / sync
|
||||
|
||||
### CrystalDiskMark‑style profile
|
||||
|
||||
Representative output from a healthy local NVMe (your numbers will differ):
|
||||
|
||||
```
|
||||
+------------------+----------------+----------------+----------------+----------------+
|
||||
| Test | Read (MB/s) | Write (MB/s) | Read (IOPS) | Write (IOPS) |
|
||||
+------------------+----------------+----------------+----------------+----------------+
|
||||
| SEQ1M Q8T1 | 6873.00 | 9.30 | 6873 | 9 |
|
||||
| SEQ1M Q1T1 | 1608.00 | 20.00 | 1608 | 20 |
|
||||
| RND4K Q32T1 | 538.00 | 10.80 | 137728 | 2764 |
|
||||
| RND4K Q32T16 | 689.00 | 261.00 | 176384 | 66816 |
|
||||
| SEQ1M Q8T1 | 3650.00 | 3120.00 | 3482 | 2976 |
|
||||
| SEQ1M Q1T1 | 2680.00 | 2510.00 | 2556 | 2394 |
|
||||
| RND4K Q32T16 | 2950.00 | 2240.00 | 720215 | 546875 |
|
||||
| RND4K Q1T1 | 78.00 | 64.00 | 19043 | 15625 |
|
||||
+------------------+----------------+----------------+----------------+----------------+
|
||||
```
|
||||
|
||||
@@ -126,8 +136,8 @@ It runs the same four tests CrystalDiskMark does, plus a dedicated **SLOG / sync
|
||||
|---|---|---|---|
|
||||
| `SEQ1M Q8T1` | Sequential 1 MiB | 8 | 1 |
|
||||
| `SEQ1M Q1T1` | Sequential 1 MiB | 1 | 1 |
|
||||
| `RND4K Q32T1` | Random 4 KiB | 32 | 1 |
|
||||
| `RND4K Q32T16` | Random 4 KiB | 32 | 16 |
|
||||
| `RND4K Q1T1` | Random 4 KiB | 1 | 1 |
|
||||
|
||||
`Q` = queue depth (`--iodepth`), `T` = threads (`--numjobs`). Note that `--iodepth` only produces real queue depth when the IO is truly asynchronous (async engine **and** `O_DIRECT`). On filesystems where that isn't available, queue depth effectively collapses toward 1 and concurrency comes only from threads (`T`).
|
||||
|
||||
@@ -174,6 +184,8 @@ The standard benchmark looked alarming — huge reads, tiny writes:
|
||||
|
||||
9.3 MB/s sequential write on an Optane‑backed NVMe pool looks broken.
|
||||
|
||||
> **Note:** this investigation was captured with an earlier test profile that used `RND4K Q32T1` in place of today's `RND4K Q1T1`. The `Q32T1` vs `Q32T16` comparison below is exactly why the default later changed — see finding #3.
|
||||
|
||||
### Investigation
|
||||
|
||||
**1. The reads are RAM, not disk.** With a 1 GiB test file on ZFS, reads come straight from ARC (RAM cache). The huge read/write asymmetry is the tell — ignore the read column for judging the disks.
|
||||
@@ -287,5 +299,5 @@ The residual gap from raw Optane (~15 µs) is ZFS ZIL‑commit overhead plus the
|
||||
- **`sudo`** is used for `fio` so it can apply `O_DIRECT` and flush device write caches at the end of write runs (`--end_fsync=1`), so cached writes can't inflate results.
|
||||
- **`O_DIRECT` is auto‑detected.** If the banner shows `O_DIRECT: DISABLED (buffered ...)`, results may reflect the page cache (RAM) rather than the device.
|
||||
- **A single shared test file** is reused across runs to keep the footprint to one file.
|
||||
- **`--slog` requires `python3`** (for JSON latency‑percentile parsing); the standard profile does not.
|
||||
- **All profiles parse `fio`'s JSON output** (`--output-format=json`) with Python's standard library — robust, unit‑safe metrics with no fragile text scraping.
|
||||
- The `--slog` profile forces synchronous IO and is intended for ZFS ZIL / SLOG and other sync‑write (NFS/iSCSI/VM) investigations.
|
||||
|
||||
Reference in New Issue
Block a user