Systrace (now superseded by Perfetto) gives you a system-wide timeline: every thread, every Binder call, every scheduled process — not just your app's activity. When frame drops don't appear in your code, the system trace reveals the real culprit.
Systrace vs Perfetto vs Android Profiler
| Tool | Scope | Best for |
|---|---|---|
| Android Studio Profiler | App only | Method-level flamecharts, heap dumps |
| Systrace (legacy) | System + app | Cross-process, scheduling, Binder calls |
| Perfetto (modern) | System + app | Same as systrace; long traces; recommended |
Always start with the Android Studio Profiler — it's fastest. Escalate to Perfetto when the bottleneck isn't visible in your code (scheduling delays, Binder latency, Zygote timing).
Adding Custom Trace Events
Instrument your code to appear in the trace timeline:
// Kotlin — wrap any block you want measured
import android.os.Trace
fun loadArticles() {
Trace.beginSection("ArticleRepository.load")
try {
// ... expensive work
} finally {
Trace.endSection()
}
}
// Or use the AndroidX tracing library for a cleaner API:
// implementation("androidx.tracing:tracing:1.1.0")
import androidx.tracing.trace
fun processArticles(articles: List<Article>) {
trace("processArticles") {
articles.forEach { processArticle(it) }
}
}
Custom sections appear as colored slices in the Perfetto timeline under your app's thread.
Capturing a Perfetto Trace
# 1. Force-stop your app
adb shell am force-stop com.example.myapp
# 2. Start recording (10 second window)
adb shell perfetto \
-c - --txt -o /data/misc/perfetto-traces/trace.pftrace \
<<EOF
buffers: { size_kb: 32768 }
data_sources: { config { name: "linux.ftrace"
ftrace_config {
ftrace_events: "sched/sched_switch"
ftrace_events: "power/suspend_resume"
atrace_categories: "view"
atrace_categories: "gfx"
atrace_categories: "am"
atrace_apps: "com.example.myapp"
}
} }
duration_ms: 10000
EOF
# 3. Launch your app and perform the scenario
adb shell am start-activity -n com.example.myapp/.MainActivity
# 4. Pull the trace
adb pull /data/misc/perfetto-traces/trace.pftrace ~/Desktop/
Open at ui.perfetto.dev.
Reading the Perfetto Timeline
Key rows to examine:
| Row | What it shows |
|---|---|
com.example.myapp | Your app's CPU slices — when it ran |
RenderThread | GPU draw calls; long slices = GPU bottleneck |
Binder slices | Cross-process calls; long = IPC bottleneck |
Choreographer#doFrame | Frame timing; should complete < 16ms |
ActivityThread.main | Your main thread — look for long slices |
| Custom sections | Your Trace.beginSection calls appear here |
Identifying Scheduling Problems
If your frame looks fast in isolation but still jank on device, check:
-
CPU frequency scaling: The trace shows CPU clock speed per core. If your main thread was scheduled on a power-efficiency core at half speed, it looks slow even when the code is fast.
-
Preemption: Your thread may have been preempted (another process got the CPU). Look for gaps in your thread's timeline where the CPU was used by another process.
-
Lock contention: Long waits on
Monitor.notifysuggest lock contention between threads.
Android Studio Method Tracer
For method-level detail without full systrace overhead:
// Programmatic sampling profiler
import android.os.Debug
override fun onResume() {
Debug.startMethodTracingSampling("my_trace", 8_000_000, 1000)
}
override fun onPause() {
Debug.stopMethodTracing()
// Output: /sdcard/my_trace.trace
// Open in Android Studio: Run → Open Trace Files
}
Key Takeaways
| Scenario | Tool |
|---|---|
| Method-level flame chart | Android Studio CPU Profiler |
| Cross-process (Binder) analysis | Perfetto |
| Startup timing | Perfetto with android.startupapp data source |
| Scheduling delays | Perfetto with sched/sched_switch ftrace events |
| Custom section timing | Trace.beginSection / trace { } |