1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
# I/O Riot NG (aka ior)
<img src=assets/ior-small.png />
I/O Riot NG is an experiment with BPF. It traces synchronous I/O syscalls and analyses how long each one took. Useful for drawing FlameGraphs like these:
<img src=assets/screenshot-flames.png />
A spiritual successor to one of my previous projects, I/O Riot (https://codeberg.org/snonux/ioriot), which was based on SystemTap and C. The NG is based on Go, C, and BPF (via libbpfgo).
Linux only.
You can read a blog post series about this here: https://foo.zone/gemfeed/2026-05-08-unveiling-ior-ng-part-1.html
## Demo
A short guided tour with animated GIFs of every major surface lives in [`docs/tutorial/tutorial.md`](./docs/tutorial/tutorial.md). Two teasers:
**Startup, the PID picker:** `sudo ./ior` opens a searchable process list. Navigate with arrow keys, filter by typing, press `Enter` to start tracing. The dashboard appears right after.
<img src=docs/tutorial/assets/01-launch.gif width=720 alt="Cold start: PID picker, then the dashboard appears" />
**Live flamegraph tab:** Once tracing, tab `1` shows a live flamegraph that rebuilds in real time as I/O events arrive. Bars grow and shift with the workload. This is the default landing tab.
<img src=docs/tutorial/assets/13-tui-flamegraph.gif width=720 alt="Live in-TUI flamegraph rebuilding from real workload" />
The demo is fully reproducible: `mage installDemoTools` once, then `sudo -v && mage demo` regenerates every GIF and screenshot. See the [tutorial](./docs/tutorial/tutorial.md) for the full walkthrough.
> **Note:** `mage installDemoTools` uses `dnf` to install `ttyd` and is only supported on Fedora / RHEL / Rocky / Alma Linux. On other distros install `ttyd` manually (binary releases are on its GitHub page) and then `go install github.com/charmbracelet/vhs@latest` for VHS; `mage demo` will find them on `PATH`.
## Requirements
- Docker and a Linux host with a BTF-enabled kernel (`/sys/kernel/btf/vmlinux` present).
- Go (any 1.x version on `PATH`) for installing the [Mage](https://magefile.org) build tool.
## Install Mage
The build orchestration uses Mage. Install the `mage` binary once before any of
the build commands below:
```shell
go install github.com/magefile/mage@latest
```
Make sure `$(go env GOPATH)/bin` (typically `$HOME/go/bin`) is on your `PATH`.
## Build
Builds a fully static `ior` binary inside a Rocky Linux 9 container and writes
it to the repo root. No local Go, clang, or libbpfgo setup required:
```shell
mage buildDocker
```
First run takes ~15–20 minutes to build the image; subsequent runs reuse the
cached image and finish in under a minute. To skip the image rebuild:
```shell
./scripts/build-with-docker.sh --run
```
To target hosts with the older glibc on RHEL/Rocky/Alma 8, build a sibling
binary called `ior.el8` from a Rocky Linux 8 container:
```shell
mage buildDockerEl8
```
For contributors who need a native build (Fedora / Rocky Linux 9), see
[docs/build-rocky-linux-9.md](./docs/build-rocky-linux-9.md) and
[AGENTS.md](./AGENTS.md).
## Compile once, run everywhere
Build on one machine, then `scp ior other-host:/usr/local/bin/` and run it
anywhere. The binary is fully statically linked and uses libbpf CO-RE
(Compile-Once, Run-Everywhere) to adapt field offsets to the target kernel's
BTF at load time. No recompile per host or kernel version needed.
See [docs/build-rocky-linux-9.md](./docs/build-rocky-linux-9.md) for the full
explanation.
## TUI
Press **H** inside the dashboard to toggle the built-in help panel. Tabs are
reachable with **tab/shift+tab** or number keys **1–8** (including the Non-IO
tab). For the full hotkey
reference, recording modes, and the `.ior.zst` vs Parquet trade-off see the
[tutorial](./docs/tutorial/tutorial.md).
## Syscall Filtering
`ior` supports attach-time dimension filters for syscall family, kind, and
name, plus exclusion counterparts:
```shell
# Trace only Time + Polling families
sudo ./ior -trace-families Time,Polling
# Trace only fd/open kinds, but exclude a noisy syscall
sudo ./ior -trace-kinds fd,open -no-trace-syscalls read
# Trace explicit syscall names and exclude one kind globally
sudo ./ior -trace-syscalls openat,recvmsg,nanosleep -no-trace-kinds null
```
Discover valid values with:
```shell
./ior --help
```
## Bytes Classification
Bytes accounting is syscall-specific:
- `ReadClassified`: `fgetxattr`, `flistxattr`, `getdents`, `getdents64`,
`getrandom`, `getxattr`, `lgetxattr`, `listxattr`, `llistxattr`,
`mq_timedreceive`, `msgrcv`, `pread64`, `preadv`, `preadv2`,
`process_vm_readv`, `read`, `readlink`, `readlinkat`, `readv`, `recvfrom`,
`recvmsg`, `syslog`
- `WriteClassified`: `mq_timedsend`, `msgsnd`, `process_vm_writev`, `pwrite64`,
`pwritev`, `pwritev2`, `sendmsg`, `sendto`, `write`, `writev`
- `TransferClassified`: `copy_file_range`, `sendfile64`, `splice`, `tee`,
`vmsplice`
- Non-bytes: all remaining traced syscalls
For full coverage by family and TracepointKind, see
[docs/syscall-tracing-plan.md](./docs/syscall-tracing-plan.md).
|