将 Fuchsia 组件作为跟踪记录提供程序进行检测时,您可以创建和观察各种类型的事件。本文档介绍了各种事件类型之间的区别、每种事件类型的最佳使用情形、如何在组件中使用事件,以及您可以在跟踪记录事件中使用的各种实参。
事件
Fuchsia 事件类型可以是以下任一类型:
- 即时事件 标记具有名称、类别和可选实参的时间点。
- 时长事件 标记两个时间点,并且可以包含名称、类别和可选 实参。
- 计数器事件 捕获通常在 Perfetto 中表示为 堆叠面积图的数值样本。
- 流事件 描述线程之间和跨进程的流切换。
- 异步事件
允许指定
id,将不同线程中的事件关联在一起 并将这些事件放在单个轨道上。 - 自定义轨道事件 允许在与组件进程(由虚拟线程提供支持)关联的专用命名轨道上对相关的时长事件和即时事件进行分组。
最基本的跟踪记录事件具有类别(第一个参数)和名称(第二个参数)。
跟踪记录事件最多可以接受 15 实参。
使用跟踪记录事件时,务必了解类别的工作原理。 以下是与类别相关的一些关键概念:
- 类别允许您对事件进行分组,以便您可以一起启用或停用这些事件。
- 类别通常是
[A-Za-z-_:]+的简短 ASCII 字符串字面量,例如gfx或audio。 - 类别是全局的。请考虑将它们命名空间化到您的组件。
- 默认情况下,类别处于停用状态。如果您添加了类别,请确保在使用
--categories #default,your_category捕获跟踪记录时指定该类别。 ffx trace支持前缀匹配。如果您运行ffx trace start --categories your_component::*,则会启用your_component::category1、your_component::category2和任何其他关联的类别。
使用事件时,请确保在代码中包含相关库:
C
#include <lib/trace/event.h>
C++
#include <lib/trace/event.h>
Rust
use fuchsia_trace::{ArgValue, Scope, ...};
您还可以查看一个简单的示例,了解如何在 C++ 中使用 跟踪记录提供程序。
即时事件
这是最基本的事件。即时事件可以标记具有名称、类别和可选实参的时间点。
类别可以选择性地注册,但大多数跟踪记录提供程序不会注册。
至少,即时跟踪记录同时具有名称和类别,以便区分:
C
TRACE_INSTANT("trace_category", "trace_name", TRACE_SCOPE_THREAD);
C++
TRACE_INSTANT("trace_category", "trace_name", TRACE_SCOPE_THREAD);
Rust
instant!("trace_category", "trace_name", Scope::Thread);
您还可以创建包含其他实参的即时事件:
C
TRACE_INSTANT("trace_category", "trace_name", TRACE_SCOPE_THREAD, argument_1, argument_1_value, argument_2, "argument_2_string");
C++
TRACE_INSTANT("trace_category", "trace_name", TRACE_SCOPE_THREAD, argument_1, argument_1_value, argument_2, "argument_2_string");
Rust
instant!("trace_category", "trace_name", Scope::Thread, argument_1 => argument_1_value, argument_2 => "argument_2_string");
在 Perfetto 中,即时事件由一个小箭头表示,如以下屏幕截图所示。
时长事件
时长事件标记两个时间点,并且可以包含名称、类别和可选实参。时长事件还可以嵌套在另一个时长事件中,并相互堆叠。
至少,时长跟踪记录同时具有名称和类别,以便区分:
C
TRACE_DURATION_BEGIN("trace_category", "example_duration");
// Do some work
TRACE_DURATION_END("trace_category", "example_duration");
C++
TRACE_DURATION_BEGIN("trace_category", "example_duration");
// Do some work
TRACE_DURATION_END("trace_category", "example_duration");
Rust
duration_begin!("trace_category", "example_duration");
// Do some work
duration_end!("trace_category", "example_duration");
或者,您可以使用 RAII(资源获取即初始化)定义时长事件,并在其超出范围时自动关闭它,如下所示:
C
{
TRACE_DURATION("trace_category", "example_duration_raii");
// Do some work
TRACE_DURATION("trace_category", "nested duration", "argument_3", argument_3_value, "argument_4", argument_4_string);
// nested_duration closes due to RAII
// trace_duration_raii closes due to RAII
}
C++
{
TRACE_DURATION("trace_category", "example_duration_raii");
// Do some work
TRACE_DURATION("trace_category", "nested duration", "argument_3", argument_3_value, "argument_4", argument_4_string);
// nested_duration closes due to RAII
// trace_duration_raii closes due to RAII
}
Rust
{
duration!("trace_category", "example_duration_raii");
// Do some work
duration!("trace_category", "nested duration", argument_3 => argument_3_value, argument_4 => argument_4_string);
// nested_duration closes due to RAII
// trace_duration_raii closes due to RAII
}
在 Perfetto 中,嵌套的时长堆栈如下所示:
计数器事件
计数器事件捕获通常在 Perfetto 中表示为堆叠面积图的数值样本。使用计数器事件时,您可以使用 id 来区分系统进程中共享相同类别和名称的计数器的多个实例。您可以将 1-15 个数值实参与事件相关联,每个实参都被解释为不同的时间序列。
此示例显示了传递给计数器事件的 counter_id 值和数据系列:
C
trace_counter_id_t counter_id = 555;
uint32_t data = get_some_data();
TRACE_COUNTER("trace_category", "trace_name", counter_id, "data_series", data);
C++
trace_counter_id_t counter_id = 555;
uint32_t data = get_some_data();
TRACE_COUNTER("trace_category", "trace_name", counter_id, "data_series", data);
Rust
let counter_id = 555;
let data = get_some_data();
counter!("trace_category", "trace_name", counter_id, "data_series" => data);
在 Perfetto 中,计数器事件如下所示:
流事件
流事件描述线程之间和跨进程的流切换。这些事件必须包含在表示流切换发生位置的时长事件中。每个流事件还可以附加实参。流开始 (TRACE_FLOW_BEGIN 或 flow_begin) 事件后可能会跟流步骤 (TRACE_FLOW_STEP 或 flow_step) 事件。
与某些跟踪记录系统(其中流 ID 的作用域限定为流名称或类别)不同,Perfetto 仅按其 ID 全局跟踪流线。如果两个不同的活跃流(即使名称或类别不同)同时共享相同的 ID,Perfetto 将绘制连接它们的错误或不明确的流线。
为确保正确的流可视化效果:
- 使用唯一 ID:在 C/C++ 中,使用 TRACE_NONCE()(在进程中唯一)或 TRACE_RANDOM_ID()(在跨进程/组件中全局唯一)。在 Rust 中,使用 fuchsia_trace::Id::new()。
避免使用静态数字,例如 555。
- 请勿同时重复使用 ID:如果您必须重复使用 ID,请确保
在使用该 ID 的上一个流完全结束(通过
TRACE_FLOW_END或flow_end!)后,再开始使用该 ID 的新流。
由于 Perfetto 以原生方式表示流的方式存在语义差异,因此 Perfetto 跟踪记录处理器和界面目前会舍弃流名称以及附加到流事件的任何实参(例如传递给 TRACE_FLOW_BEGIN 或 TRACE_FLOW_STEP 的实参)。这些名称和实参保留在 FXT 文件中,但不会在 Perfetto 查看器中显示。
最佳实践:如果您需要将诊断数据与流步骤相关联, 请将实参附加到包含时长事件,而不是流 事件本身。包含时长事件及其实参会在 Perfetto 中完全呈现。
例如:
C
// Generate a unique flow ID
trace_flow_id_t flow_id = TRACE_NONCE();
// First duration where the flow begins
{
TRACE_DURATION("trace_category", "enclosing_duration_1");
TRACE_FLOW_BEGIN("trace_category", "trace_flow_name", flow_id, "argument_1", argument_1_value);
}
// Optional intermediate duration where the flow steps
{
TRACE_DURATION("trace_category", "enclosing_duration_2");
TRACE_FLOW_STEP("trace_category", "trace_flow_step_name", flow_id);
}
// Final duration where the flow ends
{
TRACE_DURATION("trace_category", "enclosing_duration_3");
TRACE_FLOW_END("trace_category", "trace_flow_name", flow_id);
}
C++
// Generate a unique flow ID
trace_flow_id_t flow_id = TRACE_NONCE();
// First duration where the flow begins
{
TRACE_DURATION("trace_category", "enclosing_duration_1");
TRACE_FLOW_BEGIN("trace_category", "trace_flow_name", flow_id, "argument_1", argument_1_value);
}
// Optional intermediate duration where the flow steps
{
TRACE_DURATION("trace_category", "enclosing_duration_2");
TRACE_FLOW_STEP("trace_category", "trace_flow_step_name", flow_id);
}
// Final duration where the flow ends
{
TRACE_DURATION("trace_category", "enclosing_duration_3");
TRACE_FLOW_END("trace_category", "trace_flow_name", flow_id);
}
Rust
// Generate a unique flow ID
let flow_id = fuchsia_trace::Id::new();
// First duration where the flow begins
{
duration!("trace_category", "enclosing_duration_1");
flow_begin!("trace_category", "trace_flow_name", flow_id, "argument_1" => argument_1_value);
}
// Optional intermediate duration where the flow steps
{
duration!("trace_category", "enclosing_duration_2");
flow_step!("trace_category", "trace_flow_step_name", flow_id);
}
// Final duration where the flow ends
{
duration!("trace_category", "enclosing_duration_3");
flow_end!("trace_category", "trace_flow_name", flow_id);
}
在 Perfetto 中,流事件表示为箭头,例如:
异步事件
在跟踪记录中,即时事件和时长事件放置在生成它们的线程的轨道上。使用多线程异步或基于线程池的代码时,事件可能会在不同的线程上开始和结束。异步事件允许指定 id,将不同线程中的事件关联在一起,并将这些事件放在单个轨道上。
异步开始 (TRACE_ASYNC_BEGIN 或 async_begin) 事件后可能会跟异步即时 (TRACE_ASYNC_INSTANT 或 async_instant) 事件,并且必须与具有相同类别、名称和 id 的异步结束 (TRACE_ASYNC_END 或 async_end) 事件匹配。
异步事件描述异步发生且可能跨多个线程的工作。id 有助于关联同一进程中共享相同类别和名称的不同异步操作的进度。同一进程中具有匹配类别和 ID 的异步事件会嵌套。
您可以将 0-15 个实参与异步事件相关联,每个实参都用于使用其他信息对异步操作进行注释。您提供给匹配的异步开始、异步即时和异步结束事件的实参会在跟踪记录中合并在一起;您无需重复它们。
C
trace_async_id_t async_id = 555;
TRACE_ASYNC_BEGIN("trace_category", "trace_async_name", async_id, "argument_1", argument_1_value);
TRACE_ASYNC_INSTANT("trace_category", "trace_async_instant_name", async_id);
TRACE_ASYNC_END("trace_category", "trace_async_name", async_id);
C++
trace_async_id_t async_id = 555;
TRACE_ASYNC_BEGIN("trace_category", "trace_async_name", async_id, "argument_1", argument_1_value);
TRACE_ASYNC_INSTANT("trace_category", "trace_async_instant_name", async_id);
TRACE_ASYNC_END("trace_category", "trace_async_name", async_id);
Rust
let async_id = 555;
async_begin!(async_id, "trace_category", "trace_async_name", "argument_1" => argument_1_value);
async_instant!(async_id, "trace_category", "trace_async_instant_name");
async_end!(async_id, "trace_category", "trace_async_name");
在 Perfetto 中,异步事件放置在自己的命名轨道上,而不是放置在发出它的线程的轨道上。异步事件如下所示:
自定义轨道事件
在跟踪记录中,即时事件和时长事件放置在生成它们的线程的轨道上。在某些情况下,您希望在与组件进程关联的专用命名轨道上对相关事件(例如状态机转换、状态更改或电源级别)进行分组。
借助自定义轨道事件,您可以定义 Track 对象一次,然后向该轨道发出即时事件和时长事件。在底层,这是使用虚拟线程 (VThread) 实现的。
如需使用自定义轨道,请执行以下操作:
- 在组件初始化期间创建
Track对象。 - 使用
track_instant!、track_duration!、track_duration_begin!和track_duration_end!向轨道发出事件。
例如:
Rust
// 1. Create the track once (e.g., during component initialization)
let my_track = trace::Track::new("My State Machine");
// 2. Emit a scoped duration that automatically closes at the end of the block
{
track_duration!("trace_category", my_track, "Idle State");
// Do some work...
}
// 3. Emit a transient instant event on the track
track_instant!("trace_category", my_track, "Stimulus Received", "type" => "button_press");
// 4. Manual duration begin/end (e.g., for non-lexical scopes)
track_duration_begin!("trace_category", my_track, "Active State");
// Do some work...
track_duration_end!("trace_category", my_track, "Active State");
进程范围内的共享轨道(建议用于可重复使用的代码)
如果您的跟踪记录调用发生在多次执行的函数或帮助程序模块(例如请求处理程序、事务步骤或下载循环)内,则在函数内本地创建 Track 将为每次调用生成新的虚拟线程 ID。这会导致可视化工具在单独的轨道上呈现每次调用。
如需在整个进程范围内将所有事件分组到同一 可视轨道上,您应使用 std::sync::LazyLock 声明进程范围内的静态轨道:
Rust
// 1. Declare the track as a process-wide static using LazyLock
static DOWNLOAD_TRACK: std::sync::LazyLock<trace::Track> = std::sync::LazyLock::new(|| {
trace::Track::new("http-client-download-blob")
});
// 2. Reference the static track in your function
async fn download_blob_impl(url: &str) {
let track = &*DOWNLOAD_TRACK;
// Scoped duration on the process-wide shared track
track_duration!("trace_category", track, "http-client-download-blob", "url" => url);
}
在 Perfetto 中,自定义轨道干净地嵌套在组件的进程组内,与物理线程并列。轨道始终以 Track 对象名称(例如“My State Machine”)命名,状态显示为实心时长块,而即时事件直接在轨道上显示为小菱形箭头。
实参
您可以在 Fuchsia 跟踪记录中使用以下类型的事件:
- Null 实参
- 数值类型
- 字符串类型
- 指针
- KOID
- 布尔值
Null 实参
Null 实参只是一个名称,没有数据。
例如:
C
TRACE_INSTANT("trace_category", "trace_name", TRACE_SCOPE_THREAD, "SomeNullArg", TA_NULL());
C++
TRACE_INSTANT("trace_category", "trace_name", TRACE_SCOPE_THREAD, "SomeNullArg", nullptr);
Rust
instant!("trace_category", "trace_name", Scope::Thread, "SomeNullArg" => ());
数值类型
数值类型是任何 int 或 float 数据类型。
例如:
C
// Standard int types
TRACE_INSTANT("trace_category", "trace_name", TRACE_SCOPE_THREAD, "Someuint32", TA_UINT32(2145));
TRACE_INSTANT("trace_category", "trace_name", TRACE_SCOPE_THREAD, "Someuint64", TA_UINT64(423621626134123415));
TRACE_INSTANT("trace_category", "trace_name", TRACE_SCOPE_THREAD, "Someint32", TA_INT32(-7));
TRACE_INSTANT("trace_category", "trace_name", TRACE_SCOPE_THREAD, "Someint64", TA_INT64(-234516543631231));
// Doubles
TRACE_INSTANT("trace_category", "trace_name", TRACE_SCOPE_THREAD, "Somedouble", TA_DOUBLE(3.1415));
C++
// Standard int types
TRACE_INSTANT("trace_category", "trace_name", TRACE_SCOPE_THREAD, "Someuint32", 2145);
TRACE_INSTANT("trace_category", "trace_name", TRACE_SCOPE_THREAD, "Someuint64", 423621626134123415);
TRACE_INSTANT("trace_category", "trace_name", TRACE_SCOPE_THREAD, "Someint32", -7);
TRACE_INSTANT("trace_category", "trace_name", TRACE_SCOPE_THREAD, "Someint64", -234516543631231);
// Doubles
TRACE_INSTANT("trace_category", "trace_name", TRACE_SCOPE_THREAD, "Somedouble", 3.1415);
Rust
// Standard int types
instant!("trace_category", "trace_name", Scope::Thread, "Someuint32" => 2145);
instant!("trace_category", "trace_name", Scope::Thread, "Someuint64" => 423621626134123415);
instant!("trace_category", "trace_name", Scope::Thread, "Someint32" => -7);
instant!("trace_category", "trace_name", Scope::Thread, "Someint64" => -234516543631231);
// Doubles
instant!("trace_category", "trace_name", Scope::Thread, "Somedouble" => 3.1415);
字符串类型
字符串类型是任何 string 数据类型。
例如:
C
TRACE_INSTANT("trace_category", "trace_name", TRACE_SCOPE_THREAD, "ping", "pong");
C++
TRACE_INSTANT("trace_category", "trace_name", TRACE_SCOPE_THREAD, "ping", "pong");
Rust
instant!("trace_category", "trace_name", Scope::Thread, "ping" => "pong");
指针
指针在 Perfetto 查看器中以十六进制格式显示。您还可以专门指定跟踪记录实参类型,以防类型推断解析为错误的数据类型。
例如:
C
TRACE_INSTANT("trace_category", "trace_name", TRACE_SCOPE_THREAD, "somepointer", &i, "someotherpointer", &i, TA_POINTER(0xABCD));
C++
TRACE_INSTANT("trace_category", "trace_name", TRACE_SCOPE_THREAD, "somepointer", &i, "someotherpointer", &i, 0xABCD);
Rust
let x: u64 = 123;
instant!("trace_category", "trace_name", Scope::Thread, "SomeOtherPointer" => &x as *const u64);
KOID
KOID 是内核对象的 ID,并且有自己的数据类型,以将其与常规 u64 数据类型区分开。
例如:
C
TRACE_INSTANT("trace_category", "trace_name", TRACE_SCOPE_THREAD, "somekoid", TA_KOID(0x0012));
C++
TRACE_INSTANT("trace_category", "trace_name", TRACE_SCOPE_THREAD, "somekoid", TA_KOID(0x0012));
Rust
let koid: zx::Koid = vmo.get_koid()?;
instant!("trace_category", "trace_name", Scope::Thread, "somekoid" => koid);
布尔值
布尔值显示为 true 或 false。
例如:
C
TRACE_INSTANT("trace_category", "trace_name", TRACE_SCOPE_THREAD, "somebool", TA_BOOL(true));
TRACE_INSTANT("trace_category", "trace_name", TRACE_SCOPE_THREAD, "someotherbool", TA_BOOL(false));
C++
TRACE_INSTANT("trace_category", "trace_name", TRACE_SCOPE_THREAD, "somebool", true);
TRACE_INSTANT("trace_category", "trace_name", TRACE_SCOPE_THREAD, "someotherbool", false);
Rust
instant!("trace_category", "trace_name", Scope::Thread, "somebool" => true);
instant!("trace_category", "trace_name", Scope::Thread, "someotherbool" => false);