Instruments analyzer
Swift development kit for agentic coding: Swift 6/SwiftUI standards, Apple docs lookup with falsification, Xcode-as-MCP-worker operating guide, Apple's Xcode 27 skills via local extraction, and programmatic Instruments analysis
npx -y skills add jlreyes/swift-agent-kit --skill instruments-analyzerAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Gives AI agents programmatic access to Apple Instruments trace data by exporting .trace files to DuckDB. Covers recording traces, exporting to DuckDB, exploring exported tables, and running analysis scripts.
SKILL.md
6.7 KB, as published. Nobody here has run it
Instruments Analyzer
This tool gives you programmatic access to Apple Instruments trace data. Instruments is normally a GUI tool — this tool bridges the gap by exporting .trace files into DuckDB, where you can query them with SQL.
When to use this tool
- You have (or want to record) an Instruments
.tracefile - You want to analyze performance data: CPU profiling, hitches, hangs, signposts, Core Animation, SwiftUI updates, RunLoop activity, etc.
- You want root-cause analysis backed by frame-level or event-level evidence
For scroll & animation jank diagnosis: See scroll_and_animation.md — a frame-first workflow for isolating interaction windows, ranking dropped frames, cascade analysis, per-frame attribution, and producing a prioritized fix plan.
Workflow overview
- Record a trace (or use an existing one)
- Export the trace to DuckDB
- Explore the exported tables
- Prepare derived views (optional, for frame-level analysis)
- Analyze using SQL queries against the DuckDB database
Step 1: Record a trace
Use xctrace to record from the command line:
# Attach to a running app
xcrun xctrace record --template 'SwiftUI' --time-limit 20s \
--output ./traces/recording.trace \
--attach AppName --no-prompt
# Or launch the app
xcrun xctrace record --template 'SwiftUI' --time-limit 20s \
--output ./traces/recording.trace \
--launch /path/to/App.app --no-prompt
You can also use the included PerfDebugging.tracetemplate in Instruments.
Choosing a template
- SwiftUI: SwiftUI view updates, hitches, Core Animation, signposts
- Time Profiler: CPU sampling with backtraces
- Animation Hitches: Frame lifetimes, hitch detection
- Custom: Combine instruments as needed
Use xcrun xctrace list templates to see available templates.
Step 2: Export to DuckDB
The export script converts an Instruments .trace file into a DuckDB database with Parquet backing:
./scripts/export_to_duckdb.py traces/recording.trace traces/recording/analysis.duckdb
The script:
- Uses
uv runvia shebang — run it directly (not withpython3) - Requires uv
- Creates any missing parent directories for the output path
- Exports each Instruments table as a compressed Parquet file
- Creates a DuckDB database with views referencing the Parquet files
If key tables are empty after export, recommend a different Instruments template or a longer recording.
Step 3: Explore the exported tables
After export, connect to the DuckDB database and explore what's available:
-- List all tables/views
SHOW TABLES;
-- Check row counts
SELECT 'updates' AS tbl, COUNT(*) AS rows FROM updates
UNION ALL SELECT 'hitches', COUNT(*) FROM hitches
UNION ALL SELECT 'time_profile', COUNT(*) FROM time_profile
UNION ALL SELECT 'os_signpost_intervals', COUNT(*) FROM os_signpost_intervals
UNION ALL SELECT 'runloop_intervals', COUNT(*) FROM runloop_intervals
UNION ALL SELECT 'potential_hangs', COUNT(*) FROM potential_hangs;
Key tables
| Table | What it contains |
|---|---|
updates | Individual SwiftUI view body evaluations |
update_groups | Batched SwiftUI transaction groups |
hitches | Detected animation hitches (frame drops) |
hitches_frame_lifetimes | Complete frame lifetime data |
hitches_updates / hitches_renders / hitches_gpu / hitches_framewait | Per-phase frame data |
time_profile | CPU sampling with backtraces |
os_signpost_intervals | Signpost intervals (begin/end pairs) |
os_signpost | Signpost point events |
os_log | Log messages from os_log |
runloop_intervals | RunLoop activity (main thread scheduling) |
coreanimation_context_intervals | CA rendering phases (Layout, Display, Prepare, Commit) |
coreanimation_lifetime_intervals | CA frame lifetimes with acceptable latency thresholds |
potential_hangs | Detected hangs and unresponsiveness |
life_cycle_periods | App lifecycle transitions |
swiftui_causes | SwiftUI dependency/causality graph |
swiftui_changes | SwiftUI change events with backtraces |
Full schema reference: SCHEMAS.md
Common exploration queries
-- Signpost overview (what instrumentation exists)
SELECT
name, category, subsystem,
COUNT(*) AS n,
MAX(CAST(duration_ns AS BIGINT))/1e6 AS max_ms
FROM os_signpost_intervals
WHERE name IS NOT NULL
GROUP BY 1,2,3
ORDER BY max_ms DESC
LIMIT 50;
-- Worst hitches
SELECT start_ns/1e9 AS time_s, duration_ns/1e6 AS ms, narrative_description
FROM hitches
ORDER BY duration_ns DESC
LIMIT 20;
-- Heaviest CPU backtraces
SELECT
COUNT(*) AS samples,
SUM(weight_ns)/1e6 AS approx_ms,
backtrace_json
FROM time_profile
WHERE backtrace_json IS NOT NULL
GROUP BY backtrace_json
ORDER BY approx_ms DESC
LIMIT 10;
-- Hang summary
SELECT hang_type, COUNT(*) AS count, MAX(duration_ns)/1e6 AS max_ms
FROM potential_hangs
GROUP BY hang_type
ORDER BY max_ms DESC;
Step 4: Prepare derived views (for frame analysis)
The prepare_analysis.py script creates analysis-ready views on top of the raw exported data:
./scripts/prepare_analysis.py traces/recording/analysis.duckdb
This creates a frames view from hitches_frame_lifetimes with:
- Computed
missed_framescount - Severity buckets (Low / Medium / High / Extreme)
- Inferred frame budget from
coreanimation_lifetime_intervals.acceptable_latency_ns - Falls back to 60fps (16.67ms) if unavailable
Override for 120fps displays:
./scripts/prepare_analysis.py traces/recording/analysis.duckdb --budget-ms 8.33
Frame-specific cascade analysis
Analyze a specific frame with surrounding context:
./scripts/prepare_analysis.py traces/recording/analysis.duckdb --swap-id 166871
./scripts/prepare_analysis.py traces/recording/analysis.duckdb --swap-id 166871 --context-frames 10
This outputs:
- Target frame details
- Preceding frames with budget status
- Root cause identification (first over-budget frame in a cascade)
- Signposts and logs during the root cause frame
Time units
- All timestamps and durations are in nanoseconds
start_nsis relative to trace start (not wall clock)- Convert:
duration_ns / 1e6for milliseconds,start_ns / 1e9for seconds os_signpost_*timestamps are strings — cast to BIGINT when comparing
Use-case companion resources
- scroll_and_animation.md — SwiftUI scroll and animation jank diagnosis