The macOS edition of IO-Tracer. It records system I/O activity — filesystem (VFS) syscalls, block-device I/O, and (opt-in) network connection activity — plus filesystem, process, and system snapshots, and writes it all as compressed CSV.
It is the macOS counterpart of
io-tracer-linux: it captures the
same kind of data and uses the same on-disk schema, so a single parser
reads traces from either operating system.
git clone https://github.com/cacheMon/io-tracer-mac.git
cd io-tracer-mac
pip3 install -r requirements.txt
# Trace filesystem + block I/O until you press Ctrl+C
sudo python3 iotrc.pyThat's it — DTrace ships with macOS, so there's no kernel module or compiler to
install. Press Ctrl+C to stop; the tracer flushes, compresses, and (unless
--no-upload) uploads the session before exiting.
Linux's tracer is built on eBPF/BCC, which macOS does not have. macOS's native
in-kernel tracing facility is DTrace, so this edition swaps the kernel layer
for DTrace while keeping the entire userspace pipeline shared with the Linux
tracer (buffered + Zstandard-compressed CSV output, the snapshot collectors, the
session manifest.json, and the uploader). Only the event source differs:
| Layer | Linux (io-tracer-linux) |
macOS (io-tracer-mac) |
|---|---|---|
| VFS / filesystem events | eBPF kprobes (prober.c) |
DTrace syscall provider (dtrace/vfs.d) |
| Block-device I/O | eBPF block tracepoints | DTrace io provider (dtrace/io.d) |
| Network (opt-in) | eBPF socket probes | DTrace syscall provider (dtrace/network.d) |
| Snapshots / output / upload | shared Python (psutil, WriteManager, …) |
same shared Python |
dtrace/vfs.d ┐ dtrace/io.d ┐ dtrace/network.d ┐ (DTrace, in kernel)
│ │ │
└──────── SOH-delimited records on stdout ───────┐
▼
DTraceCollector (reader threads, parse)
│
┌──────────────────────────────────────┤
▼ ▼
WriteManager → fs/ block/ nw_conn/ Snappers → process/
(CSV + Zstandard, rotation) filesystem_snapshot/ spec/
│ │
└──────────► ObjectStorageManager ◄──────┘ (upload)
See docs/TRACE_TYPES.md for how each stream is collected and docs/TRACE_FORMAT.md for the full column layout.
- macOS with DTrace (ships with every release at
/usr/sbin/dtrace). - Root: DTrace requires
sudo. - Python 3.9+.
- DTrace must be permitted by System Integrity Protection (SIP) — see Allowing DTrace under SIP below.
Python dependencies (requirements.txt): psutil, requests, and zstandard.
zstandard is optional — without it the tracer still runs and keeps traces
uncompressed.
On a stock Mac, System Integrity Protection (SIP) keeps DTrace switched off,
so the syscall/io probes this tracer relies on can't attach (you'll see
probe description ... does not match any probes. System Integrity Protection is on). If the probes can't attach, the tracer stops at startup with this same
guidance rather than recording an empty trace.
Allowing DTrace is a one-time change made from macOS Recovery (the command won't run from your normal desktop):
-
Boot into Recovery:
- Apple silicon (M1/M2/M3/…): shut down, then press and hold the power button until "Loading startup options" appears → Options → Continue.
- Intel: restart and immediately hold ⌘ (Command) + R until the Apple logo appears.
-
In the menu bar choose Utilities → Terminal.
-
Run one of:
csrutil enable --without dtrace # keep SIP on, permit only DTrace (recommended) csrutil disable # fully disable SIP (only if the above isn't available)
-
Restart back to macOS (
reboot), then run the tracer withsudo.
To restore full protection when you're done, boot into Recovery again and run
csrutil enable. You can check the current setting any time (no reboot needed)
with csrutil status.
See docs/SIP.md for a friendlier, more detailed walkthrough (identifying your Mac, re-enabling SIP, troubleshooting, managed Macs).
curl -sSL https://raw.githubusercontent.com/cacheMon/io-tracer-mac/main/install.sh | sudo bashgit clone https://github.com/cacheMon/io-tracer-mac.git
cd io-tracer-mac
pip3 install -r requirements.txtsudo bash ./scripts/install_service.sh install # also: uninstall|status|start|stop|restart|logsusage: sudo python3 iotrc.py [-h] [-v] [-a] [--no-network] [--computer-id]
[--reward] [--no-upload] {dev} ...
Trace macOS I/O operations with DTrace
options:
-h, --help show this help message and exit
-v, --verbose Print verbose output
-a, --anonimize Enable anonymization of process and file names
--no-network Disable network event tracing (on by default)
--computer-id Print this machine ID and exit
--reward Show your reward code (unlocked after uploading traces)
--no-upload Disable automatic upload of traces (for testing)
subcommands:
{dev} Run in developer mode with extra logs and checks
(supports --trace-bucket NAME to override the upload bucket)
# Default trace (filesystem + block I/O + network); uploads when finished
sudo python3 iotrc.py
# Skip network connection tracing, with verbose logging
sudo python3 iotrc.py --no-network -v
# Local-only run (no upload), developer mode
sudo python3 iotrc.py dev --no-upload
# Anonymize process and file names in the trace
sudo python3 iotrc.py -a
# Print this machine's anonymous ID
python3 iotrc.py --computer-idTraces are written under your temp directory and (unless --no-upload) uploaded
with the prefix mac_trace_v1_test/{MACHINE_ID}/{TIMESTAMP}/:
mac_trace_v1_test/{MACHINE_ID}/{YYYYMMDD_HHMMSS_mmm}/
├── fs/ # VFS (filesystem syscall) traces
├── block/ # Block-device traces
├── nw_conn/ # Network connection lifecycle (default; --no-network to skip)
├── process/ # Process state snapshots
├── filesystem_snapshot/ # Filesystem metadata snapshots
├── system_spec/ # System specification files (JSON)
└── manifest.json # Self-describing schema + clock + diagnostics
Every CSV starts with a schema header row and carries a trailing mono_ns
column — the common clock for correlating records across streams. Example fs/
rows:
timestamp,operation,pid,tid,command,filename,size,offset,bytes_completed,inode,device,flags,duration_ns,return_value,errno,mmap_prot,mmap_flags,address,cmdline,ppid,container_id,fs_type,mono_ns
2026-06-17 09:14:02.481922,open,5123,5123,vim,/Users/alice/notes.md,,,,,,O_RDWR|O_CREAT,,,,,,,vim notes.md,812,,,1287340019223
2026-06-17 09:14:02.482310,read,5123,5123,vim,/Users/alice/notes.md,65536,0,4096,,,,7300,4096,,,,,vim notes.md,812,,,1287340026550Read compressed traces with pandas (zstandard installed):
import pandas as pd
df = pd.read_csv("fs_20260617_091402_481_0001.csv.zst")The macOS edition emits the same schema, but a few Linux-only fields are not available from DTrace and are left empty (per the schema's "empty when unavailable" convention):
inode,device,fs_type,container_idonfs/records — not exposed by the DTrace syscall context.queue_latency_ms,command_flags,operation_codeonblock/records — Linux block-layer extras.cache/(page cache) andpagefault/streams are not collected on macOS (the Linux tracer also leaves page-cache opt-in and page-faults disabled). The schema keeps these definitions for cross-OS alignment; the files are simply absent.- Network is the connection-lifecycle subset (socket/bind/listen/accept/
connect/shutdown) in
nw_conn/. Thenw_epoll/nw_sockopt/nw_dropstreams are Linux-only.
# Run the test suite (pure Python — no DTrace or root required)
python3 -m unittest discover -s tests
# or, with pytest installed:
pytest tests/
# End-to-end smoke test on a real Mac (generates I/O, runs a short trace)
sudo bash ./scripts/smoke_test.shLayout:
iotrc.py # CLI entry point
src/tracer/dtrace/*.d # DTrace scripts (vfs, io, network)
src/tracer/DTraceCollector.py # launches dtrace, parses records → WriteManager
src/tracer/IOTracer.py # orchestrator (snapshots, manifest, upload)
src/tracer/FlagMapper.py # macOS O_* / errno / socket decoding
src/tracer/schema.py # shared on-disk schema (single source of truth)
src/tracer/WriterManager.py # buffered, rotated, Zstandard CSV output
src/tracer/snappers/ # filesystem / process / system snapshots
... does not match any probes. System Integrity Protection is on(or "All DTrace streams exited at startup") — SIP is blocking the DTrace providers, so no events can be captured. Reboot into Recovery and runcsrutil enable --without dtrace(orcsrutil disable), then reboot and re-run the tracer. See Requirements above.dtrace: failed to initialize dtrace: DTrace requires additional privileges— run withsudo.dtrace cannot control executables signed with restricted entitlements— expected for some Apple-signed processes under SIP; this tracer only relies on thesyscall/ioproviders and skips restricted targets.- No
fs//block/files produced — confirmdtraceis onPATH(or at/usr/sbin/dtrace) and that you ran withsudo. Use-vto see per-stream startup and any dtrace diagnostics. - Traces aren't compressed (
.csvinstead of.csv.zst) — install the optionalzstandardpackage (pip3 install zstandard).
sudo bash ./uninstall.sh