Perfetto 多机追踪架构深度解析:traced_relay、机器身份与跨内核时钟同步
【免费下载链接】perfettoProduction-grade client-side tracing, profiling, and analysis for complex software systems.项目地址: https://gitcode.com/GitHub_Trending/pe/perfetto
导读
多机追踪(Multi-machine tracing)是 Perfetto 面向复杂软件系统的一项能力:它能把分布在多台操作系统镜像(宿主机与虚拟机、SoC 与协处理器、多台测试机组成的集群)上的事件,录制成一条单一 trace,在一个统一时间轴上呈现并可用 SQL 查询跨机因果关系。本文将基于仓库中的架构文档,结合traced_relay、traced与 Trace Processor 的源码实现,讲清多机追踪"是什么、各部件如何配合"(记录的具体步骤见 Multi-machine recording),读完后你将掌握 relay 转发模型、机器身份(machine_id)机制、跨内核时钟同步原理,以及数据源分发配置等关键实现细节。
问题陈述:单机服务模型为何在多机场景失效
Perfetto 标准的 service model 隐含一个前提:所有 producer、traced服务与 consumer 共享同一个操作系统镜像。它们通过本地 UNIX socket 访问traced,共享同一个 PID 命名空间,并观察同一个CLOCK_BOOTTIME时钟。
一旦某个 producer 跑在另一个内核上,这个前提立即被打破:
- 没有共享的 filesystem socket——UNIX socket 无法跨内核访问;
- PID 命名空间相互独立——同一 PID 在不同机器上指代不同进程;
- boot 时钟起点不同且各自漂移——
CLOCK_BOOTTIME的零点因内核而异,无法直接比较。
一种朴素的做法是:在每台机器上各自运行traced,录完再用 merging-traces 事后合并。但这种方式下,跨机器时钟对齐的精度只取决于各机器墙钟同步(或手动提供的偏移量),对任何时间敏感的分析(如跨机调度、RPC 时延)都不够可靠。
Live 多机追踪(live multi-machine tracing)的解决思路是:在录制期间实时测量时钟偏移,且不需要在每台机器上复制 buffer 和 consumer 机制。这正是本文介绍的核心架构。
整体架构:一台 host 的 traced + 所有远程机器的 traced_relay
多机设置中,恰好一台机器运行traced(即 host),其余每台机器运行traced_relay,把 producer 侧 IPC 转发到 host。文档中的架构图如下:
Remote machine Host machine ┌────────────────────────┐ ┌────────────────────────────┐ │ traced_probes │ │ traced --enable-relay- │ │ + other producers │ │ endpoint │ │ │ │ │ ▲ │ │ ▼ (local IPC) │ TCP/vsock │ │ (local IPC) │ │ traced_relay ────────┼──────────────►│ relay endpoint │ └────────────────────────┘ │ ▲ │ │ │ │ │ traced_probes / other │ │ local producers │ │ ▲ │ │ │ (consumer IPC) │ │ perfetto cmdline │ └────────────────────────────┘关键角色与职责划分:
traced(host 侧):通过--enable-relay-endpoint在 producer socket 上额外开放 relay endpoint,接受远程traced_relay连接;traced_relay(远程机器侧):在本地 producer socket 上接受 producer 连接,与 host 交换少量元数据后,把 producer IPC 帧通过TCP 或 vsock代理到 host;- consumer(
perfetto命令行或 UI 的 WebSocket bridge):永远只与 host 的traced通信。Trace 配置、buffer 所有权、最终读回都集中在单台机器上。
traced_relay 的"刻意轻薄"设计
从 src/traced_relay/ 目录的源码结构看,traced_relay的实现刻意保持最小化:
- relay_service_main.cc 只是入口,直接调用
perfetto::RelayServiceMain(); - relay_service.h 定义
RelayService与RelayClient两个核心类; - socket_relay_handler.h 负责 socket 间的帧转发。
它不缓冲 trace 数据、不解析 trace packet、不实现任何 consumer 侧功能——只做透明的 IPC 代理。RelayClient的职责按 relay_service.h 中的注释可分为四步:① 用 client socket name 连接 host(支持如vsock://2:10001这样的 vsock 地址);② 连接后发送SetPeerIdentity消息,让 tracing service 知道本 RelayClient 的机器身份;③ 将 socket 交给RelayIPCClient(RelayPortRPC 服务的客户端实现);④ 任何 socket 错误都会通过OnErrorCallback通知上层(RelayService)以便重试连接。此外RelayClient还承担与 host 的时间同步职责,其阶段状态机(Phase::CONNECTING / PING / UPDATE)与SendSyncClockRequest()方法体现了下文将介绍的时钟同步流程。
这种设计带来的直接好处是:buffer 分配、clock snapshot 发出、consumer 读回等复杂逻辑只在 host 上存在一份,远程机器上几乎零状态。
机器身份:SetPeerIdentity 与 machine 表
当traced_relay首次连接 host 时,会发送一条SetPeerIdentity消息,其中包含machine_id_hint。该 hint 的生成逻辑在 relay_service.cc 的RelayService::GetMachineIdHint()中:
- 首选读取
/proc/sys/kernel/random/boot_id(Linux 上可用时); - 否则回退到
uname(2)的哈希加上一个 bootup 时间戳来源(get_pseudo_boot_id()逻辑)。
因此该 hint 对同一内核的多次重连是稳定的,但在不同内核之间互不相同。
host 的traced会把每个唯一 hint 映射为一个小整数MachineId,并给从该 relay 到达的每个TracePacket盖上这个标记(即TracePacket上的machine_id字段)。导入时,Trace Processor 为每台机器在machine表中物化一行。其表结构定义见 metadata_tables.py:
| Column | Description |
|---|---|
id | Trace-Processor 分配的机器 ID。host 恒为0。 |
raw_id | 来自 trace packet 的原始机器标识(host 为0,远程机器非零)。 |
sysname,release,version,arch | 该机器的uname(2)字段。 |
num_cpus | 该内核可见的 CPU 数量。 |
system_ram_bytes,system_ram_gb | 总内存。 |
android_build_fingerprint,android_device_manufacturer,android_sdk_version | 仅 Android 机器填充。 |
跨机数据切片:凡是带每 CPU 或每线程维度的表(thread、cpu、gpu_counter_track等),都携带可空的machine_id列,因此可以用 SQL 按机器切分数据。例如cpu表通过ucpu(全局统一的 CPU 编号)关联到具体机器,其machine_id列可参与 join。目前 UI 对每机 track 的支持仍在演进中,所以machine_idjoin 是回答跨机问题最可靠的方式。
跨机器时钟同步:轻量 ping 协议与时钟图
每台远程机器有自己的CLOCK_BOOTTIME,其 producer 写入的时间戳不能与 host 直接比较。traced_relay通过轻量 ping 协议与 host 的 relay endpoint 交互:发送并接收带时间戳的消息,从而估算每台机器的时钟偏移与往返时间(RTT)。host 将每台远程机器的成对ClockSnapshot以RemoteClockSyncpacket 的形式写入 trace。
从 clock_tracker.cc 的源码可以看出这一机制在导入端的落点:时钟 ID 通过ClockId::Qualify(clock_id, machine_id_, ...)进行机器限定(machine-qualified),使不同机器的同名时钟成为时钟图中的独立节点;远程机器的时钟快照被折叠进同一个时钟图中。其后续处理完全复用 Clock Synchronization 中已有的单机机制:
- Trace Processor 把跨机偏移折叠进它本就要为
CLOCK_REALTIME、CLOCK_MONOTONIC等构建的时钟图; - 导入时把每个事件解析到单一全局 trace 时钟;
- 数据源无需任何额外工作。
值得注意的是,同一个时钟图同样支撑 post-hoc trace merging(事后合并)——区别只在于跨机边的来源:live 录制来自 ping 协议,事后合并来自墙钟 rendezvous 或 perfetto_manifest 清单文件。
数据源分发:trace_all_machines 与 machine_name_filter
默认情况下,traced只把数据源分发给 host 机器上的 producer。要从远程机器采集数据,consumer 的TraceConfig必须显式选择,二选一:
- 全局:
trace_all_machines: true; - 按数据源:
DataSource.machine_name_filter。
若两者都不设置,远程机器上的traced_probes虽然仍会注册、并作为machine表的一行出现,但永远不会被分配请求的数据源,因此不会有任何事件流出。对应 proto 定义见 trace_config.proto:
DataSource.machine_name_filter(repeated string machine_name_filter = 4)按机器名过滤。机器名的确定优先级为:PERFETTO_MACHINE_NAME环境变量 → Android 系统属性persist.traced_relay.machine_name→uname -s的 utsname sysname(如Linux)。字面量"host"是运行traced那台机器的同义词;trace_all_machines(optional bool trace_all_machines = 43):为true时,远程 producer(通过traced_relay连接的机器)中的数据源默认被匹配;为false(默认)时只匹配 host。显式设置的machine_name_filter优先级更高。
版本兼容注意事项:trace_all_machines在perfetto v54引入;v54 之前的版本默认匹配所有机器。文档建议,要跨过这个版本边界保持兼容,要么把该字段设为true,要么给所有数据源显式设置machine_name_filter。
一个容易踩的坑:同一内核无法冒充"两台机器"
同一内核上的两个 producer 即使分别注册,也不能当作"两台机器"用于测试。原因在于:两个traced_probes实例会竞争同一个/sys/kernel/tracing/ring buffer,而 per-CPU 事件会被任意切分到两个machine_id之间——trace 看起来有效,实际却静默撕裂(silently torn)。多机设置必须需要两个内核(两台机器、host 加 VM、拥有各自内核命名空间的独立容器等)。
限制与约束
多机架构文档明确列出了以下约束:
traced_relay不能与traced同机运行——两者都要绑定本地 producer socket。设置中每台机器要么运行traced(host),要么运行traced_relay(其余所有机器);- 每台远程机器必须有到达 host relay endpoint 的网络路径(TCP 或 vsock);
- 跨机器时钟对齐精度取决于 ping 协议对偏移的测量;粗略对齐的墙钟(NTP 等)有助于首批 snapshot,但并非严格必需;
- UI 的每机 track 渲染仍在演进,当前按机器切分跨机数据的权威方式仍是查询
machine表与machine_id列的 SQL。
实战速览:两台 Linux 主机的多机录制
架构文档聚焦"是什么"与"如何拼装",具体的逐步操作记录见 Multi-machine recording。这里给出核心流程梗概(host运行traced,guest运行traced_relay):
- host 启动
traced并监听 TCP(--enable-relay-endpoint让该 socket 同时接受 relay 连接):PERFETTO_PRODUCER_SOCK_NAME=0.0.0.0:20001 \ tracebox traced --enable-relay-endpoint若还需保留本地 AF_UNIX producer socket,可同时列出两个 socket 并用更窄的
--enable-relay-endpoint-on指名哪个 socket 承载RelayPort(注意该 flag 只是从PERFETTO_PRODUCER_SOCK_NAME中已列出的 socket 里选择,不会引入新端点)。 - host 启动
traced_probes:PERFETTO_PRODUCER_SOCK_NAME=127.0.0.1:20001 sudo -E tracebox traced_probes(sudo -E保留环境变量以获取 ftrace 权限)。 - guest 启动
traced_relay:PERFETTO_RELAY_SOCK_NAME=<host-ip>:20001 tracebox traced_relay,成功时输出形如Started traced_relay, listening on /tmp/perfetto-producer, forwarding to <host-ip>:20001的启动行。 - guest 启动
traced_probes:直接sudo tracebox traced_probes(无环境变量时自动连接默认 UNIX socket,即traced_relay监听的路径)。 - host 侧用显式
TraceConfig录制:多机追踪要求显式配置(tracebox perfetto -t 10s ...简写只会录 host 本机)——配置中必须包含trace_all_machines: true或各数据源的machine_name_filter,例如:buffers { size_kb: 32768 fill_policy: RING_BUFFER } trace_all_machines: true data_sources { config { name: "linux.ftrace" ftrace_config { ftrace_events: "sched/sched_switch" } } } duration_ms: 10000随后执行
tracebox perfetto --txt -c config.pbtx -o trace.pftrace。 - 验证两台机器都在 trace 中:在 SQL 查询视图中执行
SELECT id, raw_id, sysname, release, arch, num_cpus FROM machine;,期望两行,id = 0恒为 host;再通过cpu表 join 按机器统计事件数:SELECT cpu.machine_id, COUNT(*) AS num_events FROM ftrace_event JOIN cpu USING (ucpu) GROUP BY cpu.machine_id;
常见故障排查要点:machine表只有一行通常是连通性问题(检查端口可达、防火墙、host 是否绑定了0.0.0.0而非127.0.0.1);traced_relay立即退出并打印用法说明PERFETTO_RELAY_SOCK_NAME未设置。
总结与下一步
多机追踪架构的核心设计哲学可以概括为:让一台 host 承担全部状态(buffer、consumer、配置),让远程机器只做"轻薄"的 IPC 转发与时钟测量,再通过SetPeerIdentity建立机器身份、通过 ping 协议把跨内核时钟偏移折叠进既有的时钟图,最终在 Trace Processor 侧得到一张带machine_id维度的统一时间轴。这与 trace-processor-architecture 中"导入期统一时钟图"的设计一脉相承。
后续深入方向:
- Multi-machine recording——两台 Linux 主机录制多机 trace 的完整逐步演练;
- Trace merging——事后把独立录制的 trace 合并进同一多机模型;
- Clock Synchronization——跨机偏移在导入时折叠进的单机时钟同步图;
machine表参考——由SetPeerIdentity填充的表完整 schema。
【免费下载链接】perfettoProduction-grade client-side tracing, profiling, and analysis for complex software systems.项目地址: https://gitcode.com/GitHub_Trending/pe/perfetto
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考