Skip to content

frr149/ztrace

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ztrace

Compact xctrace (Instruments) summaries optimized for LLM consumption.

33,000 lines of XML → 10 actionable lines.

📝 Blog post: how and why ztrace was built

Problem

xctrace export generates exhaustive XML — every sample, every backtrace frame, every memory address. This is great for Instruments' interactive UI, but when an LLM needs to find hotspots, the signal-to-noise ratio is brutal.

Example

$ ztrace summary ./MyApp.trace

Process: ghostty  Duration: 3.8s  Template: Time Profiler
Samples: 295  Total CPU: 295ms

SELF TIME
   53.2%     157ms  ghostty  main
    3.7%      11ms  ghostty  renderer.metal.RenderPass.begin
    3.1%       9ms  ghostty  renderer.generic.Renderer(renderer.Metal).rebuildCells
    2.7%       8ms  ghostty  renderer.generic.Renderer(renderer.Metal).drawFrame
    1.7%       5ms  ghostty  font.shaper.coretext.Shaper.shape

CALL STACKS
   53.2%     157ms  main
    1.7%       5ms  main > @objc TerminalWindow.title.setter

What it does

  1. Runs xctrace export --toc for trace metadata (process, duration, template)
  2. Runs xctrace export --xpath to extract the time-profile table
  3. Parses the XML, resolving xctrace's id/ref deduplication system
  4. Filters non-actionable frames (system libraries, Swift runtime internals, dyld stubs, unsymbolicated addresses)
  5. Aggregates by function with self time, total time, and call stacks

Install

# With uv (recommended)
uv tool install git+https://github.com/frr149/ztrace

# From source
git clone https://github.com/frr149/ztrace
cd ztrace
uv sync

Usage

# Summarize a .trace bundle
ztrace summary ./MyApp.trace

# Lower threshold to show more functions (default: 1%)
ztrace summary ./MyApp.trace --threshold 0.5

# Deeper call stacks (default: 5)
ztrace summary ./MyApp.trace --depth 10

What gets filtered

Category Examples Why
System libraries libdispatch, dyld, libsystem_m Can't optimize what you don't own
Swift/ObjC runtime __swift_instantiate*, _swift_* Runtime internals, not your code
Dyld stubs DYLD-STUB$$sin Dynamic linker thunks
Unsymbolicated frames 0x104885404 Stripped binaries — noted in output

When a binary is stripped (e.g. Spotify), ztrace reports the percentage of unsymbolicated samples so you know you need dSYMs for a full picture.

Integration with Claude Code

ztrace was built specifically for LLM-assisted profiling workflows. Add this to your CLAUDE.md (global or per-project) so Claude Code uses it automatically:

### Profiling (xctrace)

- Use `ztrace summary <file.trace>` to read traces. NEVER read raw xctrace XML.
- Workflow: `xctrace record``ztrace summary`
- Flags: `--threshold 0.5` (more functions), `--depth 10` (deeper stacks)

Typical workflow inside Claude Code:

# 1. Record a trace
xctrace record --template 'Time Profiler' --time-limit 5s --launch -- .build/debug/MyApp

# 2. Summarize (ztrace output fits in context window)
ztrace summary MyApp.trace

# 3. Claude Code reads the 10-line summary and suggests optimizations

Without ztrace, step 2 would produce 30,000+ lines of XML that either overflows the context window or drowns the signal in noise.

Requirements

  • macOS (xctrace is Apple-only)
  • Python ≥ 3.12
  • Xcode Command Line Tools (xcode-select --install)

Posts

License

MIT

About

Compact xctrace (Instruments) summaries optimized for LLM consumption

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors