iotap

そのプロセス、ファイルとネットワークで本当は何をしているのか。

macOS と Linux でプロセスのファイル・ネットワーク I/O を追跡。パス、アドレス、バイト数、処理時間をリアルタイムで確認できます。

brew install rath/tap/iotap

macOS(Apple Silicon)· Linux(ARM64 / x86_64)

sudo iotap curl
iotap: tracing 4242 (curl); press Ctrl-C to stop
TIME PID OP FD REQUESTED RESULT LATENCY TARGET
14:13:20.004541 4242 sendto 5 517 517 0.041 ms tcp 192.168.1.20:61000 -> 93.184.216.34:443
14:13:20.104625 4242 recvfrom 5 16384 4096 0.041 ms tcp 192.168.1.20:61000 -> 93.184.216.34:443
14:13:20.104708 4242 recvfrom 5 16384 EAGAIN 0.041 ms tcp 192.168.1.20:61000 -> 93.184.216.34:443
14:13:20.104791 4242 write 4 4096 4096 0.041 ms /Users/me/page.html
14:13:20.104875 4242 write 1 20 20 0.041 ms /dev/ttys004
iotap: 4242 (curl) exited

iotap は macOS と Linux で使えるコマンドラインツールです。プロセス ID か名前を渡すと、そのプロセスの読み書きをひとつ残らず、リアルタイムで表示します。どのファイルやリモートアドレスか、何バイトを要求して実際に何バイト行き来したか、呼び出しにどれだけかかったか、失敗したかどうかまで。止めると、ファイルごと・エンドポイントごとに集計します。

上から順に読めば、これが curl 一回の一部始終です:

  1. 93.184.216.34 のポート 443 に 517 バイトのリクエストを送る。
  2. 16 KiB を要求して、4 KiB を受け取る。
  3. もう一度要求するが、まだ何も届いていない:EAGAIN。
  4. その 4 KiB を page.html に書く。
  5. 進捗 20 バイトを端末に書いて、終了。

よくあるツールは、それぞれ一部分しか見せてくれない

マシンの何かが忙しそうで、何のせいか知りたい。あるプロセスがディスクを読み書きし続けている、入れたばかりのアプリが頼んでもいないどこかと通信している、ビルドのある工程が遅くて I/O が怪しい、あるいはプログラムが何度もエラーになるので、どのファイルや接続でつまずいているのか見たい。そんなときがあります。

iotap はその断片をひとつにまとめます。行き来したバイト、そのバイトの持ち主であるプロセス、そして行き先のファイルやエンドポイントを。

各ツールが見せるものと、見せないもの
ツール見せるもの見せないもの
lsof, netstatいま開いているものそこを通っているもの
アクティビティモニタ, topバイト数その行き先
パケットキャプチャマシン全体のトラフィックどのプロセスのものか
strace, fs_usageプロセスの呼び出し一つひとつファイルごと・エンドポイントごとの集計
iotap指定したプロセスの読み書き一つひとつを、バイト数、レイテンシ、対象とともに。さらにファイルごと・エンドポイントごとの集計メモリマップド I/O。Linux では io_uring と libaio も

何を見るかを選ぶ

対象はプロセス ID か名前です。名前を渡すと、プロセス名、実行ファイル名、最初の引数のいずれかが(大文字小文字を区別せず)一致する実行中のプロセスをすべて対象にし、あとからその名前で起動したプロセスも続けて追跡します。

-f を付けると、追跡中のプロセスが起動したプロセスも追跡します。開始時にすでに動いていたものも、あとから起動したものも。

sudo iotap 1234                # プロセスを 1 つ
sudo iotap Safari              # Safari のプロセスすべて、新しいものも
sudo iotap -f make             # make と、make が起動するすべてのプロセス
sudo iotap -i wlan0 firefox    # wlan0 を通るネットワーク I/O だけ
sudo iotap -q -d 10 Finder     # 10 秒後に集計だけ

ひとつのトレース、四つの読み方

イベントストリームは上で見たとおりです。追跡が終わると iotap が集計します。長く動くプロセスなら、ターミナル UI で同じ表をリアルタイムに見られます。JSON Lines にすれば jq に流したりログとして残したりでき、記録しておけば、あとから root なしで、どの形式でも再生できます。

集計

Ctrl-C を押すか、プロセスが終了すると、iotap がファイルごと・エンドポイントごとに集計します。この例では、ネットワークトラフィックは 192.168.1.20 というアドレスを持つインターフェース en0 を通っています。

iotap summary: 4242 (curl) traced for 0.1 s
Files (2 targets)
READ CALLS WRITTEN CALLS FAILED TARGET
0 B 0 4.0 KiB 1 0 /Users/me/page.html
0 B 0 20 B 1 0 /dev/ttys004
Network (1 target)
RECEIVED CALLS SENT CALLS FAILED TARGET
4.0 KiB 2 517 B 1 1 tcp 93.184.216.34:443
Totals
files read 0 B (0 calls), written 4.0 KiB (2 calls)
network received 4.0 KiB (2 calls), sent 517 B (1 call)
en0 received 4.0 KiB (2 calls), sent 517 B (1 call)
5 calls, 1 failed

ターミナル UI

--tui は直近 1 秒と累計のスループットを上に表示し、その下にファイル、ネットワークエンドポイント、直近 10,000 件のイベントの三つのタブを並べます。下は、記録したダウンロードを再生したあとの Files タブです。

iotap 4242 curl (exited) 0:00:12
FILE READ FILE WRITTEN NET RECEIVED NET SENT
per second 0 B 1.2 MiB 1.2 MiB 0 B
total 0 B 14.1 MiB 14.1 MiB 517 B
End of the recording. Press q for the summary.
1 Files (2) 2 Network (1) 3 Events (7,205) sort: bytes
READ CALLS WRITTEN CALLS FAILED IDLE TARGET
0 B 0 14.1 MiB 3601 0 <1s /Users/me/big.iso
0 B 0 20 B 1 0 <1s /dev/ttys004
4242 (curl) exited q quit s sort p pause i interfaces r reset ↑↓ select enter details
  • 1 2 3 タブを切り替え
  • s バイト数、読み、書き、呼び出し回数、新しい順で並べ替え
  • Enter 選択した行の詳細
  • p 表示だけ止める。追跡は続く
  • y 選択したパスやアドレスをコピー
  • i ネットワークインターフェースごとのトラフィック
  • q 終了して集計を表示

JSON Lines

--json はイベントを 1 行ずつ書き出します。時刻は Unix エポック基準のナノ秒、対象は小さなオブジェクトです。jq にパイプするか、そのままログとして残せます。下は 1 件のイベントを複数行に展開したものです。

{
"type": "event",
"time_ns": 1790259200104708333,
"pid": 4242,
"tid": 2,
"op": "recvfrom",
"dir": "read",
"syscall": "recvfrom",
"fd": 5,
"requested": 16384,
"bytes": null,
"messages": null,
"errno": 35,
"error": "EAGAIN",
"latency_ns": 41666,
"target": {
"kind": "socket",
"proto": "tcp",
"local": "192.168.1.20:61000",
"remote": "93.184.216.34:443"
},
"interface": "en0",
"resolved": "traced"
}
sudo iotap --json curl | jq -c 'select(.type == "event")'

記録

--record FILE でトレースをファイルに保存し、--replay FILE でそのファイルを同じ処理に通します。そのため、どの出力モードで再生しても、ライブ実行のときと同じ出力になります。記録にはパスとアドレスだけが含まれ、転送されたデータは含まれません。再生できるのは、記録したときと同じ OS だけです。

sudo iotap --net-only --record t.iotaprec 1234
iotap --replay t.iotaprec              # 同じ出力をもう一度、root なしで

カーネルがすでに記録しているものを読むだけ

プログラムが行う read、write、open、connect はすべてシステムコールで、カーネルは頼めば、呼び出しが起きたその都度ひとつずつ記録してくれます。どのプロセスからの呼び出しか、引数と結果がどうだったかまで。iotap はその記録を読みます。

デバッガを付けることも、コードを注入することも、見ているプログラムに手を加えることもありません。

macOS では
その記録がカーネルのトレース機能 kdebug です。Apple の fs_usage が読んでいるのと同じもので、カーネルに組み込まれているので、何もインストールする必要はありません。
Linux では
iotap 自身の小さな eBPF プログラムをカーネルのシステムコールトレースポイントに取り付けます。カーネルは実行前にそれを検証し、iotap が終了すると取り外します。
sudo が必要な理由
どちらもマシン上のすべてのプロセスを覗ける手段なので、カーネルがそれを許すのは root だけです。root でなければ、iotap はそう告げて、何も触らないうちに止まります。記録の再生に root は要りません。
メタデータだけ
iotap が記録するのは、呼び出し、ディスクリプタ、バイト数、レイテンシ、パス、ソケットのエンドポイントだけです。転送されるデータそのものは読みも保存もしません。あるプロセスが SSH 鍵を読んでも、そのファイルを何バイト読んだかが見えるだけで、中身は決して見えません。
指定したものだけ
カーネルに記録を頼むのは、指定したプロセスと、-f を付けたならそれが起動したプロセスの呼び出しだけです。ほかのプロセスには触れません。
何も残さない
macOS では kdebug を使えるプログラムは同時にひとつだけなので、iotap はエラー、シグナル、パニックを含め、終わり方にかかわらず必ず解放します。Linux では iotap が終了した瞬間にカーネルが eBPF プログラムを外します。ターミナル UI も同じように、どんな場合でも端末を元に戻します。
バイナリひとつ、デーモンなし
iotap は実行している間だけ動き、自分から接続を張ることはありません。オプションのホスト名解決(--resolve)もシステムのリゾルバを通します。
見えないもの
メモリマップドファイル I/O と、ページキャッシュの書き戻しのようにカーネルがプロセスの代わりに行う I/O。Linux では io_uring や libaio 経由の I/O も同様です。残りは README の Limitations にあります。

Homebrew で入れて、追跡を始める

Apple Silicon Mac と ARM64・x86-64 Linux には、Homebrew でビルド済みバイナリをインストールできます。Rust のインストールは不要です。

Homebrew がなければ、先にインストールしてください。必要なライブラリは Homebrew が管理します。Homebrew 自体の OS 要件も適用されます。

追跡には sudo が必要です。Linux では BPF とシステムコールトレースポイントを備えたカーネル 5.8 以降が必要です。保存した記録の再生には root 権限は要りません。

iotap をインストール:

brew install rath/tap/iotap

インストールされたバージョンを確認:

iotap --version

実行中のプロセスを追跡(1234 をその PID に置き換えてください):

sudo iotap --tui 1234

リリースのバイナリを直接ダウンロードし、SHA256SUMS で検証することもできます。Linux 版には glibc 2.28 以降、libelf、zlib が必要です。必要なパッケージを確認してください。

ソースからビルドする(Intel Mac を含む)

ソースからは macOS と、64 ビット Arm・x86-64 Linux 向けにビルドできます。

ビルドには Rust 1.98.1 が必要で、rust-toolchain.toml がそのバージョンを選びます。macOS では Xcode Command Line Tools、Linux では clang、make、pkg-config、C コンパイラ、それに libelf と zlib の開発パッケージも必要です。

Debian や Ubuntu では、まずビルドに必要なパッケージを入れます:

sudo apt install build-essential clang pkg-config \
  libelf-dev zlib1g-dev

続いてビルドし、実行中のプロセスを追跡します:

git clone https://github.com/rath/iotap.git
cd iotap
cargo build --release --locked
sudo ./target/release/iotap 1234