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                # 프로세스 하나
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은 이벤트 하나를 한 줄씩 씁니다. 시각은 Unix epoch 기준 나노초, 대상은 작은 객체입니다. jq로 파이프하거나 그대로 로그로 남기면 됩니다. 아래는 이벤트 하나를 여러 줄로 펼친 모습입니다.

{
"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은 그 파일을 같은 처리 과정에 다시 넣습니다. 그래서 어떤 출력 모드로 재생하든 실시간으로 실행했을 때와 똑같은 출력이 나옵니다. 레코딩에는 경로와 주소만 담기고 전송된 데이터는 담기지 않으며, 레코딩한 운영체제에서만 재생됩니다.

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, 그리고 페이지 캐시 writeback처럼 커널이 프로세스 대신 하는 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