See what a process is really doing with your files and your network.
iotap is a command-line tool for macOS and Linux. Give it a process ID or a name and it reports every read and write that process makes as it happens: the file or the remote address, how many bytes were asked for and how many moved, how long the call took, and whether it failed. When you stop, it sums the trace up per file and per endpoint.
Read top to bottom, this is the whole life of one curl:
- Sends a 517-byte request to 93.184.216.34 on port 443.
- Asks for 16 KiB back and gets 4 KiB.
- Asks again and finds nothing waiting yet:
EAGAIN. - Writes the 4 KiB to
page.html. - Writes 20 bytes of progress to the terminal, then exits.
The usual tools each show a piece
Something on your machine is busy and you want to know with what. A process is chewing through the disk, an app you just installed is talking to somewhere you never asked for, a build step is slow and you suspect I/O, or a program keeps failing and you want to see which file or connection it trips on.
iotap puts the pieces together: the bytes, the process they belong to, and the file or endpoint they went to.
| Tool | Shows | Leaves out |
|---|---|---|
| lsof, netstat | What is open right now | What moves through it |
| Activity Monitor, top | How many bytes | Where they went |
| A packet capture | The traffic of the whole machine | Which process it belongs to |
| strace, fs_usage | Each call a process makes | The adding up, per file and per endpoint |
| iotap | Each read and write of the processes you name, with bytes, latency and target, summed per file and endpoint | Memory-mapped I/O, and on Linux io_uring and libaio |
Choose what to watch
A target is a process ID or a name. A name matches every running process whose name, executable or first argument equals it, ignoring case, and iotap also traces processes started later under that name.
With -f, it also traces every process the traced ones start, those running when it begins and those started later.
sudo iotap 1234 # one process
sudo iotap Safari # every Safari process, and new ones
sudo iotap -f make # make and every process it starts
sudo iotap -i wlan0 firefox # only network I/O over wlan0
sudo iotap -q -d 10 Finder # summary only, after 10 seconds
One trace, four ways to read it
The event stream is what you saw above. When tracing ends, iotap sums it up. For a process that runs longer, the terminal UI shows the same tables live, JSON Lines feed jq or a log, and a recording lets you replay the trace later, in any of these forms, without root.
Summary
When you press Ctrl-C or the process exits, iotap sums the trace up per file and per endpoint. Here the network traffic went over en0, the interface that holds 192.168.1.20.
Terminal UI
--tui shows throughput for the last complete second and in total, then three tabs: files, network endpoints, and the latest 10,000 events. This is the Files tab after replaying a recorded download.
- 1 2 3 switch tabs
- s sort by bytes, read, write, calls or recency
- Enter details of the selected row
- p pause the view; tracing goes on
- y copy the selected path or address
- i traffic of each network interface
- q quit and print the summary
JSON Lines
--json writes every event as one line, with times in nanoseconds since the Unix epoch and the target as a small object. Pipe it to jq, or keep it as a log. This is one event, spread over several lines.
sudo iotap --json curl | jq -c 'select(.type == "event")'
Recordings
--record FILE saves the trace, and --replay FILE feeds it through the same processing, so a replay reproduces the output of the live run in any output mode. A recording holds paths and addresses but no transferred data, and replays on the operating system it was made on.
sudo iotap --net-only --record t.iotaprec 1234
iotap --replay t.iotaprec # the same output again, without root
It reads what the kernel already records
Every read, write, open and connect a program makes is a system call, and the kernel can be told to log each one as it happens, with the process it came from, its arguments and its result. iotap reads that log.
It attaches no debugger, injects no code and changes nothing in the program it watches.
- On macOS
- The log is the kernel trace facility, kdebug, the same source Apple's
fs_usagereads. It is built into the kernel, so nothing has to be installed. - On Linux
- iotap loads a small eBPF program of its own onto the kernel's syscall tracepoints. The kernel verifies it before running it and unloads it when iotap exits.
- Why it needs sudo
- Both are a view into every process on the machine, so the kernel gives them only to root. Without it, iotap says so and stops before it touches anything. Replaying a recording needs no root.
- Metadata only
- iotap records the call, the descriptor, byte counts, latency, the path and the socket endpoint. It never reads or stores the data being transferred: a process reading your SSH key shows up as a read of that file and its size, never its contents.
- Only what you name
- The kernel is asked to record the calls of the processes you name and, with
-f, of the processes they start. No others. - Nothing left behind
- On macOS only one program can own kdebug at a time, so iotap releases it on every exit path, including errors, signals and panics. On Linux the kernel drops the eBPF program the moment iotap exits. The terminal UI restores the terminal the same way.
- One binary, no daemon
- iotap runs only while you run it and makes no connections of its own. The optional host name lookups (
--resolve) go through the system's resolver. - What it does not see
- Memory-mapped file I/O and I/O the kernel does on a process's behalf, such as page-cache writeback. On Linux also I/O submitted through io_uring or libaio. The Limitations section of the README lists the rest.
Build it, then run it with sudo
iotap builds from source with Cargo. It runs on macOS, and on Linux 5.8 or later on 64-bit Arm or x86-64 with BPF and syscall tracepoints, as distribution kernels have them.
To build you need Rust 1.98.1, which rust-toolchain.toml selects. On macOS, add the Xcode Command Line Tools. On Linux, add clang, make, pkg-config, a C compiler, and the libelf and zlib development files.
On Debian and Ubuntu, install what the Linux build needs first:
sudo apt install build-essential clang pkg-config \
libelf-dev zlib1g-dev
Then build it and trace a process:
git clone https://github.com/rath/iotap.git
cd iotap
cargo build --release
sudo ./target/release/iotap curl