EN

Android Perfetto 系列 17:从 Camera/Audio 到平台级 Trace 工程化

Word count: 7.6kReading time: 35 min
2026/05/04
loading

Camera open 慢、Audio underrun、WebView 首屏卡住、Flutter/游戏掉帧,这类问题的证据会散在 App、system_servercameraserver/audioserver、HAL、SurfaceFlinger 和调度轨道里。只在 UI 里多看几条轨道,很容易漏掉关键时间点。

第 17 篇用 Camera 和 Audio 举例,讲一套可复用的专项分析方法:先抓对数据,再识别对象,再算阶段和阻塞,报告里保留能跳回 UI 复查的时间点。Camera/Audio 是例子,核心是领域 schema 和平台 tracing 协作。读完之后,至少能把 Camera open 慢拆成可复查阶段,把 Audio underrun 拆成周期异常,而不是只交一个 Top slice 表。

这篇默认沿用前文的 UI、SQL、质量检查和现场 Trace 方法。专项分析沿用这些工具,只是按领域对象重新组织。

领域分析从对象字典开始

Camera 和 Audio 的难点不在“有没有 camera/audio 这个 category”,而在 Trace 里到底有哪些对象:

领域 对象 为什么要识别
Camera App client、cameraserver、provider、HAL、request/session id open、configure、preview、capture 可能分散在不同进程
Audio App audio thread、audioserver、MixerThread、FastMixer、audio HAL、Bluetooth/audio route underrun 往往来自周期抖动、写入不稳、HAL 阻塞或调度延迟

自动化分析的第一步是生成对象字典:相关进程、线程、track、slice 名称、log tag、request/session id。后面的 SQL 不直接猜根因,只围绕这个字典计算阶段耗时和阻塞证据。

对象字典建议由脚本输出,不要只存在于人脑里的一组关键词。一次 Camera case 里可能同时出现 App 进程、cameraserverprovider@2.7-service、vendor camera daemon、codec、SurfaceFlinger;一次 Audio case 里可能同时出现 App、audioserveraudio.primary、Bluetooth、media codec。

确认后的对象字典先长这样,SQL 只是生成和验证它的一种方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
object_dictionary_version: camera_audio_v1
objects:
- role: camera_app_client
object_type: thread
process_name: com.example.app
thread_name: main
track_id: 102
uid: 10123
package_name: com.example.app
service_instance: ""
hal_transport: ""
selinux_context_or_domain: untrusted_app
vendor_owner: app-team
source_signal: track_event
confidence: 1.0
owner_confirmed: true
- role: camera_service
object_type: process
process_name: cameraserver
vendor_owner: platform-camera
source_signal: atrace
confidence: 1.0
owner_confirmed: true

领域 schema 是 App、平台、SQL 和报告之间的协作接口。它要版本化,而不是散在文章、脚本和口头约定里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
schema_version: camera_domain_v1
domain: camera
scenario: camera_open_preview
owner_team: platform-camera
privacy_level: system-metadata-and-app-markers
object_roles:
- camera_app_client
- camera_service
- camera_provider
- camera_hal
- display_service
stable_events:
- event_name: Camera#OpenStart
owner: app
required_args: [camera_id, session_id, event_id]
- event_name: Camera#ConfigureStreamsStart
owner: platform
required_args: [camera_id, session_id, stream_id]
- event_name: Camera#FirstPreviewFrame
owner: platform
required_args: [camera_id, session_id, stream_id, surface_id]
stage_definitions:
- stage_name: open_api
start_event: Camera#OpenStart
end_event: Camera#OpenEnd
- stage_name: first_presented_frame
start_event: Camera#OpenStart
end_event: Camera#FirstPreviewFrame
report_fields:
- stage_name
- status
- start_ts
- end_ts
- dur_ms
- evidence_slice
- review_ts
- source_role

脚本先把候选对象列出来,领域负责人再确认哪些对象属于本次场景。确认结果不能只停在人脑里,要写成 domain_objects 表或同结构的 YAML/JSON,后续查询只 JOIN 这张字典。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
CREATE PERFETTO TABLE domain_objects AS
SELECT
'candidate' AS role,
'thread' AS object_type,
p.name AS process_name,
p.upid,
p.pid,
NULL AS uid,
NULL AS package_name,
th.name AS thread_name,
th.utid,
th.tid,
tt.id AS track_id,
NULL AS log_tag,
NULL AS request_id_key,
NULL AS session_id_key,
NULL AS service_instance,
NULL AS hal_transport,
NULL AS selinux_context_or_domain,
NULL AS source_signal,
NULL AS vendor_alias,
NULL AS vendor_owner,
0.5 AS confidence,
0 AS owner_confirmed
FROM thread th
LEFT JOIN process p USING (upid)
LEFT JOIN thread_track tt USING (utid)
WHERE LOWER(p.name) LIKE '%camera%'
OR LOWER(p.name) LIKE '%audio%'
OR LOWER(p.name) LIKE '%media%'
OR p.name IN ('cameraserver', 'audioserver', 'surfaceflinger');

这张表的字段至少要覆盖 role、进程/线程、track_id、厂商别名、置信度和人工确认状态。第一版仍然可以靠关键词生成,但报告只能使用 owner_confirmed = 1 的对象。否则 vendor HAL、provider、codec、Bluetooth route 一漂移,自动化还是会退回模糊匹配。

候选对象可以这样列给领域负责人复查:

1
2
3
4
5
6
7
8
9
10
SELECT DISTINCT
role,
process_name,
pid,
thread_name,
tid,
confidence,
owner_confirmed
FROM domain_objects
ORDER BY process_name, thread_name;

这一步的输出要人工看一眼。自动化可以缩小范围,但不能凭 LIKE 把 provider、HAL、codec、Bluetooth route 的归属全判准。

确认后的对象要写回同一张表,或者落成一份同结构的 YAML/JSON。这个最小示例把本次 Camera open 相关对象标记成报告可用对象,并给它们补上角色。

1
2
3
4
5
6
7
8
9
10
11
UPDATE domain_objects
SET owner_confirmed = 1,
role = CASE
WHEN process_name = 'com.example.app' THEN 'camera_app_client'
WHEN process_name = 'cameraserver' THEN 'camera_service'
WHEN LOWER(process_name) LIKE '%provider%' THEN 'camera_provider'
WHEN process_name = 'surfaceflinger' THEN 'display_service'
ELSE role
END
WHERE process_name IN ('com.example.app', 'cameraserver', 'surfaceflinger')
OR LOWER(process_name) LIKE '%provider%';

后续 SQL 只使用 owner_confirmed = 1 的对象。候选表负责找全,确认表负责让报告稳定。固定 role enum 包括:camera_app_clientcamera_servicecamera_providercamera_haldisplay_serviceaudio_appaudio_serveraudio_mixeraudio_halbluetooth_audio

配置从领域 preset 开始

权限边界要先写清:

能力 谁能做 说明
App marker / metadata 普通 App android.os.Trace、NDK ATrace、Track Event、业务元数据
已登记 trigger 普通 App 或测试 harness 只能激活平台预先声明的 trigger 名称
App profiling shell + profileable 或 debuggable release 性能对比优先 profileable,debuggable 只用于调试
ftrace/process stats/FrameTimeline/system log shell、Traceur、平台/OEM 受信 consumer 受 SELinux 和 build 类型限制,普通 App 不能开启
上传、限频、隐私、preset 选择 平台/OEM 诊断流程 必须有授权、脱敏、保留周期和审计

下面这份是 lab_domain_debug 配置,不是低开销 field preset。它保留 sched/freq/idle、Camera/Audio/hal/binder atrace、FrameTimeline、process stats 和 log,用于研发设备、userdebug 或实验室复现。field 版本应减到必要数据源,log、binder_lock、高频领域事件只在专项短 trace 打开。

Android 9/10 非 Pixel 可能需要先启用 traced;Android 12 前非 root 不要假设 perfetto 能直接读 /data/local/tmp 配置,优先用 stdin。Camera 如果要量“用户可见首帧”,还需要 SurfaceFlinger/FrameTimeline;只看 camera/HAL 只能量到 API 返回、HAL buffer 或 provider 阶段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
duration_ms: 30000
flush_period_ms: 10000

buffers {
size_kb: 131072
fill_policy: RING_BUFFER
}

data_sources {
config {
name: "linux.ftrace"
ftrace_config {
compact_sched { enabled: true }
ftrace_events: "sched/sched_switch"
ftrace_events: "sched/sched_waking"
ftrace_events: "power/cpu_frequency"
ftrace_events: "power/cpu_idle"
atrace_categories: "camera"
atrace_categories: "audio"
atrace_categories: "gfx"
atrace_categories: "view"
atrace_categories: "hal"
atrace_categories: "binder_driver"
atrace_categories: "binder_lock"
atrace_categories: "am"
atrace_categories: "wm"
atrace_categories: "input"
# category/event 以目标设备 Record 页或 --query 输出为准。
atrace_apps: "<target_package>"
}
}
}

data_sources {
config {
name: "android.surfaceflinger.frametimeline"
}
}

data_sources {
config {
name: "linux.process_stats"
process_stats_config {
scan_all_processes_on_start: true
proc_stats_poll_ms: 5000
}
}
}

data_sources {
config {
name: "android.log"
android_log_config {
log_ids: LID_DEFAULT
log_ids: LID_SYSTEM
log_ids: LID_EVENTS
log_ids: LID_CRASH
filter_tags: "CameraService"
filter_tags: "CameraProvider"
filter_tags: "AudioFlinger"
filter_tags: "AudioTrack"
filter_tags: "AudioPolicyManager"
}
}
}

atrace_categories 打开的是 Android 系统 category,不会自动打开 App 里的 android.os.Trace 或 NDK ATrace;App 侧 ATrace 共享 ATRACE_TAG_APP,需要按包名配置 atrace_apps,没有独立 category 过滤。需要参数和跨线程关联时,改用第 13 篇的 Perfetto SDK Track Event。

user build 上采 App 侧 profiling 或部分本地调试信号时,release 包应使用 <profileable android:shell="true" />debuggable 会改变性能特征,只适合调试,不适合严肃耗时对比。系统服务、HAL、CPU/native profiling 另走 userdebug/eng 或平台诊断权限。

android.log 只在 Android userdebug 构建上支持。user build 或线上场景不要假设日志一定在 Trace 里,外部 logcat、bugreport 或平台侧脱敏日志应放进同一个 case 包。现场包只保留白名单 tag 和窗口摘要,原始 log 要走授权和脱敏。

category 名称也要以目标系统为准。Perfetto 文档把 ATrace 分成 system category 和 per-app events,系统 category 来自 Android 内部进程;App event 共享 ATRACE_TAG_APP,需要按包名启用。写 preset 时不要只复制别人机器上的 category 列表,最好在 Perfetto UI 的 Record 页面或设备侧配置里确认当前版本支持什么。

buffers.size_kb 是 Perfetto central buffer,不等于 ftrace per-CPU kernel ring buffer。sched/ftrace 丢失要看 stats 里的 ftrace overrun/dropped/data_loss;必要时才调 ftrace_config.buffer_size_kbdrain_period_ms 这类参数,并先在短 trace 验证设备兼容性和开销。新增 vendor kernel tracepoint 要先确认 /sys/kernel/tracing/events/*/*/format、字段稳定性和 Perfetto parser/stdlib 支持;否则只能作为 raw ftrace 候选证据,或需要改 Perfetto parser / 自定义 data source。

质量门禁不要只写一个抽象分数。至少固定跑这条 SQL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
SELECT name, idx, severity, source, value
FROM stats
WHERE value != 0
AND (
severity IN ('error', 'data_loss')
OR name IN (
'ftrace_setup_errors',
'ftrace_cpu_has_data_loss',
'traced_buf_trace_writer_packet_loss',
'traced_buf_chunks_overwritten',
'traced_buf_chunks_discarded',
'traced_buf_patches_failed',
'traced_flushes_failed',
'traced_final_flush_failed',
'track_event_parser_errors',
'track_event_tokenizer_errors',
'track_event_thread_invalid_end',
'graphics_frame_event_parser_errors',
'systrace_parse_failure',
'clock_sync_failure',
'invalid_clock_snapshots'
)
OR name GLOB 'ftrace_cpu_*overrun*'
OR name GLOB 'ftrace_cpu_*dropped*'
OR name GLOB 'traced_buf_*packet_loss'
)
ORDER BY name, idx;

idx 对应 TraceConfig 的 buffer 下标。报告要按 affected_signal 分组:ftrace/schedatrace/systracetrack_eventFrameTimelineandroid.logclockcentral_buffer。RING_BUFFER 下的 overwritten 要单独解释:它可能只是窗口外旧数据被覆盖,也可能说明问题前证据不够长。报告要把 data loss、packet loss、overwrite 分开降级。

Camera 看阶段

Camera open 慢不能只写“camera 慢”。至少拆成这些阶段:

阶段 owner 关注点
App 发起 open App marker 用户操作、权限、UI thread 是否等待
App 到 cameraserver App + camera service Binder 往返、服务端线程池是否拥塞
cameraserver 到 provider/HAL platform event provider 进程、HAL 线程、设备等待
configure streams platform/HAL event stream 数量、分辨率、Surface、HAL configure 耗时
first HAL buffer HAL/provider event request 提交、buffer 从 HAL 返回
first presented frame FrameTimeline/SurfaceFlinger + App marker buffer 进入 SurfaceFlinger 后被用户看到
capture/result platform/HAL event request id、result callback、buffer/metadata 返回

这里有三个不同口径:API 调用返回、第一帧从 HAL 返回、第一帧被用户看到。前两个偏 Camera framework / HAL,第三个还要经过 BufferQueue、SurfaceFlinger 和 FrameTimeline。报告里要写清你量的是哪一个,不然“open 变快了”可能只是 API 更早返回,预览首帧并没有提前。

专项 SQL 必须绑定问题窗口。这个窗口可以来自 trigger metadata、业务 marker、用户复现步骤,或者人工在 UI 中选出的区间。缺 owner 事件时,报告写 missing_owner_eventstage_unavailable,不要把 LIKE 查询升级成阶段结论。

1
2
3
4
5
WITH target_window AS (
SELECT 120e9 AS start_ts, 126e9 AS end_ts
)
SELECT start_ts / 1e9 AS start_s, end_ts / 1e9 AS end_s
FROM target_window;

稳定事件存在时,先用事件计算阶段。缺某个事件时,状态列要输出 stage_unavailable,耗时列保持空值,不要补一个看起来已经量化完成的数字:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
WITH target_window AS (
SELECT 120e9 AS start_ts, 126e9 AS end_ts
),
events AS (
SELECT
MIN(CASE WHEN s.name = 'Camera#OpenStart' THEN s.ts END) AS open_start_ts,
MIN(CASE WHEN s.name = 'Camera#OpenEnd' THEN s.ts END) AS open_end_ts,
MIN(CASE WHEN s.name = 'Camera#ConfigureStreamsStart' THEN s.ts END) AS configure_start_ts,
MIN(CASE WHEN s.name = 'Camera#ConfigureStreamsEnd' THEN s.ts END) AS configure_end_ts,
MIN(CASE WHEN s.name = 'Camera#FirstHalBuffer' THEN s.ts END) AS first_hal_buffer_ts,
MIN(CASE WHEN s.name = 'Camera#FirstPresentedFrame' THEN s.ts END) AS first_presented_frame_ts
FROM slice s
JOIN track t ON s.track_id = t.id
LEFT JOIN thread_track tt ON s.track_id = tt.id
LEFT JOIN thread th USING (utid)
LEFT JOIN process p USING (upid)
JOIN domain_objects d
ON d.owner_confirmed = 1
AND d.role IN (
'camera_app_client',
'camera_service',
'camera_provider',
'camera_hal',
'display_service'
)
AND (
d.track_id = s.track_id
OR d.utid = th.utid
OR d.process_name = p.name
)
CROSS JOIN target_window
WHERE s.ts >= start_ts
AND s.ts < end_ts
)
SELECT
CASE
WHEN open_start_ts IS NULL OR open_end_ts IS NULL THEN 'stage_unavailable'
ELSE 'measured'
END AS open_api_status,
(open_end_ts - open_start_ts) / 1e6 AS open_api_ms,
CASE
WHEN configure_start_ts IS NULL OR configure_end_ts IS NULL THEN 'stage_unavailable'
ELSE 'measured'
END AS configure_streams_status,
(configure_end_ts - configure_start_ts) / 1e6 AS configure_streams_ms,
CASE
WHEN open_start_ts IS NULL OR first_hal_buffer_ts IS NULL THEN 'stage_unavailable'
ELSE 'measured'
END AS first_hal_buffer_status,
(first_hal_buffer_ts - open_start_ts) / 1e6 AS open_to_first_hal_buffer_ms,
CASE
WHEN open_start_ts IS NULL OR first_presented_frame_ts IS NULL THEN 'stage_unavailable'
ELSE 'measured'
END AS first_presented_frame_status,
(first_presented_frame_ts - open_start_ts) / 1e6 AS open_to_first_presented_frame_ms
FROM events;

如果稳定事件还没补齐,再退回候选 slice 查询。这个查询只用于建立候选阶段,重点看 process_namethread_nameslice_namedur_ms,不能直接替代上面的阶段口径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
WITH target_window AS (
SELECT 120e9 AS start_ts, 126e9 AS end_ts
)
SELECT
s.ts / 1e9 AS ts_s,
s.dur / 1e6 AS dur_ms,
s.name AS slice_name,
th.name AS thread_name,
p.name AS process_name,
th.tid,
p.pid
FROM slice s
JOIN thread_track tt ON s.track_id = tt.id
JOIN thread th USING (utid)
LEFT JOIN process p USING (upid)
JOIN domain_objects d ON d.utid = th.utid
CROSS JOIN target_window
WHERE d.owner_confirmed = 1
AND s.ts < end_ts
AND s.ts + s.dur > start_ts
AND (
LOWER(s.name) LIKE '%camera%'
OR d.role IN (
'camera_app_client',
'camera_service',
'camera_provider',
'camera_hal'
)
)
ORDER BY s.dur DESC
LIMIT 100;

如果这条查不到足够的阶段信息,通常有两种处理:补 android.os.Trace / ATRACE / Track Event,或者让平台侧在 CameraService、provider、HAL 关键阶段补稳定事件名。事件参数里要校验 camera_id/session_id/request_id;用 Track Event 时从 args/debug annotation 或 flow id 取,不要只靠事件名。

Audio 看周期

Audio 问题比 Camera 更强调周期性。一次长 slice 不一定导致听感问题,连续几个 mixer 周期抖动、App 写入间隔不稳、HAL 写入阻塞,才更接近 underrun 或断续。

Audio 报告优先看三件事:周期尾部是否变长,连续异常是否出现,异常是否和 underrun log、route change 或听感时间点重合。Top slice 只作为辅助线索。

Audio 候选 slice 也要绑定问题窗口,并优先使用 domain_objects,避免整段 trace 的历史噪声混进来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
WITH target_window AS (
SELECT 120e9 AS start_ts, 126e9 AS end_ts
)
SELECT
s.ts / 1e9 AS ts_s,
s.dur / 1e6 AS dur_ms,
s.name AS slice_name,
th.name AS thread_name,
p.name AS process_name
FROM slice s
JOIN thread_track tt ON s.track_id = tt.id
JOIN thread th USING (utid)
LEFT JOIN process p USING (upid)
JOIN domain_objects d ON d.utid = th.utid
CROSS JOIN target_window
WHERE d.owner_confirmed = 1
AND s.ts < end_ts
AND s.ts + s.dur > start_ts
AND (
LOWER(s.name) LIKE '%audio%'
OR LOWER(s.name) LIKE '%mixer%'
OR LOWER(s.name) LIKE '%underrun%'
OR d.role IN ('audio_app', 'audio_server', 'audio_mixer', 'audio_hal', 'bluetooth_audio')
)
ORDER BY s.ts
LIMIT 200;

再看 audioserver 的 Mixer/FastMixer 运行周期。这里输出 start-to-start 周期和 off-CPU gap:前者只能发现周期异常,不能直接说明被锁、Binder 或 HAL 写入打断;原因还要关联 thread_state、HAL slice、underrun log 和 route metadata。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
WITH target_window AS (
SELECT 120e9 AS start_ts, 126e9 AS end_ts
),
mixer_runs AS (
SELECT
ss.ts,
ss.dur,
th.name AS thread_name,
p.name AS process_name,
LEAD(ss.ts) OVER (
PARTITION BY ss.utid
ORDER BY ss.ts
) AS next_ts
FROM sched ss
JOIN thread th USING (utid)
LEFT JOIN process p USING (upid)
CROSS JOIN target_window
WHERE p.name = 'audioserver'
AND ss.ts < end_ts
AND ss.ts + ss.dur > start_ts
AND (
LOWER(th.name) LIKE '%mixer%'
OR LOWER(th.name) LIKE '%fastmixer%'
)
)
SELECT
ts / 1e9 AS ts_s,
thread_name,
dur / 1e6 AS running_ms,
(next_ts - ts) / 1e6 AS start_to_start_period_ms,
(next_ts - (ts + dur)) / 1e6 AS off_cpu_gap_ms
FROM mixer_runs
WHERE next_ts IS NOT NULL
ORDER BY start_to_start_period_ms DESC
LIMIT 100;

Audio 自动化报告里,周期异常比单个 Top slice 更有价值。start_to_start_period_ms 说明节奏是否变长,off_cpu_gap_ms 说明两次运行之间空档是否变大;要判断 CPU 等待、HAL write、route/offload 变化或正常 idle,还要再看对应 slice、thread_state 和 log。

不要给 period_ms 写一个跨设备固定阈值。它和 sample rate、buffer size、fast path、Bluetooth route、offload、AAudio/OpenSL ES/AudioTrack 使用方式都有关系。更稳的做法是:同一设备同一 route 下拿一段干净播放作为基线,再比较异常现场里周期尾部是否变长、是否连续变长、是否和 underrun log 或听感时间点重合。

Running 和 Runnable 分开算

很多专项报告会把线程状态混在一起写。建议分开:

  • Running:线程已经在 CPU 上执行,用 sched 算。
  • Runnable:线程想运行但没拿到 CPU,用 thread_state.state IN ('R', 'R+') 算。R+ 表示 runnable/preempted,也应计入调度等待。

运行态分析有前置条件:必须采到 sched_switch、wakeup/waking,且 stats 里没有 ftrace_cpu_has_data_loss、overrun 或 dropped。否则只能写“调度证据不完整”,不能写“没有 CPU 等待”。

这条 SQL 对问题窗口内的领域线程同时输出 Running 和 Runnable:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
WITH target_window AS (
SELECT 120e9 AS start_ts, 126e9 AS end_ts
),
domain_threads AS (
SELECT DISTINCT utid, thread_name, process_name
FROM domain_objects
WHERE owner_confirmed = 1
),
running AS (
SELECT
ss.utid,
SUM(MIN(ss.ts + ss.dur, end_ts) - MAX(ss.ts, start_ts)) AS running_ns
FROM sched ss
CROSS JOIN target_window
WHERE ss.ts < end_ts
AND ss.ts + ss.dur > start_ts
GROUP BY ss.utid
),
runnable AS (
SELECT
ts.utid,
SUM(MIN(ts.ts + ts.dur, end_ts) - MAX(ts.ts, start_ts)) AS runnable_ns
FROM thread_state ts
CROSS JOIN target_window
WHERE state IN ('R', 'R+')
AND ts.ts < end_ts
AND ts.ts + ts.dur > start_ts
GROUP BY ts.utid
)
SELECT
d.process_name,
d.thread_name,
COALESCE(running.running_ns, 0) / 1e6 AS running_ms,
COALESCE(runnable.runnable_ns, 0) / 1e6 AS runnable_ms
FROM domain_threads d
LEFT JOIN running USING (utid)
LEFT JOIN runnable USING (utid)
ORDER BY runnable_ms DESC, running_ms DESC
LIMIT 100;

如果 Runnable 很高,优先看 CPU 竞争、线程优先级、频率和唤醒源;如果 Running 很高,再考虑 CPU profiling 或函数级热点。

Log 用来补阶段语义

日志适合补 request id、session id、route change、underrun、错误码。Trace 里有 android.log 时,也要限制在问题窗口内:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
WITH target_window AS (
SELECT 120e9 AS start_ts, 126e9 AS end_ts
)
SELECT
l.ts / 1e9 AS ts_s,
p.name AS process_name,
l.prio,
l.tag,
l.msg
FROM android_logs l
LEFT JOIN thread th USING (utid)
LEFT JOIN process p USING (upid)
CROSS JOIN target_window
WHERE l.ts >= start_ts
AND l.ts < end_ts
AND (
LOWER(l.tag) LIKE '%camera%'
OR LOWER(l.tag) LIKE '%audio%'
OR LOWER(l.msg) LIKE '%underrun%'
OR LOWER(l.msg) LIKE '%configurestream%'
OR LOWER(l.msg) LIKE '%route%'
)
ORDER BY l.ts;

不要把 log 当成唯一证据。它负责给阶段命名,耗时和阻塞仍要回到 slice、sched、thread_state、Binder 和 HAL 线程。

报告输出要能回 UI

自动化报告不要只吐 SQL 表。建议固定四块:

模块 输出
数据质量 Trace 时长、stats 异常、关键数据源是否存在
对象字典 相关进程、线程、track、log tag、request/session id、确认状态
阶段指标 Camera open/config/HAL buffer/presented frame,Audio write/mix/output 周期
复查点 tsdur、进程、线程、slice/log 名称

示例结构可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Camera open analysis
trace: camera_open_run03.perfetto-trace
data_quality: clean
target_window: 123.000s..124.200s
phase:
open_api: 42 ms
configure_streams: 98 ms
first_hal_buffer: 286 ms
first_presented_frame: 486 ms
blocking:
cameraserver Binder thread runnable: 41 ms
provider HAL slice: 98 ms
review:
ts=123.456s process=cameraserver thread=Binder:1234 slice=connectDevice dur=73ms
ts=123.612s process=provider thread=CamX slice=configure_streams dur=98ms

报告里每个异常都要能跳回 Perfetto UI。只有结论没有时间点,别人没法复查。

对领域负责人来说,报告要指向可复查的时间段,而不是停在“脚本判断 Camera HAL 慢”。例如 provider configure 花了 98ms、对应 request id 是 17、同一时间 Binder 线程 Runnable 41ms、没有 data loss。脚本负责把上下文整理到一起,最终结论仍要能被 UI 和源码路径复核。

服务端和问题包还要有机器可读的 summary.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
{
"case_id": "case-20260504-camera-001",
"domain": "camera",
"scenario": "camera_open_preview",
"preset_id": "camera_open_lab_v4",
"config_version": "v4",
"trace_config_hash": "sha256:...",
"perfetto_version": "v51",
"trigger": "app_camera_open_slow",
"target_window": {
"start_ts": 123000000000,
"end_ts": 124200000000,
"source": "track_event"
},
"data_quality": "clean",
"required_signals_available": true,
"missing_required_signals": [],
"stats_findings": [],
"object_dictionary_version": "camera_domain_v1",
"domain_objects": ["camera_app_client", "camera_service", "camera_provider"],
"stage_metrics": [
{
"stage_name": "configure_streams",
"status": "measured",
"start_ts": 123612000000,
"end_ts": 123710000000,
"dur_ms": 98,
"source_role": "camera_provider",
"evidence_slice": "configure_streams",
"review_ts": 123612000000
}
],
"blocking_evidence": [
{
"type": "runnable",
"process": "cameraserver",
"thread": "Binder:1234",
"dur_ms": 41,
"source_sql": "sql/domain_thread_state.sql"
}
],
"app_marker_status": "matched",
"platform_event_status": "matched",
"fallback_used": "none",
"evidence_grade": "confirmed",
"assignee_hint": "platform-camera",
"ui_review_points": [
{
"ts": 123612000000,
"dur": 98000000,
"process": "provider",
"thread": "CamX",
"track_id": 88,
"slice": "configure_streams"
}
],
"privacy_mode": "log_tag_whitelist",
"redaction_status": "redacted",
"raw_trace_available": true,
"log_excerpt_policy": "window_only",
"retention_until": "2026-05-07T15:30:12+08:00",
"next_action": "owner_review"
}

让 App 和平台补稳定事件

Camera/Audio 自动化最怕名称漂移。不同 Android 版本、厂商 HAL、相机模组、音频路由,slice 名称都可能变。可控代码里应补稳定事件:

事件 owner required args 用途 隐私边界
Camera#OpenStart App camera_id, session_id, event_id open_api 起点 不写用户内容
Camera#OpenEnd App camera_id, session_id, event_id, result open_api 终点 错误码白名单
Camera#ConfigureStreamsStart platform camera_id, session_id, stream_id configure 阶段起点 分辨率/格式可保留
Camera#FirstPreviewFrame platform/App camera_id, session_id, stream_id, surface_id 可见首帧候选 不写画面内容
Audio#TrackStart App/platform track_id, route, sample_rate, buffer_frames 音频周期起点 route 用枚举
Audio#WriteBuffer App track_id, frames, buffer_level App 写入节奏 不写媒体内容
Audio#Underrun platform track_id, route, sample_rate, buffer_frames, underrun_count underrun 事件 route/tag 白名单
Audio#RouteChange platform old_route, new_route, reason route 变化 不写设备唯一标识

这些事件可以来自 android.os.Trace、NDK ATrace、Perfetto SDK Track Event 或平台侧 ATRACE。事件名要稳定,不能把动态 id 拼进事件名。

如果用 ATrace,重点是稳定 slice 名和 async cookie。ID 类信息优先放 Track Event 参数、flow id、log/side table 或同一 case 的元数据;counter 只放 queue depth、buffer level、underrun count 这类可解释数值。需要更完整的参数和跨线程关联时,优先用第 13 篇讲过的 Perfetto SDK Track Event。

平台自定义 data source 优先级更低。只有高频、强结构化,或普通 slice/counter 表达不了时才做;manifest 里要写清 data source name、producer 进程、SELinux 域、TraceConfig、解析器和 SQL 兼容策略。

从专项方法扩展到问题清单

Camera/Audio 只是例子。团队要把 Perfetto 用稳,需要把常见现象翻译成采集 preset、采集方、构建类型、分析路径和输出证据。

问题 基础数据源 专项加开 默认输出
UI 卡顿 sched、freq、idle、gfx、view、wm、input、process_stats、FrameTimeline binder_driver、App ATrace/Track Event 问题帧、App/HWUI/SurfaceFlinger 阶段、线程状态
点击响应慢 input、sched、freq、view、wm、binder_driver、FrameTimeline App 视觉状态 counter、高速相机对照 input 到首个可见变化
App 启动 sched、freq、am、wm、view、binder_driver、FrameTimeline log、CPU profiling、App ATrace/Track Event 启动阶段、首帧、Top wait
ANR sched、freq、binder_driver、am、wm、log lock、IO、CPU profiling 主线程等待证据
Binder 慢 sched、binder_driver、binder_lock、process_stats system_server log、接口事件 慢事务 TopN、server 状态
Native 内存 process_stats heapprofd、log、业务 trigger 分配栈和增长趋势
Camera sched、freq、camera、hal、binder_driver、FrameTimeline、log CPU profiling、平台事件 open/config/HAL buffer/可见首帧
Audio sched、freq、audio、hal、binder_driver、log CPU profiling、平台事件 underrun 前后周期抖动
功耗/温控 sched、freq、idle、android.power、thermal、log GPU counters、devfreq、power rails 资源趋势和异常窗口

每个 preset 都要有负责人、版本、用途、默认窗口、采集方、构建边界、数据源、开销等级、隐私等级和质量门禁。没有这些字段,preset 很快会变成一堆没人敢删的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
preset_id: ui_jank_v4
maintainer: platform-performance
mode: ring_buffer_stop_trigger
default_window: 30s
collector: platform_trace_service
consumer_identity: oem_perfetto_collector
selinux_domain: oem_tracing_service
allowed_data_sources:
- linux.ftrace
- android.surfaceflinger.frametimeline
- track_event
can_read_trace_output: true
upload_path: oem_diagnostics
supported_builds:
- userdebug
- oem_diagnostics_user
app_marker_policy:
atrace_apps:
- com.example.app
requires_profileable_or_debuggable: true
requires_userdebug_or_root: false
requires_vendor_producer: false
runtime_overhead_level: low
data_rate_budget_mb_s: 1.0
max_duration_s: 60
forbidden_sources_in_field:
- linux.perf
- heapprofd
- gpu.counters
- syscall_high_frequency
- page_fault_high_frequency
privacy_level: system-metadata-and-app-markers
trace_config: configs/ui_jank_v4.pbtx
config_format: binary_proto
config_hash: sha256:...
review_status: approved
required_signals:
- FrameTimeline
- sched
- thread_state
- process_stats
fallback_sources:
- bugreport_summary
- external_logcat
quality_gate:
quality_sql: sql/trace_health.sql
disallow_data_loss: true
required_stats_clean:
- ftrace/sched
- central_buffer
- clock
require_required_signals: true
report_template: reports/ui_jank.md

普通 App 不能自己开启系统级 ftrace、process stats 或 SurfaceFlinger FrameTimeline session。App 侧能做的是写稳定 marker、发已登记 trigger、补本地元数据;负责持有系统 trace session 的通常是平台服务、OEM 诊断组件、Traceur、shell 或实验室脚本。

user build 上只有 Traceur、shell、平台签名且 SELinux 授权的 OEM 组件能持有系统 trace session;任意普通 App 或未授权诊断 App 都不能直接控制系统级 Perfetto consumer socket。线上只允许审核过的 binary TraceConfig 或 preset id,不下发任意 text proto。

user build 上采 App profiling 还要看 profileable/debuggable。线上自动上传原始 trace、logcat 或 bugreport 之前,必须有用户授权、企业或 OEM 诊断协议、脱敏策略、访问控制、保留周期和审计记录。线上/bugreport 路径要在采集端或读取端配置 trace_filter / 字段级脱敏,并把过滤规则版本写进 case metadata。

报告也要给结论分级:

等级 写法 必须字段
确认 X 在窗口 A 内阻塞了 Y required_signals_available=truedata_quality=cleanui_review_points>=1source_sql
倾向 更像 X 方向 主证据成立,missing_evidence 列出缺源码、日志或领域状态
排除 暂不支持 X 结论 signal_present=true 且指标没有支持该方向,不能来自数据缺失
降级 本 Trace 不能判断 X degrade_reason 写明 data loss、数据源缺失或样本不足

这个分级能保护报告质量。工程讨论里很多争议并不来自 Trace 本身,而是来自结论等级写错:只有“倾向”的证据,被写成了“确认”。

平台和 OEM 侧要建设什么

App 侧能补业务语义,但很多问题发生在 App 看不到的地方:system_server Binder 堵塞、AudioFlinger underrun、Camera HAL 阻塞、Power HAL 策略切换、调度器迁核、GPU 执行轨道不足、Bugreport 里证据散落。平台侧要把四件事固定下来:preset、trigger、证据包格式、自动分析入口。

平台侧不要让工程师每次临时写 TraceConfig。TraceConfig 应该像接口一样被版本化管理,放在仓库里评审、测试、发布:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
perfetto-presets/
base/
base_low_overhead_v1.pbtx
ui/
ui_jank_field_v3.pbtx
media/
audio_underrun_field_v3.pbtx
camera_open_lab_v4.pbtx
system/
binder_slow_field_v2.pbtx
thermal_degrade_field_v2.pbtx
boottrace_v2.pbtx
manifest.yaml
sql/
ui_jank_summary.sql
audio_mixer_jitter.sql
camera_open_stage.sql

一套 Perfetto 体系至少分三层:

层级 使用场景 采集方 采集特点 典型数据源
field user build 线上/灰度问题 平台/OEM 诊断组件或 Traceur;App 只发 marker/trigger 低开销、Ring Buffer、短窗口 stop trigger sched、process stats、少量 App ATrace、必要 counter
lab 实验室复现和版本对比 shell、测试框架、平台脚本 可控设备、可重复脚本、指标稳定 FrameTimeline、freq、idle、Binder、领域 log 摘要
deep 专项排查 userdebug/root、平台工程工具 短时间、高开销、人工操作 heapprofd、linux.perf、GPU counters、更多 HAL 事件

不要把 deep preset 放到线上。CPU profiling、native heap profiling、GPU counters 这类能力在实验室和专项排查里很有价值,但在 user build 的长期现场采集里成本太高,隐私和稳定性风险也更高。

deep preset 的边界要单独写:

  • linux.perf 是采样 profiler,不是全量事件;默认只限短窗口、低频率、目标进程/线程 scope,并写清 unwind、kernel frame、root/userdebug/kptr 限制。
  • vendor tracepoint 先查 /sys/kernel/tracing/events/*/*/format、字段稳定性和 ftrace_setup_errors;未知事件只能作为 raw event 或候选证据。
  • field 层禁止 linux.perf、heapprofd、GPU counters、syscall/pagefault 高频事件;必要 counter 必须写采样周期、单位、设备支持范围和上传隐私级别。
  • 每个 preset 要有实测写入速率和 data loss 基线,不用一个 cost_level 代替真实预算。

Ring Buffer 适合保留触发前后的短窗口,不等于可以无限期留住全量上下文;长 Trace 要用第 15 篇那套后台采集、分段保存和明确退出条件。

trigger 中心也要收敛。App 代理、Framework watchdog、AudioFlinger、CameraService、Thermal/Power、实验室工具都只能发送已登记的 trigger 名称。平台侧再按设备、用户、版本、场景、网络和存储状态限频。名称进入平台后不要随意改,否则服务端索引、SQL 模板、历史趋势和告警规则都会断开。

Bugreport 也要和 Perfetto 结合。bugreport_score > 0 会把正在运行的 trace session 标记为 Bugreport 候选;bugreport_score <= 0 不 eligible。Android dumpstate 调用 perfetto --save-for-bugreport 时,会选择最高分候选 trace 保存到 Bugreport 路径。Android S/T 上会保存候选 trace 并提前停止原 session;Android U+ 上会创建只读快照,原 session 可以继续运行。bugreport_filename 是 Android V / Perfetto v42+ 字段。bugreport_score > 0 还会改变 clone/可附加行为,manifest 里要记录支持版本、隐私等级和可访问主体。

系统埋点要先建字典,再补代码:

1
2
3
4
5
6
7
8
9
10
11
camera:
CameraService#Open
CameraProvider#ConfigureStreams
CameraHAL#FirstRequest
CameraHAL#FirstResult

audio:
AudioTrack#Start
AudioFlinger#MixerCycle
AudioHAL#Write
BluetoothAudio#RouteChange

命名要稳定,含义要能被 SQL 聚合。动态信息放参数、counter 或 metadata,不要拼进事件名。URL、联系人、消息内容、搜索词、地理位置明文不能进入 trace event。

服务端至少要做五件事:文件验收、可信度验收、场景摘要、聚合索引、人工入口。人工打开 UI 前,报告里应该已经写清楚 preset、trigger、触发窗口、质量门禁状态、相关进程线程和机器摘要指向哪个负责人。

处理状态建议写成显式状态机:

1
2
3
4
5
6
7
8
9
10
ingested
-> quality_checked
-> scenario_classified
-> assignee_selected
-> evidence_graded
-> issue_linked
-> fix_build_recorded
-> same_preset_rerun
-> before_after_compared
-> regression_rule_updated

这个状态机的价值在于约束证据来源:每个结论都能回到同一份 preset、同一条 SQL 和同一种证据等级。版本升级后,如果字段改名、事件缺失、数据源不可用,也能在 quality_checked 阶段直接降级,报告不会继续输出看似确定的结论。

常见坑

  • 只按 LIKE '%camera%' 会漏掉 provider、vendor HAL、codec、media 进程。
  • 只开 atrace_categories 会漏掉 App 侧 android.os.Trace,需要 atrace_apps 或 Track Event。
  • 普通 App 不能自己打开系统 ftrace/process stats,只能配合平台采集方发 marker 或 trigger。
  • UI、启动、点击响应类问题不能把 FrameTimeline 当可选项;没有它时只能降级为线程/阶段分析。
  • android.log 只在 userdebug Trace 里可靠,user build 要准备替代来源。
  • Binder 慢只是现象,服务端可能在等 HAL、锁、IO 或下游硬件。
  • Audio 要看周期抖动,不要只看 Top long slice。
  • Camera 要拆 open、configure、preview、capture,不要混成一个总耗时。
  • Running 和 Runnable 要分开算,前者看 sched,后者看 thread_state
  • Agent 或脚本只能组织证据,不能替代 UI 复查和领域负责人判断。
  • Camera 首帧要区分 API 返回、HAL buffer 返回和画面显示。
  • Audio 周期阈值要按设备、route、buffer 配置建立基线。
  • 线上 preset 越开越重,会带来功耗、隐私和稳定性问题。
  • 系统埋点没有字典,事件名漂移后 SQL 和历史趋势都会失效。
  • Bugreport 和 Trace 分离,研发要在多个附件里手工找同一时间窗口。

小结

Camera、Audio 这类领域问题的处理方式是:固定 preset,建立对象字典,按阶段和周期写 SQL,输出带时间点的报告,再回 UI 复查异常点。

这套做法可以迁移到 WebView、Flutter、游戏和厂商自定义系统服务。差别只在对象字典和指标模板,底层仍然是 Trace 质量、进程线程归属、阶段耗时、调度状态和日志语义。平台侧再把 preset、trigger、Bugreport、系统埋点、服务端摘要和权限护栏补齐,Perfetto 才能从个人工具变成团队能力。

参考文档

  1. ATrace data source
  2. Track events
  3. Android Log data source
  4. Android Jank detection with FrameTimeline
  5. Buffers and dataflow
  6. Trace Processor Stats
  7. Getting Started with PerfettoSQL
  8. Trace Processor Python API
  9. AOSP Camera HAL
  10. AOSP Audio architecture
  11. Advanced System Tracing on Android
  12. TraceConfig reference
  13. Android <profileable> manifest element

关于我 && 博客

欢迎关注 Android Performance

CATALOG
  1. 1. 领域分析从对象字典开始
  2. 2. 配置从领域 preset 开始
  3. 3. Camera 看阶段
  4. 4. Audio 看周期
  5. 5. Running 和 Runnable 分开算
  6. 6. Log 用来补阶段语义
  7. 7. 报告输出要能回 UI
  8. 8. 让 App 和平台补稳定事件
  9. 9. 从专项方法扩展到问题清单
  10. 10. 平台和 OEM 侧要建设什么
  11. 11. 常见坑
  12. 12. 小结
  13. 13. 参考文档
  14. 14. 关于我 && 博客