ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Perfetto TraceConfig 深度解析:用一个配置文件驱动整个性能追踪系统

Perfetto TraceConfig 深度解析:用一个配置文件驱动整个性能追踪系统 Perfetto TraceConfig 深度解析用一个配置文件驱动整个性能追踪系统【免费下载链接】perfettoProduction-grade client-side tracing, profiling, and analysis for complex software systems.项目地址: https://gitcode.com/GitHub_Trending/pe/perfetto本文以 Perfetto 官方文档docs/concepts/config.md为主线系统讲解 TraceConfig 的结构、缓冲区策略、动态缓冲映射、PBTX/二进制两种配置格式、长 trace 流式落盘、trace 压缩、数据源级配置、生产者过滤与触发器机制并结合仓库中的 proto 定义trace_config.proto、data_source_config.proto与追踪服务实现tracing_service_impl.cc逐点验证帮助你既能直接写出可运行的 trace 配置也能理解配置在traced服务内被解析、路由的完整链路。设计前提数据源默认空闲与多数常驻日志系统如 Linux 的 rsyslog、Android 的 logcat不同Perfetto 中所有 trace 数据源data source默认都是空闲的只有在被显式指令要求时才记录数据——具体而言当一个或多个tracing session 处于激活状态时配置中被列出的数据源才会开始工作。tracing session 由perfetto命令行客户端或任何 Consumer发起并携带一个 TraceConfig。这个按需激活的设计直接决定了 TraceConfig 的地位它既是启动追踪的开关也是描述采什么、采多久、写到哪的唯一契约。一个最小可运行的配置示例官方文档给出的最简配置如下duration_ms: 10000 buffers { size_kb: 65536 fill_policy: RING_BUFFER } data_sources { config { name: linux.ftrace target_buffer: 0 ftrace_config { ftrace_events: sched_switch ftrace_events: sched_wakeup } } }含义是开启linux.ftrace内核数据源只采集sched_switch与sched_wakeup两个调度事件写入 64 MB 的环形缓冲区持续 10 秒。使用时perfetto --txt -c config.pbtx -o trace_file.perfetto-trace仓库内提供了更完整的配置样例目录 test/configs/包括 ftrace.cfg、heapprofd.cfg、long_trace.cfg 等 25 个真实可用的配置。根据 test/configs/README.md这些.cfg文件会在构建时被序列化为 protobuf 二进制然后推到设备上执行$ adb push out/some_gn_config/ftrace.cfg.protobuf /data/local/tmp $ adb push out/some_gn_config/perfetto /data/local/tmp $ adb shell cd /data/local/tmp ./perfetto -c ftrace_cfg.protobuf -o out.protobufTraceConfig 的三大职责TraceConfig 是一个 protobuf 消息schema 定义在 protos/perfetto/config/trace_config.proto。它承担三件事整个追踪系统的全局行为trace 最大时长duration_ms、内存缓冲区数量与大小buffers、输出 trace 文件的最大尺寸max_file_size_bytes等启用哪些数据源及其各自配置例如为内核追踪数据源参见 内核调度追踪指定要开启哪些 ftrace 事件为堆分析器参见 native 堆分析器指定目标进程名与采样率。各数据源的详细配置见文档站 data sources 章节{数据源} x {缓冲区}的映射关系即每个数据源写入哪个缓冲区见下文动态缓冲映射。服务端的分发机制追踪服务traced实际上是一个配置分发器configuration dispatcher它从perfetto命令行客户端或任意 Consumer收到完整配置后将配置的不同部分转发给各个已连接的 Producer。当一个 tracing session 被启动时服务会读取 TraceConfig 的外层字段如duration_ms、buffers据此决定自身行为读取data_sources列表对于配置中列出的每个数据源若其名字如示例中的linux.ftrace已在服务中注册服务就会请求对应的 producer 进程启动该数据源并把DataSourceConfig子消息的原始字节原封不动地传递过去这一点对前向兼容至关重要见后文。trace_config.proto中TraceConfig的关键外层字段字段号来自源码注释Next id: 50此处只列与本文相关者字段字段号作用buffers1重复的BufferConfig定义内存缓冲区data_sources2重复的DataSource定义启用的数据源builtin_data_sources20控制服务内置数据源时钟快照、系统信息等duration_ms3trace 时长注意它不统计系统挂起时间write_into_file8周期性把缓冲区刷入输出文件file_write_period_ms9覆盖默认落盘周期最小 100msmax_file_size_bytes10写入达到该字节数后停止 sessiontrigger_config/activate_triggers17 / 18触发器声明与激活compression/compression_type47 / 24(废弃)压缩编解码选择Buffers数量、大小与填充策略buffers段定义追踪服务拥有的内存缓冲区的数量、大小与策略# Buffer #0 buffers { size_kb: 4096 fill_policy: RING_BUFFER } # Buffer #1 buffers { size_kb: 8192 fill_policy: DISCARD }每个缓冲区的填充策略FillPolicy有两种取值在 trace_config.proto 中定义RING_BUFFER默认枚举值 1缓冲区行为如同环形缓冲区写满后回卷wrap around新数据覆盖最旧的 trace 数据DISCARD枚举值 2缓冲区写满后停止接收数据后续写入尝试全部被丢弃。proto 注释进一步明确只要缓冲区还有空间、或读者追上了写者它的行为与RING_BUFFER一致一旦写者碰到未读块就停止接受新数据。警告DISCARD对在 trace 结束时刻才提交数据的数据源会产生意外副作用——若缓冲区中途写满这些数据源在收尾时提交的数据会被直接丢弃。一个有效的 trace 配置至少必须定义一个缓冲区。最简单的情况是所有数据源写入同一个缓冲区。这在多数基础场景没问题但当不同数据源写入速率差异悬殊时就会出问题。文档中给出的经典反例内核调度追踪器在典型 Android 手机上约 10000 events/s写入约 1 MB/s内存状态轮询linux.sys_stats一类把/proc/meminfo内容写入缓冲区配置为每 5 秒轮询一次每次约 100 KB。若二者共用一个 4 MB 的RING_BUFFER大多数 trace 里只会含有一帧内存快照甚至很可能一帧都没有——尽管第 2 个数据源本身工作完全正常。因为在 5 秒的轮询间隔内调度数据源就能把整个缓冲区填满把内存快照数据冲出缓冲区。解决方案就是下文的动态缓冲映射。动态缓冲映射target_bufferPerfetto 中的数据源与缓冲区的映射是动态的一个 session 最简单可以只定义一个缓冲区此时所有数据源默认都写入它需要隔离时用每个数据源配置里的target_buffer字段指定缓冲区的下标0 基data_sources { config { name: linux.ftrace target_buffer: 0 # -- 写入 buffer 0 ftrace_config { ... } } } data_sources { config { name: linux.sys_stats target_buffer: 1 # -- 写入 buffer 1 sys_stats_config { ... } } } data_sources { config { name: android.heapprofd target_buffer: 1 # -- 同样写入 buffer 1 heapprofd_config { ... } } }从源码结构看target_buffer有一个容易忽略的双重语义data_source_config.proto 的注释配置下发时它是相对TraceConfig.buffers数组的 0 基下标表达映射关系当TracingService向 producer 发出SetupDataSource/StartDataSource时会把该字段覆写为全局缓冲区 id受其他并发 session 影响producer 用它调用CreateTraceWriter。自 v54 起还新增了target_buffer_name字段号 11与BufferConfig.name字段号 7允许按名字而非下标引用缓冲区可读性更好且不易出错若两个字段同时给出且指向不同缓冲区服务会直接拒绝该配置。这种双字段并存的设计正是为了让同一份配置能同时兼容新旧版本服务。真实配置中的用法可参考 test/configs/ftrace.cfg所有数据源linux.ftrace、linux.process_stats、linux.inode_file_map、perfetto.metatrace都映射到target_buffer: 0并同时为perfetto.traced_probes这个 producer 单独指定共享内存大小producers { producer_name: perfetto.traced_probes shm_size_kb: 4096 page_size_kb: 4 } duration_ms: 10000配置格式PBTX 文本 vs 二进制 protobufperfetto命令行客户端接受 TraceConfig 有两种方式文本格式PBTX面向人工调试与探索的首选格式。PBTXProtoBuf TeXtual representation是 proto 的文本表示schema 即 trace_config.proto。使用时给perfetto加--txt标志声明配置按 PBTX 解释perfetto -c /path/to/config.pbtx --txt -o trace_file.perfetto-trace注意--txt选项自 Android 10Q才引入更早的版本只支持二进制格式。警告机器对机器脚本、工具、benchmark的交互不要使用文本格式——字段重命名、枚举变整数等 schema 演进都会导致它更容易被破坏。二进制格式面向 M2M 场景的首选格式把 PBTX 交给 protobuf 的protoc编译器编码为二进制再传给perfetto此时不带--txtcd ~/code/perfetto # Android 源码树中位于 external/perfetto protoc --encodeperfetto.protos.TraceConfig \ -I. protos/perfetto/config/perfetto_config.proto \ config.txpb \ config.binperfetto -c config.bin -o trace_file.perfetto-trace这里编码用的聚合 proto 是 protos/perfetto/config/perfetto_config.proto其TraceConfig消息位于该文件第 5127 行附近与独立的 trace_config.proto 内容对应。流式长 traceStreaming long traces默认情况下Perfetto 把完整 trace 缓冲区保存在内存中直到 session 结束才一次性写入-o指定的目标文件。这样做是为了降低追踪系统对系统的性能侵入性但代价是trace 的最大尺寸被限制在设备物理内存之内。在 benchmark、难以复现问题等场景下往往需要捕获远超内存的 trace。Perfetto 为此提供周期性把缓冲区落盘的模式涉及TraceConfig的三个字段write_into_filebool字段号 8为 true 时周期性地把 trace 缓冲区排空到输出文件。启用后用户态缓冲区只需大到能容纳两个落盘周期之间的数据即可。典型 trace 速率约 1–4 MB/s16 MB 的内存缓冲区大约能撑 4 秒左右的写入周期file_write_period_msuint32字段号 9覆盖默认的落盘周期5 秒。周期越短所需缓冲区越小但追踪的性能侵入越高。源码强制了 100 ms 下限tracing_service_impl.cc 中若该值为 0 则回退到常量kDefaultWriteIntoFilePeriodMs5000ms定义于该文件第 173 行若小于kMinWriteIntoFilePeriodMs则被钳制为 100 msmax_file_size_bytesuint64字段号 10写入达到 N 字节后停止 tracing session用于给 trace 体积封顶。一个完整可运行的示例是 test/configs/long_trace.cfg30 分钟长 trace# 周期性地把 trace 缓冲区刷入目标文件 write_into_file: true # 最大时长30 分钟 duration_ms: 1800000 # 每 2.5 秒把用户态缓冲区写入文件一次 file_write_period_ms: 2500 # 周期性地把共享内存缓冲区中的 trace 提交到中心缓冲区。 # 否则导入该 trace 时 trace_processor_shell 和 traceconv # 需要加 --full-sort 选项。 flush_period_ms: 20000 # 缓冲区必须足以容纳 file_write_period_ms 内的 trace 数据。 # 经验值约 10–20 MB/sfile_write_period_ms 约 3s - 30 MB。 buffers { size_kb: 32768 fill_policy: RING_BUFFER } data_sources { config { name: linux.ftrace target_buffer: 0 ftrace_config { # 这两个参数只影响内核 trace 缓冲区大小 # 及其向用户态缓冲区搬运的频率 buffer_size_kb: 16384 drain_period_ms: 250 ftrace_events: cpu_frequency ftrace_events: sched_switch # ...完整列表见该文件 } } } data_sources { config { name: linux.process_stats target_buffer: 0 } }注意其中flush_period_ms: 20000的作用周期性向所有数据源发起 Flush保证数据尽早从共享内存提交到中心缓冲区若不开启分析工具导入时需要--full-sort参数补齐排序。小结要抓长 trace只需设write_into_file: true、设一个较大的duration_ms、并使用 32 MB 或更大的内存缓冲区。压缩 trace追踪服务可以在写出时压缩 trace显著减小落盘文件以及从设备拉取的数据体积代价是追踪期间额外的 CPU 开销。压缩默认关闭通过 TraceConfig 显式开启。Perfetto 的读取端会透明解压压缩后的 trace 可以直接在 Perfetto UI 和 Trace Processor 中打开无需任何手工步骤。选择编解码器设置compression字段通过填充哪个子消息来选择编解码器zstd推荐与 deflate 速度相近压缩率更好deflatezlib较早的编解码器保留用于兼容。zstd推荐# 其余配置buffers、data_sources 等省略 compression { zstd {} }deflate# 其余配置buffers、data_sources 等省略 compression { deflate {} }调整 zstd 级别zstd 暴露一个压缩级别用 CPU 换更小的文件compression { zstd { level: 9 } }取值范围1最快到22最小超过最大值的级别会被钳制0或不设置使用 zstd 默认级别3对大多数 trace 是良好平衡点负数级别同样合法对应更快但压缩率更低的模式。这与 trace_config.proto 中Zstd.level的注释完全一致。跨服务版本兼容compression字段自 Perfetto v58Android 26Q3引入。服务只会读取它认识的编解码器并选择字段号最高的那个因此一份配置可以同时面向新旧服务两个子消息都填上v58 服务会用zstd字段号 2旧服务回落到deflate字段号 1compression { deflate {} zstd {} }这一按字段号从大到小分支的策略可以直接在服务端代码中验证tracing_service_impl.cc 先检查compression.has_zstd()在PERFETTO_BUILDFLAG(PERFETTO_ZSTD)编译宏保护下命中即用ZstdCompressFn(target, level)否则再检查compression.has_deflate()且该分支同时兼容遗留的compression_type COMPRESSION_TYPE_DEFLATE。compression出现之前的服务使用现已废弃的compression_type字段仅支持 deflate。它仍被尊重但当两个字段同时设置时compression优先。注意编解码器只有在对应构建开关启用时才被编译进二进制enable_perfetto_zlib、enable_perfetto_zstd。未启用的构建——尤其是进程内 SDK 后端——会忽略压缩请求直接输出未压缩 trace。若要从应用内直接写 trace 并启用压缩参见 SDK 构建开关说明。在 Perfetto 之外读取压缩 trace若需要拿到未压缩的原始 protobuf例如喂给非 Perfetto 工具可以用trace_processor展开压缩的 packets./trace_processor util decompress_packets trace.perfetto-trace trace.decompressed数据源专属配置与前向兼容除了 trace 级参数配置还定义各数据源专属的行为。在 proto 层面这体现在TraceConfig的DataSourceConfig子消息中。摘取自 data_source_config.protomessage TraceConfig { ... repeated DataSource data_sources 2; } message DataSource { optional protos.DataSourceConfig config 1; ... } message DataSourceConfig { optional string name 1; ... optional FtraceConfig ftrace_config 100 [lazy true]; ... optional AndroidPowerConfig android_power_config 106 [lazy true]; }ftrace_config、android_power_config等都是数据源专属配置的例子。追踪服务完全不解析这些字段的内容而是把整个DataSourceConfig对象按名字路由给同名注册的数据源。字段号有明确约定源码注释低字段号到 99 为止保留给不属于任何特定数据源、需要traced服务处理的公共字段name、target_buffer、tracing_session_id、buffer_exhausted_policy等100 及以上全部是数据源专属配置且必须标注[lazytrue]。该标注对 protozero 代码生成器有特别含义与普通嵌套消息不同它生成的是原始字节访问器如const std::string ftrace_config_raw()而不是const protos::FtraceConfig ftrace_config()目的是避免在实现数据源侧的代码中注入过多#include依赖、避免二进制体积膨胀。此外还预留了legacy_config字段号 1000作为自由文本配置的逃生舱。向后/向前兼容追踪服务把DataSourceConfig消息的原始二进制块直接路由给同名数据源不做解码再重编码。因此即使配置中包含服务构建时还不认识的新字段服务也会把DataSourceConfig原样传递给数据源。这使得引入新数据源无需服务预先了解它——这正是把整块 proto 透传设计带来的前向兼容能力。文档同时坦陈了一个局限TODO目前扩展DataSourceConfig增加自定义 proto 仍需修改 Perfetto 仓库里的data_source_config.proto对外部项目不理想长期计划是为非上游扩展保留一段字段号范围、并为客户端代码提供通用模板化访问器。在此之前官方接受直接上游提交为自定义数据源引入临时配置字段。多进程数据源与生产者过滤部分数据源是单例的——例如 Android 上随 Perfetto 提供的调度追踪器全系统只有一个数据源归traced_probes服务所有。但一般情况下多个进程可以声明同名数据源例如使用 Perfetto SDK 做用户态插桩时。若多个 producer 声明了同一数据源当某个 tracing session 的配置指定了该数据源Perfetto 默认会要求所有声明它的进程启动它。如需把启用范围限制到特定进程或进程集合可用producer_name_filter精确匹配与producer_name_regex_filter正则匹配二者均为repeated语义为 OR——列出[foo, bar]会在两个进程若存在上都启用。DataSource消息还支持machine_name_filter字段号 4用于多机场景下按机器名过滤。Perfetto 的典型运行时模型是一个进程 一个 Producer一个 Producer 通常承载多个数据源。示例——只在 Chrome 与 Chrome Canary 上启用track_event数据源buffers { size_kb: 4096 } data_sources { config { name: track_event } # 仅在 Chrome 与 Chrome canary 上启用该数据源 producer_name_filter: com.android.chrome producer_name_filter: com.google.chrome.canary }触发器Triggers正常条件下tracing session 的生命周期与perfetto命令行调用一一对应配置传入时开始记录duration_ms到期或客户端退出时结束。Perfetto 另提供基于触发器的启动/停止模式在 trace 配置中预先声明——一组触发器本质是自由字符串每个触发器命中时应启动还是停止trace以及启动/停止的延迟。为什么需要触发器安全模型为什么不直接启动 perfetto 或kill -SIGTERM它根本原因在于安全模型在多数部署形态如 Android中只有特权实体如 adb shell能配置/启动/停止追踪应用是非特权的不能控制追踪。触发器给非特权应用提供了有限控制 tracing session 生命周期的途径特权 Consumer正常有权启动追踪的实体如 Android 上的 adb shell预先声明可能的触发器名字及其行为非特权实体任意普通应用进程可以激活这些触发器但对触发器做什么没有发言权只能上报某事件发生了。触发器可通过命令行工具信号发出/system/bin/trigger_perfetto trigger_name也可以启动一个独立 trace session其配置只使用activate_triggers: trigger_name字段——此时perfetto客户端会忽略其余配置转而以 producer 身份连接服务并发送触发器见 trace_config.proto 中activate_triggers的注释。启动型触发器START_TRACING启动型触发器允许在某个关键事件发生后才真正开始记录。配置中声明START_TRACING模式会使 tracing session 保持空闲不记录任何数据直到触发器命中或trigger_timeout_ms超时。注意duration_ms与触发器 trace 不能同时使用。示例配置# 若 myapp_is_slow 命中trace 开始记录数据并在 5s 后停止 trigger_config { trigger_mode: START_TRACING triggers { name: myapp_is_slow stop_delay_ms: 5000 } # 若 30s 内没有任何触发器命中trace 将在未记录任何数据的情况下结束 trigger_timeout_ms: 30000 } # 其余配置照常 buffers { ... } data_sources { ... }proto 中每个Trigger还提供更精细的控制字段trace_config.protoproducer_name_regex限制哪些 producer 可激活该触发器、max_per_24_h24 小时滚动窗口内的最大触发次数、skip_probability0–1 的概率跳过防止高频触发器主导 trace 收尾。停止型触发器STOP_TRACING与飞行记录仪模式STOP_TRACING触发器允许在触发器命中时提前结束 trace。该模式下 trace 在perfetto客户端启动时立即开始记录与常规相同触发器仅作为提前收尾信号。由此可以组合出飞行记录仪flight-recorder模式用RING_BUFFER缓冲区 STOP_TRACING触发器启动 tracetrace 会循环录制直到肇事事件被检测到才收尾定稿。这对于根因在最近过去的事件非常关键例如应用检测到慢滚动、掉帧# 若 missed_frame 命中trace 在 1s 后停止 trigger_config { trigger_mode: STOP_TRACING triggers { name: missed_frame stop_delay_ms: 1000 } # 若 30s 内无触发器命中trace 在 30s 后结束 trigger_timeout_ms: 30000 } # 其余配置照常 buffers { ... } data_sources { ... }补充一点源码层面的背景TriggerConfig.TriggerMode枚举中除上述两种外还有CLONE_SNAPSHOT值 4注释指明仅建议在 Android V / Perfetto v38 使用旧版服务存在导致 session 无限延长的 bug以及一个被保留的值 3——这正是当年CLONE_SNAPSHOT首次实现时的编号因 bug 改到了 4 以避免旧配置踩坑。跨版本部署时可用use_clone_snapshot_if_available标志要求trigger_mode设为STOP_TRACING在新服务上获得快照行为、旧服务上回退为停止行为。Android 上使用 adb 的注意事项在 Android 上通过adb shell操作时有几个坑原文档 Android 章节全部保留CtrlC正常情况下优雅终止 trace不会通过adb shell perfetto传播只有基于交互式 PTY 的adb shell会话才会在 Android 12 之前的非 root 设备上由于 SELinux 规则过于严格配置只能通过cat config | adb shell perfetto -c --表示 stdin传递Android 12 起可以使用/data/misc/perfetto-configs目录存放配置Android 10 之前的设备上adb 无法直接 pull/data/misc/perfetto-traces可用adb shell cat /data/misc/perfetto-traces/trace trace绕过捕获较长 tracebenchmark、CI 场景时使用PID$(perfetto --background)启动再以kill $PID停止。小结与延伸阅读TraceConfig 是 Perfetto 中一份配置驱动一切的枢纽外层字段决定服务行为时长、缓冲区、落盘、压缩、触发器data_sources段按名字路由并原样透传数据源专属配置target_buffer建立数据源与缓冲区的动态映射。理解服务只认外层字段、对专属配置做透传这一条分发规则就能同时解释它的前向兼容能力与lazytrue生成的原始字节访问器设计。延伸阅读均为仓库内文档TraceConfig 参考proto 定义 与 DataSourceConfig 参考缓冲区与数据流服务模型Consumer / Producer完整配置样例目录Perfetto SDK用户态数据源Perfetto UI 查看 trace。【免费下载链接】perfettoProduction-grade client-side tracing, profiling, and analysis for complex software systems.项目地址: https://gitcode.com/GitHub_Trending/pe/perfetto创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表