A lightweight, embeddable Objective-C tracer that produces shareable Perfetto traces
π Maintained as needed. AppleTrace captures your app's execution timeline β manual sections and/or every
objc_msgSendβ and renders it in Perfetto, right in the browser. With the arrival of the AI era, there is even more that can be explored and improved when the need arises.
- What is AppleTrace?
- Key Features
- Quick Start
- How It Works
- Installation
- Usage
- Processing & Visualizing Traces
- Platform & Hook Support
- Testing
- Project Structure
- FAQ
- Contributing
- License
AppleTrace is an iOS/macOS tracing toolkit. You instrument your app β either by
adding manual APTBeginSection / APTEndSection markers, or by hooking every
objc_msgSend automatically β and AppleTrace records a timeline of events into
sandbox trace fragments. A small Python pipeline merges those fragments into a
single trace.json that you open directly in Perfetto
to explore the call timeline, durations, threads, and counters.
The trace visualization shows the method execution timeline and call relationships.
- π Automatic method tracing β direct
objc_msgSend/objc_msgSendSuper2rebinding on arm64 captures Objective-C activity with no source changes. - π― Manual sections β
APTBeginSection/APTEndSection(and theAPTBegin/APTEnd/APTScopeSectionhelpers) mark exactly the regions you care about β the lowest-risk option, works on every OS version. - π Rich event types β instant markers (
APTInstant), counter series (APTCounterfor memory, FPS, queue depth, β¦), and async/flow events (APTAsyncBegin/APTAsyncEnd) that cross threads and dispatch queues. - β‘ Built for the hot path β
(Class, SEL)name interning, a zero-allocation per-thread call stack, and per-thread batched writing keepmalloc/snprintf/ dispatch off the per-message path. An opt-in binary fragment format (APPLETRACE_BINARY=1) keeps string formatting off it entirely. - π§΅ Thread names β Perfetto shows real thread names instead of bare ids.
- π Runtime filtering β scope automatic tracing with class-prefix allow/deny
lists (
APPLETRACE_TRACE_CLASS_ALLOW/APPLETRACE_TRACE_CLASS_DENY). - π Perfetto-first β open
trace.jsonat ui.perfetto.dev; nothing to install, runs in the browser, scales to large traces. Begin/end pairs export asXcomplete events by default to halve trace size. - π Python 3 tooling β a unified CLI (
scripts/appletrace_cli.py), streaming merge for large captures, automated tests, and GitHub Actions CI.
- π Performance analysis β find hotspots and long methods on a real timeline.
- π Debugging β follow method execution flow across threads.
- π Learning β see how iOS/macOS frameworks actually dispatch.
- π‘οΈ Security research β analyze third-party app behavior.
# 1. Prerequisites (macOS)
brew install python git ldid # ldid is only needed for re-signing loader builds
# 2. Clone
git clone https://github.com/everettjf/AppleTrace.git
cd AppleTrace
# 3. Optional: Python tooling for merging/tests
python3 -m pip install -r requirements.txt#import <appletrace/appletrace.h>
- (void)yourMethod {
APTBegin; // section named "[ClassName yourMethod]"
// ... your code ...
APTEnd;
}// From your app, after launch:
APTInstallObjcMsgSendHook();# β¦or without code changes, via environment variable:
export APPLETRACE_AUTO_HOOK_OBJC_MSGSEND=1# Run the app; fragments land in <app sandbox>/Library/appletracedata.
# Pull that folder from the simulator/device, then:
python3 merge.py -d /path/to/appletracedata # β trace.json
# or merge AND open Perfetto in one step:
sh go.sh /path/to/appletracedataOpen ui.perfetto.dev and drag in trace.json (or use
Open trace file).
Your app (instrumented) Host tooling Browser
βββββββββββββββββββββββββββββ ββββββββββββββββββββββββ ββββββββββββββββββ
β APTBeginSection / APTEndβ¦ β β merge.py / β β β
β APTInstant / APTCounter β ββββΊ β appletrace_cli.py β ββββΊ β ui.perfetto.devβ
β APTAsyncBegin / β¦ β β β β β
β objc_msgSend auto-hook β β fragments β trace.jsonβ β drag & drop β
βββββββββββββββββββββββββββββ ββββββββββββββββββββββββ ββββββββββββββββββ
per-thread batched writes X-complete collapse
β <sandbox>/Library/appletracedata β single JSON array
- Instrument β add manual markers, or install the
objc_msgSendhook. - Capture β events accumulate in per-thread buffers and flush in bulk to
trace fragments under
<app sandbox>/Library/appletracedata. - Merge β pull the folder and run
merge.py; begin/end pairs collapse into PerfettoXcomplete events. - Visualize β drag
trace.jsoninto Perfetto.
| Requirement | Version | Used for |
|---|---|---|
| macOS | 10.15+ | Build environment |
| Xcode | 12+ | iOS/macOS builds (arm64) |
| Python | 3.9+ | Trace merging, CLI, and tests |
| Perfetto | Web | Visualization at ui.perfetto.dev |
| ldid | Optional | Re-signing the loader's embedded framework |
| LLDB | Optional | Driving the dynamic hook mode |
From the repository root:
# iOS device (arm64)
xcodebuild -project appletrace/appletrace.xcodeproj -scheme appletrace \
-configuration Release -sdk iphoneos buildAppleTrace targets arm64 only. arm64e is out of scope: the auto-hook would have to rebind pointer-authenticated GOT entries, so the hook source deliberately fails to compile for arm64e. Build a plain arm64 slice.
Embed the resulting appletrace.framework into your target (see
sample/ManualSectionDemo for manual mode and sample/TraceAllMsgDemo for the
auto-hook). For injecting into third-party apps, see the loader/ project and
run loader/resign.sh after swapping in a rebuilt framework.
Objective-C
#import <appletrace/appletrace.h>
- (void)viewDidLoad {
APTBegin; // auto-named "[ClassName viewDidLoad]"
[super viewDidLoad];
APTEnd;
}
- (void)networkRequest {
APTBeginSection("network"); // explicit section name
// ... network code ...
APTEndSection("network");
}C / C++
#include <appletrace/appletrace.h>
void complexFunction() {
APTBeginSection("processing");
// ... C++ code ...
APTEndSection("processing");
}
void saferCppFunction() {
APTScopeSection("processing"); // RAII: ends automatically at scope exit
// ... C++ code ...
}// Mark a point in time on the current thread's timeline
APTInstant("cache_miss");
// Plot a value over time (memory, FPS, queue depth, ...)
APTCounter("resident_mb", 142.5);
APTCounter("fps", 60);
// Track work that crosses threads / dispatch queues (matched by name + id)
uint64_t requestID = 42;
APTAsyncBegin("image_load", requestID);
dispatch_async(queue, ^{
// ... work on another thread ...
APTAsyncEnd("image_load", requestID);
});APTSetEnabled(NO); // Temporarily pause recording
APTSetEnabled(YES); // Resume
BOOL on = APTIsEnabled(); // Query state
APTFlush(); // Force buffered writes to disk
APTSyncWait(); // Block until pending writes complete
NSLog(@"trace dir = %s", APTGetTraceDirectory());
BOOL hooked = APTIsObjcMsgSendHookInstalled();export APPLETRACE_ENABLED=1
export APPLETRACE_DATA_DIR="$HOME/tmp/appletracedata"
export APPLETRACE_BLOCK_SIZE_MB=32
export APPLETRACE_KEEP_EXISTING=1
# Automatic objc_msgSend hook (arm64)
export APPLETRACE_AUTO_HOOK_OBJC_MSGSEND=1
# Only trace classes whose names start with these comma-separated prefixes
export APPLETRACE_TRACE_CLASS_ALLOW="MyApp,UI"
# Never trace classes with these prefixes (takes precedence over allow)
export APPLETRACE_TRACE_CLASS_DENY="NSKVO,_"
# Opt-in binary fragment format (keeps string formatting off the hot path)
export APPLETRACE_BINARY=1# Merge all fragments into trace.json (X complete events by default)
python3 merge.py -d /path/to/appletracedata
# Keep raw begin/end events instead of collapsing them
python3 merge.py -d /path/to/appletracedata --raw
# Unified CLI
python3 scripts/appletrace_cli.py merge /path/to/appletracedata
python3 scripts/appletrace_cli.py open /path/to/appletracedata # merge + open Perfetto
# One-liner helper
sh go.sh /path/to/appletracedatamerge.py auto-discovers both text (trace[_N].appletrace) and binary
(trace[_N].appletracebin) fragments and decodes each by its magic header.
Then drag the resulting trace.json into ui.perfetto.dev.
Want to try it without building anything? Drag the prebuilt
sampledata/trace.json into Perfetto.
AppleTrace targets arm64 only.
| Mode | arm64 |
|---|---|
Manual sections & explicit events (APTBeginSection, APTInstant, β¦) |
β |
Automatic objc_msgSend / objc_msgSendSuper2 hook |
β |
- Manual sections are the lowest-risk baseline and work on every iOS/macOS version.
- The arm64 auto-hook is validated end-to-end on the iOS Simulator and a host
stress test β nested sends,
superdispatch, cross-thread events, a 10-argument call, and floating-point / small-aggregate ABI cases all survive the tracing wrapper. - arm64e is not supported. Its callers reach
objc_msgSendthrough authenticated GOT entries (__DATA_CONST.__auth_got), which would require re-signing rebound pointers with the correct pointer-authentication context. Rather than ship an unvalidated hook, the hook source hard-errors when built for arm64e β build a plain arm64 slice instead.
# Python tooling (merge pipeline + binary fragment format)
python3 -m pytest tests
# objc_msgSend hook smoke tests (builds + runs the sample on a simulator)
./scripts/test_objc_msgsend_hook.sh
./scripts/test_objc_msgsend_hook_experimental.sh
# Batched-writer concurrency stress test (host build, text + binary modes)
./scripts/test_batching_stress.shThe experimental hook script additionally validates super dispatch,
cross-thread events, stack-passed and floating-point Objective-C arguments, and
small aggregate returns. The stress test asserts no events are lost or duplicated
across threads, flushes, and thread exits.
AppleTrace/
βββ appletrace/ # Core tracing framework (appletrace.xcodeproj)
β βββ appletrace/src/ # Framework source + objc_msgSend hook
βββ loader/ # Dynamic library loader + resign.sh
βββ sample/
β βββ ManualSectionDemo/ # Manual instrumentation demo
β βββ TraceAllMsgDemo/ # Automatic objc_msgSend hook demo
βββ scripts/ # CLI + smoke/stress test scripts
β βββ appletrace_cli.py # Merge + open-in-Perfetto CLI
βββ docs/ # Binary format & batching design notes
βββ tests/ # Python regression tests + stress harness
βββ sampledata/ # Demo trace.json for Perfetto
βββ merge.py # Merge fragments β trace.json
βββ appletrace_binary.py # Binary fragment encoder/decoder
βββ go.sh # Merge and open Perfetto
βββ requirements.txt # Python dev/test dependencies
Is AppleTrace still maintained? Yes β maintained as needed. AppleTrace is no longer under active day-to-day development, but the arrival of the AI era opens up more possibilities for future improvements and targeted maintenance when useful.
Does AppleTrace work on recent iOS versions? Manual instrumentation works on all iOS versions. The automatic hook mode targets arm64 only (arm64e is out of scope β see Platform & Hook Support).
Can I trace third-party apps? Yes β see the loader project and this Chinese guide: ζθ½½ MonkeyDev ε― trace 第δΈζΉ App.
Why is Python 3 required? Python 2 reached end-of-life in 2020. The tooling requires Python 3.9+.
Can I use this on macOS apps? Yes β AppleTrace works for both iOS and macOS applications.
Contributions are welcome! Please read the Contributing Guide and the Agent Guide for repository conventions.
- Fork the repository.
- Create a feature branch:
git checkout -b feature/amazing-feature - Commit your changes (run the test suite first).
- Push and open a Pull Request.
Code style: Google Objective-C Style Guide Β· PEP 8 Β· Google Shell Style Guide
Inspired by Facebook's fbtrace, and built around Google's Perfetto and the Trace Event Format.
AppleTrace is released under the MIT License. See LICENSE for details.

