系统 Trace 能告诉你线程什么时候运行、CPU 跑在哪个核、FrameTimeline 哪一帧超时;在启用 Binder/ftrace/sched 证据时,也能还原 Binder 调用和等待线索。但它不知道播放器正在解哪一个 frame id,也不知道游戏引擎正在加载哪个 scene,更不知道一个业务任务在队列里排了多久。
第 13 篇只回答一个问题:业务语义怎么进入 Trace。系统能告诉你主线程慢了,Track Event 负责告诉你当时在 decode 哪一帧、哪个队列堵住、哪个请求跨线程流转。两边合在一起,才能把“主线程慢了 18ms”缩小到 frame 1082 附近的纹理上传候选区间,再结合 RenderThread/HWUI、GPU 和 FrameTimeline 继续验证。
文章按一条接入路线展开:先判断什么时候真的需要业务语义、SDK 接入边界在哪里;再约定一份业务阶段字典,让事件命名先于代码确定;选定 in-process / system backend 解决不同问题;给出最小接入骨架;用 category 当线上开关、和系统 Trace 一起抓;跨线程任务用 flow 或独立 track 串起来;Counter 只放状态、事件名要当 SQL 接口;最后是控制写入量、App 侧现场 Trace 协议、以及 Trace package 怎么带上下文回到分析侧。
什么时候需要业务语义
没有业务语义缺口时,不需要接 SDK。先按场景选工具:
| 场景 | 建议 |
|---|---|
| Java/Kotlin 层临时标记函数耗时 | 用 android.os.Trace 或 AndroidX tracing,抓取时打开应用 atrace |
| Android native / C++ 模块要长期埋点 | 用 Perfetto SDK 的 Track Event |
| 跨平台引擎、游戏、播放器、Camera pipeline | 用 Track Event,加稳定参数和 counter |
| 要输出复杂结构化状态,普通 slice/counter 不够 | 再考虑自定义 DataSource |
| 只想知道系统线程状态、Binder、频率 | 先用系统数据源,没业务语义缺口时不用接 SDK |
Android-only 场景里,如果 android.os.Trace 和 NDK ATrace_* 已经够用,官方也建议继续使用它们。SDK 仍然适合 Android 的 native、跨平台和结构化业务语义场景;简单 Android-only section 不必为了形式替换 ATrace。它们会进入 Perfetto 的 atrace 路径,适合普通 App 函数标记;抓取时要在 linux.ftrace 里配置 atrace_apps: "com.example.app" 或通过命令行 -a com.example.app 打开对应 App。
Perfetto SDK 更适合 native 模块和跨平台模块。它直接产生 Track Event,支持 category、slice、counter、flow、参数、独立 track,也方便和系统 Trace 放在同一个时间轴上。
App 侧 tracing 能力先按边界选:
| 能力 | 典型用途 | 版本和现场边界 |
|---|---|---|
android.os.Trace / AndroidX Trace |
Java/Kotlin 粗粒度 section | 走 atrace;Android 12/API 31+ App tracing 默认对所有 App 可用,API 29/30 非 debuggable App 依赖 profileable,更低版本或特殊场景可用 AndroidX forceEnableAppTracing();采集侧仍要打开 atrace_apps |
NDK ATrace_* |
native 粗粒度 section | begin/end/isEnabled 从 API 23 起可用,async/counter 类能力有更高 API 边界;同样走 atrace |
AndroidX tracing-perfetto |
App 自己的启动期/本地 Perfetto 采集 | 解决 App 内采集和初始化,不等同于系统冷启动全程分析 |
Perfetto SDK kSystemBackend |
native / 跨平台模块写 Track Event 到系统 Trace | Android 9+ 平台包含 tracing 服务,但 P/Q 尤其非 Pixel 设备要验证 traced 是否启用和 producer socket 是否可达;Android 11+ 默认条件更稳定 |
ATrace 也有开销。热循环、每次锁尝试、每个对象都打 android.os.Trace / ATrace_*,会让 JNI、trace_marker 和内核路径本身扰动结果。Java/Kotlin 和 NDK ATrace 更适合粗粒度 section;高频业务状态优先用 SDK category gating、sampling,或者默认关闭的 debug category。
SDK 接入边界
Perfetto SDK 里有两层能力:
| 能力 | 适合表达 | 代价 |
|---|---|---|
| Track Event | 函数耗时、业务阶段、队列长度、frame id、请求 id、跨线程关系 | 接入成本低,Trace Processor 和 UI 原生支持 |
| Custom DataSource | 自定义 protobuf 状态、高频结构化数据、专用二进制格式 | 需要维护 schema,通常还要给 Trace Processor 补解析 |
只要 slice、counter、flow 和参数能表达问题,优先用 Track Event。自定义 DataSource 的门槛更高,不适合作为第一版埋点方案。
自定义 DataSource 适合两类情况:一类是普通 slice、counter 和 debug annotation 表达不了结构,比如要周期性转储一个子系统状态;另一类是数据量太大,需要强类型 schema 减少每条事件的体积。
它的代价也更直接:Trace Processor 默认不知道你的 protobuf,后续要补解析、查询表或 Trace Summary 输出。第一版先用 Track Event,能少维护一整套数据格式。
业务阶段字典先于代码
Track Event 不是把宏塞进每个函数。先定义业务阶段字典,再写代码。字典要让后续 SQL、报告和看板知道:事件叫什么、属于哪个阶段、字段单位是什么、缺字段时怎么降级。
event_name |
category |
phase |
必填参数 | 可选参数 | 单位/基数 | 采样 | 报告指标 |
|---|---|---|---|---|---|---|---|
DecodeFrame |
player |
decode | frame_id、stream_id、codec |
width、height |
frame_id 在 stream_id 内唯一 |
每帧一次,debug 字段按需开 | decode_dur_ms、decode_max_ms |
UploadTexture |
rendering |
upload | frame_id、texture_bytes |
surface_id |
texture_bytes 用 bytes |
每帧最多一次 | upload_dur_ms、upload_bytes |
RenderSubmit |
rendering |
submit | frame_id、surface_id |
queue_depth |
queue_depth 用 items |
每帧一次 | submit_dur_ms、queue_depth_items |
WaitForBuffer |
camera |
wait | request_id、buffer_queue |
producer |
request_id 在 session 内唯一 |
只在等待超过阈值时记录 | wait_for_buffer_ms |
字段改名、删字段、改单位,都要提升 arg_schema_version。长期报告字段不要叫 debug.frame_id,而应叫业务 schema 里的 frame_id / request_id,并记录 frame_id_domain、nullable_policy 和兼容策略。缺 RenderSubmit 时,报告只能输出 decode/upload 阶段,不能写端到端结论;字段里应写 missing_marker=RenderSubmit、evidence_grade=partial。
两种 backend 解决不同问题
Perfetto SDK 可以用 in-process backend,也可以用 system backend:
| Backend | 谁控制 Trace | 适合场景 |
|---|---|---|
kInProcessBackend |
App 自己创建 tracing session | 单进程验证、离线测试、只看业务事件 |
kSystemBackend |
外部 perfetto 命令或系统服务控制 |
和 sched、freq、Binder、FrameTimeline 合并分析 |
Android 性能分析里更常用 system backend。App 在这里只是 producer,系统 tracing session 决定什么时候开始、什么时候停止。这个边界要写进设计:收集系统 Trace 时,App 不能自己读取整份系统 Trace,避免拿到其他进程的数据。
Android 9(P) 虽然已有 Perfetto/traced,但命令行文本配置 --txt 从 Android 10(Q) 起才更适合直接使用;P 设备通常要走二进制 TraceConfig。实验室和本地问题复现适合用 system backend,线上产品默认接入时还要单独评估兼容、包体和开关策略。
最小接入骨架
最小接入只解决三件事:稳定 category、system backend、可查询事件名。native 模块接入骨架如下。
1 | // tracing_categories.h |
头文件只声明 category。category 名会进入 Trace 和后续 SQL,名字要稳定,不要把动态 id、页面实例号或请求号塞进 category 名里。
1 | // tracing_categories.cc |
PERFETTO_TRACK_EVENT_STATIC_STORAGE() 给 Track Event 注册需要的静态存储留位置。它只应该出现在一个 .cc 文件里,避免多重定义。
1 | // tracing_init.cc |
初始化阶段选择 backend,并把前面定义的 category 注册给 Track Event。Android 系统 Trace 场景里通常用 kSystemBackend,这样 App 事件才能和系统 sched、Binder、FrameTimeline 放在同一份 Trace 里。
之后就能在业务代码里打点。这段用 slice 表示耗时,用参数记录 frame id:
1 | void DecodeFrame(int frame_id) { |
TRACE_EVENT 是作用域事件。进入当前作用域时开始,离开当前作用域时结束。它适合包住一段同步工作,例如 decode、layout、upload、submit。
不跟随函数作用域的事件,用 TRACE_EVENT_BEGIN 和 TRACE_EVENT_END。这对跨多个函数的阶段有用,但不要把 begin/end 分散到完全不相关的调用路径里,容易造成同线程 slice 嵌套混乱、闭合错配或 SQL 解释困难。
接 SDK 时还要留一个构建层面的边界。官方 C++ SDK 是 C++17 库,发布包里常见形态是 perfetto.h 和 perfetto.cc 两个合并版源文件,适合塞进已有 native 构建系统。
只放进 perfetto.h 还不够:要确认编译标准、线程库、符号裁剪、包体增量,以及 App 进程启动早期是否已经完成 Tracing::Initialize()。
早期启动阶段也要谨慎。C++ SDK 只有在 tracing session 已经启用后才会记录普通 Track Event;AndroidX tracing-perfetto 提供了启动阶段初始化能力,但它解决的是 App 自己的启动期采集,不等于系统冷启动分析。冷启动仍要回到 ActivityTaskManager、zygote、Binder、主线程和首帧显示这些系统信号。
category 是线上开关
category 决定哪些事件会被打开。建议把它当成配置接口设计,不要随手起名。C++ API 里没有命中任何规则时,非 debug、非 slow category 默认会被打开;C API 的默认行为不同。
线上 preset 的主规则很简单:显式 disabled_categories: "*",再白名单打开需要的 category。这样可以避开 C/C++ 默认差异,也能避免 debug category 在现场意外打开。debug 和 slow tag 默认关闭,专项分析时再显式打开。
常见划分方式:
player:播放器主阶段,例如 prepare、decode、render。rendering:渲染提交、纹理上传、场景切换。camera:request、HAL callback、buffer queue。pipeline.debug:调试用高频事件,定义 category 时加debugtag。pipeline.slow:开销较高的事件,定义 category 时加slowtag。
category 应按子系统和开关粒度设计,不按页面、实验组、请求类型命名;页面、实验组、请求类型放参数或 metadata,避免 category 数量膨胀。
以下配置明确选择 category:
1 | buffers { |
这样做的好处是,现场抓 Trace 时不必换包,只改配置就能决定打开哪些业务事件。system backend 下 track_event 可能来自多个 producer,现场或线上 preset 要用 producer_name_filter / regex 把范围收住。示例里的 com.example.app 只是占位;先从 trace、data source 或 producer 列表确认实际 producer name,多进程 App 用多个 exact filter 或 regex filter。
和系统 Trace 一起抓
业务事件只解释“业务正在做什么”。系统事件解释“这段业务为什么慢”。合抓时,一般把 track_event 和 ftrace 放在同一份配置里:
1 | buffers { size_kb: 131072 fill_policy: RING_BUFFER } # 0: sched / ftrace |
抓完后,用 SQL 确认事件进来了。这条 SQL 用第 11 篇讲过的 thread_slice 视图,直接把进程名、线程名和业务参数查出来。thread_slice 是 slices.with_context 提供的视图,已经把 slice、track、thread、process 关联好;单篇阅读时只要保留前面的 INCLUDE 即可。
1 | INCLUDE PERFETTO MODULE slices.with_context; |
这条查询适合默认线程 track 上的 Track Event 或 atrace slice。独立 track、process track 事件不一定有 utid,可能不会出现在 thread_slice 里;这类事件要改用 process_slice、thread_or_process_slice,或直接从 slice JOIN track 查,再按 track.name/parent_id 还原上下文。
这条 SQL 的价值不止是“查到了事件”,它还能继续和 thread_state、CPU 频率、Binder 事件、FrameTimeline 做时间范围关联。TrackEvent slice 是业务时间轴证据,不等于 CPU 一直在执行这段业务;CPU 执行要再关联 sched / thread_state,并区分默认 ThreadTrack、自定义 Track 和跨线程 flow。普通 key/value 参数会作为 debug annotation 进入 args,SQL 用 debug.<key> 读取;typed TrackEvent 字段不走这个 debug.* args 口径,需要对应解析、表或专用字段。
时间范围关联只是第一步。App 的 frame_id 是业务帧,FrameTimeline 的 frame/vsync 是系统帧;只靠时间重叠容易把排队、预渲染、跨线程工作归到错误帧。最终要结合 Choreographer frame/vsync、JankStats frame start、RenderThread/HWUI slice、actual_frame_timeline_slice / expected_frame_timeline_slice 再确认。
这里的 frame_id 是 debug annotation,适合排查和临时 SQL。debug annotation 默认是调试字段;如果进入长期指标,要把字段名、类型、单位、缺省值和迁移策略当成接口冻结。只有能控制 Perfetto proto、解析和发布流程时,再考虑 typed TrackEvent 字段或自定义 DataSource。现场 preset 还可以用 filter_debug_annotations / filter_dynamic_event_names 降低隐私和体积风险。
业务阶段报告至少要把 marker 完整性写出来:
1 | INCLUDE PERFETTO MODULE slices.with_context; |
CSV 输出可以固定成这类字段:
1 | trace_name,scenario,frame_or_request_id,phase,start_ms,dur_ms,thread_name,frame_jank_type,evidence_grade,missing_marker |
跨线程任务用 flow 或独立 track
很多业务慢不在单个函数里,而在队列之间。比如 UI 线程提交一个请求,decoder 线程处理,render 线程消费。只用同线程 slice,读者很难看出它们属于同一个 frame。
可以用 flow 把相关事件连起来。不要直接把容易复用的业务 frame_id 当 flow id,先生成一次 Trace 内唯一的 token:
1 | uint64_t MakeFlowId(uint64_t session_id, uint64_t stream_id, uint64_t frame_id) { |
选中事件时,Perfetto UI 会用箭头展示关系。中间阶段继续使用 Flow,最终消费阶段再用 TerminatingFlow。
这里用的是 ProcessScoped(flow_id),所以 id 至少要在进程内、一次 trace 生命周期内稳定,不要直接拿容易复用的临时指针当 flow id;跨进程 flow 不要继续用 ProcessScoped 语义,至少要使用全局唯一 id,并明确 namespace 和来源进程。多阶段 pipeline 也可以给每条边单独分配 flow id,例如 enqueue_to_decode_flow_id、decode_to_render_flow_id,避免一条 flow 被多条路径复用。
如果一个任务跨函数、跨线程持续很久,也可以放到独立 track 上:
1 | void StartRequest(uint64_t request_id) { |
这类事件适合任务队列、播放器 pipeline、异步加载、跨线程 GPU 提交。它比“每个函数都打点”更能解释业务路径。
perfetto::Track(request_id) 里的 id 要在一次 Trace 内对这个并发任务唯一。线上 request id、frame id 很容易复用,复用后不同任务会合到同一条 track,看起来像一个长任务或错误串行。更稳的做法是加 session/pipeline namespace,或者生成专用 64-bit track id;需要长期阅读时,补 track descriptor / name,让 UI 和 SQL 都能识别生命周期。descriptor / interning 丢失时,要回到第 12 篇的 stats 检查。
Counter 只放状态,不放日志
counter 适合随时间变化的数值,例如队列长度、in-flight 请求数、buffer 占用。frame id 是标识符,不是可聚合指标;它更适合放在 slice 参数或 flow id 里。这个例子记录 decode 队列深度:
1 | void ReportDecodeQueueDepth(int depth) { |
counter 不适合承载日志文本,也不适合拿标识符做 avg/max。日志放 logcat,Trace 里只保留能和时间轴关联、可以被解释为状态的数值。
counter 要固定单位和聚合口径。DecodeQueueDepth_items 看峰值和窗口内均值,InFlightRequests_count 看峰值和持续时间,BufferBytes_bytes 看峰值、增长斜率和是否回落;不要对 frame_id、request_id 这类标识符求 avg/max。
事件名是 SQL 接口
事件名一旦进了 SQL、看板和回归报告,就变成了接口。不要把动态 id 拼进事件名。
1 | // 不建议:每个 frame 都会生成不同事件名,SQL 很难稳定统计。 |
更好的命名方式是“阶段 + 动作”:
DecodeFrame:解码一个业务帧,参数带frame_id、stream_id、codec。UploadTexture:上传纹理或 buffer,参数带frame_id、texture_bytes。RenderScene:提交一轮场景渲染,参数带scene_id或surface_id。SubmitRequest:提交一个跨线程请求,参数带request_id和queue_depth。WaitForBuffer:等待上游 buffer,可和 Binder、线程状态、buffer queue 证据关联。
事件名稳定以后,后面的 SQL 才能稳定。这里顺手引入 time.conversion,让最大耗时的单位直接写在 SQL 里;平均值保留一位小数时仍然可以用 / 1e6:
1 | INCLUDE PERFETTO MODULE slices.with_context; |
控制写入量
Track Event 开销低,但不是免费。第 12 篇讲过 data loss,这里要先拆清两条路径:android.os.Trace / ATrace 写太密会压 ftrace per-CPU buffer;Perfetto SDK Track Event 写太密主要压 producer shared memory、central buffer 和 incremental state。SDK Track Event 丢包时不要去调 ftrace buffer,ATrace 丢事件时也不要只盯 track_event stats。
建议给每类埋点设一个预算:
- 事件密度:按
events/sec、每帧事件数、参数字节数和目标 Trace 时长估算写入量。120Hz、多线程、多阶段和动态字符串叠加后,每帧一次也可能过密。 - 数据形状:每个小对象、每次锁尝试、每个像素级循环都不适合默认开启;高频 debug category 默认关闭,专项 Trace 可用 1/N 采样或触发后短窗口开启。
- 参数开销:便宜标量可直接传;昂贵字符串、JSON、容器遍历、锁内状态快照放在 enabled 判断或 lambda 内。参数在宏调用前已经拼好时,category gating 救不了这部分开销。
- 采集验收:长 Trace 里把业务 Track Event 放到独立 central buffer,抓完检查
stats、event_count_by_category和track_event_loss_stats;这只能隔离 central buffer 竞争,不能修复 producer shared memory burst loss。 - Ring Buffer 场景要关注 Track Event 的 descriptor / string interning / incremental state,可以配合
incremental_state_config.clear_period_ms或低写入率 buffer,避免后半段事件缺名字和上下文。
统一验收查询可以先从第 12 篇的 health check 开始,再补 Track Event 专用项:
1 | SELECT name, idx, severity, source, value |
报告里单独给 track_event_trust_level。比如 packet loss 为 0,但 track_event_tokenizer_errors 或 track_hierarchy_missing_uuid 非 0,业务事件仍要降级。
App 侧现场 Trace 协议
App 侧接 Perfetto,最容易踩的坑是把 App 当成一个小型 adb shell perfetto 客户端。普通 App 没有随意启动系统 tracing 的权限,也不适合在线上动态下发高开销 TraceConfig。现场方案更适合拆成三层:
- 平台侧:预置受控 TraceConfig,声明 trigger、Ring Buffer、文件路径、数据源白名单和采集上限。
- App 侧:记录业务埋点和页面状态,检测慢帧、卡死、超时等异常,按平台约定激活 trigger。
- 服务端:接收 Trace package,跑
stats、摘要 SQL 和聚合索引,给人工分析留出可复查入口。
Perfetto 的 STOP trigger 很适合这类场景:trace 先以 Ring Buffer 方式循环记录,App 检测到问题后触发一个已声明的名字,系统在 stop_delay_ms 之后收尾。这样能保留问题发生前的一段现场,也能把触发后的尾巴收进来。
一个受控现场 UI 卡顿 preset 可以长这样。它适合灰度白名单、采样率受控、写入速率已实测、stats 可验收的实验室或现场排查;线上默认 preset 应减少 atrace category,必要时只保留 sched、FrameTimeline 和最小 Track Event。它不是“全量图形专项 preset”,但足够支持主线程、RenderThread/HWUI、FrameTimeline、CPU 调度、SurfaceFlinger 侧 FrameTimeline/线程调度线索和 App Track Event 的第一轮定位;涉及 Layer、Transaction、HWC、Display 归因时,切换图形专项 preset。
1 | buffers { size_kb: 131072 fill_policy: RING_BUFFER } # 0: sched / ftrace |
这里的 max_per_24_h 和 skip_probability 是兜底,实际产品仍要在平台侧做用户、设备、版本、trigger 和天维度限频。遇到 GPU、SurfaceFlinger、HWC 或功耗专项时,再切换更完整的图形/功耗 preset。
测试设备上可以用命令验证 trigger:
1 | /system/bin/trigger_perfetto app_ui_jank |
Perfetto trigger 的模型是:有权限的 consumer 预先声明 trigger 名字,App 这类 producer 只能激活已声明的名字。接了 Perfetto SDK 的 native 模块也可以用 SDK 触发;产品上仍建议由平台接口统一控制权限和配额:
1 | perfetto::Tracing::ActivateTriggers({"app_ui_jank"}, 10000); |
第二个参数是触发请求的 TTL,避免 producer 还没连上 tracing service 时留下无限期的 pending trigger。
线上 App 不应假设自己能执行 /system/bin/trigger_perfetto。更推荐由平台提供受控接口,例如系统服务、厂商 SDK、企业版设备管理组件或测试框架代理。App 只提交一个受限请求:
1 | { |
平台侧根据 preset 白名单、采样率、权限、设备状态和每日配额决定是否触发。这套接口约定比“App 自己拼 TraceConfig”更适合长期维护。服务端匹配时应输出 trigger_event_ts、nearest_marker、marker_delta_ms、matched_by,避免外部 JSON 和 trace 时间轴只靠人工猜。
触发策略要靠近用户感知,而不是靠近内部实现细节:
| 触发 | 典型信号 | Trace 里要看什么 |
|---|---|---|
| UI 慢帧 | JankStats 连续慢帧、严重超预算 | 主线程、RenderThread、FrameTimeline、CPU |
| 主线程卡死前兆 | 2s / 4s / 8s watchdog | 主线程状态、Binder、锁、IO、系统服务 |
| 业务 timeout | Camera open、播放器 prepare、首屏 timeout | App marker、Binder、HAL、线程状态 |
触发名进入平台 preset 后要保持稳定。改名会让服务端聚合、SQL 模板和历史数据断开。
Trace package 要带上下文
一份现场包先分最小必需和平台化扩展。第 13 篇只要求最小必需:Trace 本体、业务 metadata、采集 preset、stats 可信度结果。上传策略、日志白名单和保留周期属于平台化后的规则,可以后续单独设计。
1 | trace-package/ |
app_metadata.json 描述这次问题现场,字段要固定并带 schema 版本:
1 | { |
其他几个文件也要有最小字段:
trace_stats.json 至少保留第 12 篇 health check 的结果:
1 | { |
upload_policy.json 说明这份现场包能不能上传、保留多久:
1 | { |
app_events.jsonl 用一行一个 JSON 事件保存触发点和少量 App 侧状态,并和 Track Event 共享 scenario_id/session_id/action_id:
1 | {"elapsed_realtime_ns":123456789000000,"event":"trigger","scenario_id":"feed_scroll_120hz","session_id":"s-20260504-001","action_id":"scroll-42","page":"Feed","action":"scroll"} |
log_excerpt.txt 只放白名单 tag 的短窗口摘要,至少记录 tag、level、elapsed realtime 和脱敏后的 message。
时间戳建议统一使用 elapsedRealtimeNanos / CLOCK_BOOTTIME 语义,并在字段名里写清楚。Perfetto 能同步 trace packet 里带 timestamp_clock_id / ClockSnapshot 的时间域,但外部 JSON 不会自动归一。metadata 里最好记录 trace start/end 的 elapsed realtime,或者服务端使用明确 offset 把外部事件放回 Trace 窗口。Track Event 默认让 SDK 取时间;只有能保证 clock domain 和转换逻辑时才传显式 timestamp,并记录 clock source、offset 和单位。
App 侧不要直接截断 trace.perfetto-trace。Perfetto trace 是 protobuf 流,粗暴二进制裁剪可能破坏 packet、时钟同步和增量状态。更稳的做法是在采集侧控制窗口:Ring Buffer 控制触发前窗口,stop_delay_ms 控制触发后窗口,trigger 限频控制重复抓取。
max_file_size_bytes 是文件大小硬上限,达到后会停止 tracing;长时间 flight recorder 如果 cap 太小,可能在 trigger 前就结束。它适合和 write_into_file / file_write_period_ms 一起控制落盘风险,不是窗口裁剪工具。
平台化后,上传策略再把配额、隐私和重试写进产品规则:
- 设备侧配额:按用户、设备、版本、trigger 和天维度限频。
- 网络条件:默认只在 Wi-Fi 或用户允许的网络上传,低电量、温度异常、后台受限时延后。
- 内容白名单:metadata 字段固定,log 只允许白名单 tag,App trace section 禁止拼接用户输入。
- Trace 本体:最小化数据源,限制进程/producer,避免默认 logcat;ftrace、sched、Binder、线程名、进程名本身也可能是敏感信息。
- 传输和保留:加密上传,服务端访问审计,Trace、log 摘要、聚合结果设置独立 TTL,到期自动清理。
App 提供业务阶段、异常触发和 metadata;平台提供受控 preset、trigger 和文件管理;服务端提供可信度检查、摘要和聚合。三部分配齐之后,Perfetto 才能从一次次手工抓取,进入可持续的线上问题排查流程。
总结
Perfetto SDK 的价值是补业务语义:系统知道线程在跑,SDK 告诉你这段时间在 decode 哪一帧、队列有多深、请求从哪里流到哪里。
工程上建议按这个顺序做:Java/Kotlin 层先用 android.os.Trace,native 长期埋点用 Track Event,复杂结构再考虑自定义 DataSource。事件名稳定、category 可控、counter 有单位、flow 有稳定 id,后面的 SQL 和报告才可长期复用。进入现场 Trace 后,App 还要把触发、metadata、证据包和上传策略一起设计好。
参考文档
- Tracing SDK
- Track events
- Recording In-App Traces with Perfetto
- androidx.tracing.perfetto
- Trace configuration - Triggers
- android.os.Trace
- JankStats Library
关于我 && 博客
欢迎关注 Android Performance。