使用 atrace 为 Android 应用/平台添加 trace

在本指南中,你将学习如何:

本页面主要面向:

Atrace slices example

Atrace 是 Android 4.3 中引入的 API,在 Perfetto 之前就存在,允许你为代码添加插桩。它仍然被支持和使用,并且与 Perfetto 配合良好。

在底层,Atrace 将事件转发到内核 ftrace 环形缓冲区,并与调度数据和其他系统级 trace 数据一起被获取。Atrace 既是:

  1. 一个公共 API,通过 Android SDK 暴露给 Java/Kt 代码,通过 NDK 暴露给 C/C++ 代码,开发者可以使用它来丰富 trace 以注释其应用。
  2. 一个私有平台 API,用于注释多个框架函数和核心系统服务的实现。它为开发者提供有关框架在底层正在做什么的见解。

两者之间的主要区别在于,私有平台 API 允许指定一个 tag(也称为 category),而 SDK/NDK 接口隐式使用 TRACE_TAG_APP。

在这两种情况下,Atrace 都允许你手动添加围绕代码墙时间和数值的插桩,例如用于注释函数的开始或结束、逻辑用户旅程、状态更改。

线程作用域同步 slices

Slices 用于在代码执行周围创建矩形,并在视觉上形成伪调用栈。

语义和约束:

参考 frameworks/base/core/java/android/os/Trace.java

import android.os.Trace; import static android.os.Trace.TRACE_TAG_AUDIO; public void playSound(String path) { Trace.traceBegin(TRACE_TAG_AUDIO, "PlaySound"); try { // 测量打开声音服务所需的时间。 Trace.traceBegin(TRACE_TAG_AUDIO, "OpenAudioDevice"); try { SoundDevice dev = openAudioDevice(); } finally { Trace.traceEnd(); } for(...) { Trace.traceBegin(TRACE_TAG_AUDIO, "SendBuffer"); try { sendAudioBuffer(dev, ...) } finally { Trace.traceEnd(); } // 在 trace 中记录缓冲区使用统计信息。 Trace.setCounter(TRACE_TAG_AUDIO, "SndBufferUsage", dev.buffer) ... } } finally { Trace.traceEnd(); // 结束根 PlaySound slice } }

// ATRACE_TAG 是在此翻译单元中使用的 category。 // 从 Android 的 system/core/libcutils/include/cutils/trace.h 中 // 定义的 categories 中选择一个。 #define ATRACE_TAG ATRACE_TAG_AUDIO #include <cutils/trace.h> void PlaySound(const char* path) { ATRACE_BEGIN("PlaySound"); // 测量打开声音服务所需的时间。 ATRACE_BEGIN("OpenAudioDevice"); struct snd_dev* dev = OpenAudioDevice(); ATRACE_END(); for(...) { ATRACE_BEGIN("SendBuffer"); SendAudioBuffer(dev, ...) ATRACE_END(); // 在 trace 中记录缓冲区使用统计信息。 ATRACE_INT("SndBufferUsage", dev->buffer); ... } ATRACE_END(); // 结束根 PlaySound slice }

参考 SDK reference documentation for os.trace

// 使用 SDK API 时不能选择 tag/category。 // 隐式所有调用都使用 ATRACE_TAG_APP tag。 import android.os.Trace; public void playSound(String path) { try { Trace.beginSection("PlaySound"); // 测量打开声音服务所需的时间。 Trace.beginSection("OpenAudioDevice"); try { SoundDevice dev = openAudioDevice(); } finally { Trace.endSection(); } for(...) { Trace.beginSection("SendBuffer"); try { sendAudioBuffer(dev, ...) } finally { Trace.endSection(); } // 在 trace 中记录缓冲区使用统计信息。 Trace.setCounter("SndBufferUsage", dev.buffer) ... } } finally { Trace.endSection(); // 结束根 PlaySound slice } }

参考 NDK reference documentation for Tracing

// 使用 NDK API 时不能选择 tag/category。 // 隐式所有调用都使用 ATRACE_TAG_APP tag。 #include <android/trace.h> void PlaySound(const char* path) { ATrace_beginSection("PlaySound"); // 测量打开声音服务所需的时间。 ATrace_beginSection("OpenAudioDevice"); struct snd_dev* dev = OpenAudioDevice(); ATrace_endSection(); for(...) { ATrace_beginSection("SendBuffer"); SendAudioBuffer(dev, ...) ATrace_endSection(); // 在 trace 中记录缓冲区使用统计信息。 ATrace_setCounter("SndBufferUsage", dev->buffer) ... } ATrace_endSection(); // 结束根 PlaySound slice }

计数器

语义和约束:

参考 frameworks/base/core/java/android/os/Trace.java

import android.os.Trace; import static android.os.Trace.TRACE_TAG_AUDIO; public void playSound(String path) { SoundDevice dev = openAudioDevice(); for(...) { sendAudioBuffer(dev, ...) ... // 在 trace 中记录缓冲区使用统计信息。 Trace.setCounter(TRACE_TAG_AUDIO, "SndBufferUsage", dev.buffer.used_bytes) } }

// ATRACE_TAG 是在此翻译单元中使用的 category。 // 从 Android 的 system/core/libcutils/include/cutils/trace.h 中 // 定义的 categories 中选择一个。 #define ATRACE_TAG ATRACE_TAG_AUDIO #include <cutils/trace.h> void PlaySound(const char* path) { struct snd_dev* dev = OpenAudioDevice(); for(...) { SendAudioBuffer(dev, ...) // 在 trace 中记录缓冲区使用统计信息。 ATRACE_INT("SndBufferUsage", dev->buffer.used_bytes); } }

参考 SDK reference documentation for os.trace

// 使用 SDK API 时不能选择 tag/category。 // 隐式所有调用都使用 ATRACE_TAG_APP tag。 import android.os.Trace; public void playSound(String path) { SoundDevice dev = openAudioDevice(); for(...) { sendAudioBuffer(dev, ...) // 在 trace 中记录缓冲区使用统计信息。 Trace.setCounter("SndBufferUsage", dev.buffer.used_bytes) } }

参考 NDK reference documentation for Tracing

// 使用 NDK API 时不能选择 tag/category。 // 隐式所有调用都使用 ATRACE_TAG_APP tag。 #include <android/trace.h> void PlaySound(const char* path) { struct snd_dev* dev = OpenAudioDevice(); for(...) { SendAudioBuffer(dev, ...) // 在 trace 中记录缓冲区使用统计信息。 ATrace_setCounter("SndBufferUsage", dev->buffer.used_bytes) } }

跨线程 async slices

Async slices 允许 trace 可能在不同线程上开始和结束的逻辑操作。它们是 Perfetto SDK 中 track events 的相同概念。

由于 begin/end 可以发生在不同的线程上,你需要向每个 begin/end 函数传递一个 cookie 。cookie 只是一个用于匹配 begin/end 对的整数。cookie 通常从代表正在被 trace 的逻辑操作的指针或唯一 ID 派生(例如,作业 id)。

语义和约束:

参考 frameworks/base/core/java/android/os/Trace.java

import android.os.Trace; import static android.os.Trace.TRACE_TAG_NETWORK; public class AudioRecordActivity extends Activity { private AtomicInteger lastJobId = new AtomicInteger(0); private static final String TRACK_NAME = "User Journeys"; ... button.setOnClickListener(v -> { int jobId = lastJobId.incrementAndGet(); Trace.asyncTraceForTrackBegin(TRACE_TAG_NETWORK, TRACK_NAME, "Load profile", jobId); // 模拟异步工作(例如,网络请求) new Thread(() -> { Thread.sleep(800); // 模拟延迟 Trace.asyncTraceForTrackEnd(TRACE_TAG_NETWORK, TRACK_NAME, jobId); }).start(); }); ... }

// ATRACE_TAG 是在此翻译单元中使用的 category。 // 从 Android 的 system/core/libcutils/include/cutils/trace.h 中 // 定义的 categories 中选择一个。 #define ATRACE_TAG ATRACE_TAG_NETWORK #include <cutils/trace.h> #include <thread> #include <chrono> #include <atomic> static constexpr const char* kTrackName = "User Journeys"; void onButtonClicked() { static std::atomic<int> lastJobId{0}; int jobId = ++lastJobId; ATRACE_ASYNC_FOR_TRACK_BEGIN(kTrackName, "Load profile", jobId); std::thread([jobId]() { std::this_thread::sleep_for(std::chrono::milliseconds(800)); ATRACE_ASYNC_FOR_TRACK_END(kTrackName, jobId); }).detach(); }

参考 SDK reference documentation for os.trace

// 使用 SDK API 时不能选择 tag/category。 // 隐式所有调用都使用 ATRACE_TAG_APP tag。 import android.os.Trace; public class AudioRecordActivity extends Activity { private AtomicInteger lastJobId = new AtomicInteger(0); ... button.setOnClickListener(v -> { int jobId = lastJobId.incrementAndGet(); Trace.beginAsyncSection("Load profile", jobId); // 模拟异步工作(例如,网络请求) new Thread(() -> { Thread.sleep(800); // 模拟延迟 Trace.endAsyncSection("Load profile", jobId); }).start(); }); ... }

参考 NDK reference documentation for Tracing

// 使用 NDK API 时不能选择 tag/category。 // 隐式所有调用都使用 ATRACE_TAG_APP tag。 #include <android/trace.h> #include <thread> #include <chrono> #include <atomic> void onButtonClicked() { static std::atomic<int> lastJobId{0}; int jobId = ++lastJobId; ATrace_beginAsyncSection("Load profile", jobId); std::thread([jobId]() { std::this_thread::sleep_for(std::chrono::milliseconds(800)); ATrace_endAsyncSection("Load profile", jobId); }).detach(); }

我应该使用 Atrace 还是 Perfetto Tracing SDK?

在撰写本文时,这个问题没有一个明确的答案。我们的团队正在努力提供一个可以包含所有 atrace 用例的替代 SDK,但我们还没有达到目标。所以答案是:取决于具体情况。

何时优先选择 Atrace 何时优先选择 Tracing SDK
你需要简单且有效的功能。 你需要更高级的功能(例如 flows)。
你可以接受整个应用的一个开/关切换。(如果你在 Android 系统中,你只能使用有限的 tags) 你需要对 trace categories 进行细粒度控制。
你可以接受事件在主 ftace 缓冲区中被多路复用。 你希望控制在不同缓冲区中多路复用事件。
插桩开销不是大问题,你的 trace 点偶尔被命中。 你希望为你的插桩点实现最小开销。你的 trace 点是频繁的(每 10ms 或更少)

如果你是一个独立应用

你应该考虑使用 Jetpack 的 androidx.tracing。我们与 Jetpack 项目密切合作。使用 androidx.tracing 将在我们改进 SDK 时带来更平滑的迁移路径。

采集 trace

为了记录 atrace,你必须启用 linux.ftrace 数据源并在 ftrace_config 中添加:

你可以在这里看到完整的 atrace categories 列表here

Atrace recording via UI

curl -O https://raw.githubusercontent.com/google/perfetto/main/tools/record_android_trace python3 record_android_trace \ -o trace_file.perfetto-trace \ -t 10s \ # To record atrace from apps. -a 'com.myapp' \ # or '*' for tracing all apps # To record atrace from system services. am wm webview

:data_sources { config { name: "linux.ftrace" ftrace_config { atrace_categories: "am" atrace_categories: "wm" atrace_categories: "webview" atrace_apps: "com.myapp1" atrace_apps: "com.myapp2" } } }

后续步骤

现在你已经学习了如何使用 ATrace 插桩你的代码,这里是一些你可能觉得有用的其他文档:

采集 trace

其他 Android 数据源

分析 trace