そのプロセス、ファイルとネットワークで本当は何をしているのか。
macOS と Linux でプロセスのファイル・ネットワーク I/O を追跡。パス、アドレス、バイト数、処理時間をリアルタイムで確認できます。
iotap は macOS と Linux で使えるコマンドラインツールです。プロセス ID か名前を渡すと、そのプロセスの読み書きをひとつ残らず、リアルタイムで表示します。どのファイルやリモートアドレスか、何バイトを要求して実際に何バイト行き来したか、呼び出しにどれだけかかったか、失敗したかどうかまで。止めると、ファイルごと・エンドポイントごとに集計します。
上から順に読めば、これが curl 一回の一部始終です:
- 93.184.216.34 のポート 443 に 517 バイトのリクエストを送る。
- 16 KiB を要求して、4 KiB を受け取る。
- もう一度要求するが、まだ何も届いていない:
EAGAIN。 - その 4 KiB を
page.htmlに書く。 - 進捗 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 を通っています。
ターミナル UI
--tui は直近 1 秒と累計のスループットを上に表示し、その下にファイル、ネットワークエンドポイント、直近 10,000 件のイベントの三つのタブを並べます。下は、記録したダウンロードを再生したあとの Files タブです。
- 1 2 3 タブを切り替え
- s バイト数、読み、書き、呼び出し回数、新しい順で並べ替え
- Enter 選択した行の詳細
- p 表示だけ止める。追跡は続く
- y 選択したパスやアドレスをコピー
- i ネットワークインターフェースごとのトラフィック
- q 終了して集計を表示
JSON Lines
--json はイベントを 1 行ずつ書き出します。時刻は Unix エポック基準のナノ秒、対象は小さなオブジェクトです。jq にパイプするか、そのままログとして残せます。下は 1 件のイベントを複数行に展開したものです。
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