Tracing SDK

Perfetto Tracing SDK 是一个 C++17 库,允许用户空间应用程序发出 Trace 事件,并向 Perfetto trace 中添加更多应用程序特定的上下文。

使用 Tracing SDK 时,需要考虑两个主要方面:

  1. 你是只对 Tracing 来自自己应用程序的事件感兴趣,还是想收集完整的全栈 Trace,将应用程序 Trace 事件与系统 Trace 事件(如调度器 trace、系统调用或任何其他 Perfetto 数据源)叠加在一起。

  2. 对于应用程序特定的 Tracing,你是需要 Tracing 简单类型的 Timeline 事件(例如 Slice、Counter),还是需要定义具有自定义强类型 Schema 的复杂数据源(例如,将应用程序子系统的状态转储到 trace 中)。

对于仅限 Android 的插桩,如果现有的 android.os.Trace (SDK) / ATrace_* (NDK) 能够满足你的使用场景,建议继续使用它们。基于 Atrace 的插桩在 Perfetto 中得到完全支持。有关详情,请参阅 Data Sources -> Android System -> Atrace Instrumentation

快速入门

TIP: 这些示例中的代码也可在[仓库中](/examples/sdk/README.md)找到。

要开始使用 Client API,首先从最新的 Perfetto 版本下载 SDK 源文件:

  1. 访问 https://github.com/google/perfetto/releases/latest
  2. 下载 perfetto-cpp-sdk-src.zip 并将文件解压到 sdk/perfetto 目录

或者,用于开发目的,你可以使用 tools/gen_amalgamated --output sdk/perfetto 生成它们。

然后,使用 CMake 进行构建:

cd examples/sdk cmake -B build cmake --build build

SDK 由两个文件组成,sdk/perfetto.hsdk/perfetto.cc。这些是 Client API 的合并版本,设计为易于集成到现有的构建系统中。源代码是自包含的,只需要符合 C++17 标准库。

例如,要将 SDK 添加到 CMake 项目中,请编辑你的 CMakeLists.txt:

cmake_minimum_required(VERSION 3.13) project(PerfettoExample) find_package(Threads) # 为 Perfetto 定义静态库。 include_directories(perfetto/sdk) add_library(perfetto STATIC perfetto/sdk/perfetto.cc) # 将库链接到你的主可执行文件。 add_executable(example example.cc) target_link_libraries(example perfetto ${CMAKE_THREAD_LIBS_INIT}) if (WIN32) # perfetto 库包含许多符号,因此它需要大对象格式。 target_compile_options(perfetto PRIVATE "/bigobj") # 在 windows.h 中禁用旧功能。 add_definitions(-DWIN32_LEAN_AND_MEAN -DNOMINMAX) # 在 Windows 上,我们应该链接到 WinSock2。 target_link_libraries(example ws2_32) endif (WIN32) # 使用 Visual Studio 编译器时启用符合标准的模式。 if (MSVC) target_compile_options(example PRIVATE "/permissive-") endif (MSVC)

接下来,在你的程序中初始化 Perfetto:

#include <perfetto.h> int main(int argc, char** argv) { perfetto::TracingInitArgs args; // backends 决定在何处记录 Trace 事件。你可以选择一个或多个: // 1) 进程内 backend 仅在应用程序本身内记录。 args.backends |= perfetto::kInProcessBackend; // 2) 系统 backend 将事件写入系统 Perfetto daemon, // 允许在同一 Timeline 上合并应用程序和系统事件(例如,ftrace)。 // 要求 Perfetto `traced` daemon 正在运行(例如,在 Android Pie 和更新版本上)。 args.backends |= perfetto::kSystemBackend; perfetto::Tracing::Initialize(args); }

现在你已经准备好使用 Trace 事件为你的应用程序进行插桩了。

自定义数据源 vs Track 事件

SDK 提供两个抽象层来注入tracing 数据,它们彼此构建,在代码复杂度和表现力之间进行权衡:Track 事件

Track 事件

Track 事件是处理应用程序特定 Tracing 的建议选项,因为它们处理了许多细微之处(例如,线程安全、刷新、字符串驻留)。Track 事件是基于代码库中简单 TRACE_EVENT 注释标记的有界事件(例如,slice、counter),如下所示:

#include <perfetto.h> PERFETTO_DEFINE_CATEGORIES( perfetto::Category("rendering") .SetDescription("Events from the graphics subsystem"), perfetto::Category("network") .SetDescription("Network upload and download statistics")); PERFETTO_TRACK_EVENT_STATIC_STORAGE(); ... int main(int argc, char** argv) { ... perfetto::Tracing::Initialize(args); perfetto::TrackEvent::Register(); } ... void LayerTreeHost::DoUpdateLayers() { TRACE_EVENT("rendering", "LayerTreeHost::DoUpdateLayers"); ... for (PictureLayer& pl : layers) { TRACE_EVENT("rendering", "PictureLayer::Update"); pl.Update(); } }

它们在 UI 中的渲染效果如下:

Track event example

Track 事件是最佳的默认选项,可以用很低的复杂度满足大多数 Tracing 用例。

要将新的 Track 事件包含在 trace 中,请确保 trace 配置中包含 track_event 数据源,并包含已启用和已禁用的类别列表。

data_sources { config { name: "track_event" track_event_config { enabled_categories: "rendering" disabled_categories: "*" } } }

有关完整说明,请参阅 Track 事件页面

自定义数据源

对于大多数用途,Track 事件是为应用程序进行 tracing 插桩的最直接的方式。然而,在某些罕见情况下,它们不够灵活,例如,当数据不适合 Track 的概念,或者数据量足够大以至于需要强类型 Schema 来最小化每个事件的大小。在这种情况下,你可以为 Perfetto 实现 _自定义数据源_。

与 Track 事件不同,使用自定义数据源时,你还需要在 Trace Processor 中进行相应的更改,以启用导入数据格式。

自定义数据源是 perfetto::DataSource 的子类。Perfetto 将自动为每个激活它的 Tracing 会话创建一个该类的实例(通常只有一个)。

class CustomDataSource : public perfetto::DataSource<CustomDataSource> { public: void OnSetup(const SetupArgs&) override { // 使用此回调根据 SetupArgs 中的 TraceConfig 对你的数据源应用任何自定义配置。 } void OnStart(const StartArgs&) override { // 此通知可用于初始化 GPU 驱动程序、启用 Counters 等。 // StartArgs 将包含 DataSourceDescriptor,可以进行扩展。 } void OnStop(const StopArgs&) override { // 撤销在 OnStart 中完成的任何初始化。 } // 数据源也可以有每个实例的状态。 int my_custom_state = 0; }; PERFETTO_DECLARE_DATA_SOURCE_STATIC_MEMBERS(CustomDataSource);

数据源的静态数据应该在一个源文件中定义,如下所示:

PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS(CustomDataSource);

自定义数据源需要向 Perfetto 注册:

int main(int argc, char** argv) { ... perfetto::Tracing::Initialize(args); // 添加以下内容: perfetto::DataSourceDescriptor dsd; dsd.set_name("com.example.custom_data_source"); CustomDataSource::Register(dsd); }

与所有数据源一样,需要在 trace 配置中指定自定义数据源以启用 tracing:

perfetto::TraceConfig cfg; auto* ds_cfg = cfg.add_data_sources()->mutable_config(); ds_cfg->set_name("com.example.custom_data_source");

最后,调用 Trace() 方法以使用你的自定义数据源记录事件。传递给该方法的 lambda 函数仅在启用 tracing 时才会被调用。它总是被同步调用,并且如果有多个并发 Tracing 会话处于活动状态,可能会被多次调用。

CustomDataSource::Trace([](CustomDataSource::TraceContext ctx) { auto packet = ctx.NewTracePacket(); packet->set_timestamp(perfetto::TrackEvent::GetTraceTimeNs()); packet->set_for_testing()->set_str("Hello world!"); });

如果有必要,Trace() 方法可以访问自定义数据源状态(上面示例中的 my_custom_state)。这样做会获取互斥锁以确保在另一个线程上调用 Trace() 方法时不会销毁数据源(例如,因为停止了追踪)。例如:

CustomDataSource::Trace([](CustomDataSource::TraceContext ctx) { auto safe_handle = trace_args.GetDataSourceLocked(); // 持有 RAII 锁。 DoSomethingWith(safe_handle->my_custom_state); });

进程内模式 vs 系统模式

这两种模式不是互斥的。应用程序可以配置为在两种模式下工作,并响应进程内 tracing 请求和系统 tracing 请求。两种模式都生成相同的 trace 文件格式。

进程内模式

在此模式下,perfetto 服务和应用程序定义的数据源完全在进程内托管,在被profile 应用程序的同一进程中。不会尝试连接到系统 traced daemon。

进程内模式可以通过在初始化 SDK 时设置 TracingInitArgs.backends = perfetto::kInProcessBackend 来启用,请参阅下面的示例。

此模式用于生成仅包含应用程序发出的事件的 trace,但不包含其他类型的事件(例如,调度器 trace)。

主要优点是,通过完全在进程内运行,它不需要任何特殊的操作系统特权,被 profile 进程可以控制追踪会话的生命周期。

此模式在 Android、Linux、MacOS 和 Windows 上受支持。

系统模式

在此模式下,应用程序定义的数据源将使用 IPC over UNIX socket 连接到外部 traced 服务。

系统模式可以通过在初始化 SDK 时设置 TracingInitArgs.backends = perfetto::kSystemBackend 来启用,请参阅下面的示例。

此模式的主要优点是可以创建融合的 trace,其中应用程序事件被叠加在同一 OS 事件的 Timeline 上。这启用了全栈性能调查,一直查看到系统调用和内核调度事件。

此模式的主要限制是它要求外部 traced daemon 正在运行并可通过 UNIX socket 连接访问。

建议用于本地调试或实验室测试场景,其中用户(或测试工具)可以控制操作系统部署(例如,在 Android 上 sideload 二进制文件)。

使用系统模式时,必须从外部控制追踪会话,使用 perfetto 命令行客户端(请参阅参考)。这是因为在收集系统 trace 时,不允许tracing 数据生产者读取 trace 数据,因为它可能会披露有关其他进程的信息并允许侧信道攻击。