CN

Android Perfetto Series 17: Domain Automation, Issue Checklists, and Platform...

Word count: 9.2kReading time: 57 min
2026/05/04
loading

For slow camera opens, audio underruns, a stalled WebView first screen, or dropped frames in Flutter and games, the evidence is scattered across the app, system_server, cameraserver/audioserver, HAL, SurfaceFlinger, and scheduling tracks. Simply inspecting a few more tracks in the UI can easily leave critical timestamps unnoticed.

Using Camera and Audio as examples, this article describes a reusable method for domain-specific analysis: capture the right data, identify the objects, calculate stages and blocking, and preserve timestamps in the report so that reviewers can return to the UI. Camera and Audio are examples; the core is a domain schema and collaboration with platform tracing. By the end, you should at least be able to break a slow camera open into reviewable stages and an audio underrun into cycle anomalies, rather than delivering only a table of the longest slices.

Version note (reviewed October 2026): Configuration, SQL tables, and triggers are checked against Perfetto v58.3. Record Android, device-producer, and analysis-tool versions separately. The domain schema, event names, roles, state machine, and field restrictions below are proposed team conventions, not built-in Android interfaces or validated product capabilities.

Perfetto Series Catalog

  1. Android Perfetto Series Catalog
  2. Android Perfetto Series 1: Introduction to Perfetto
  3. Android Perfetto Series 2: Capturing Perfetto Traces
  4. Android Perfetto Series 3: Familiarizing with the Perfetto View
  5. Android Perfetto Series 4: Opening Large Traces via Command Line
  6. Android Perfetto Series 5: Choreographer-based Rendering Flow
  7. Android Perfetto Series 6: Why 120Hz? Advantages and Challenges
  8. Android Perfetto Series 7: MainThread and RenderThread Deep Dive
  9. Android Perfetto Series 8: Understanding Vsync and Performance Analysis
  10. Android Perfetto Series 9: Interpreting CPU Information
  11. Android Perfetto Series 10: Binder Scheduling and Lock Contention
  12. Android Perfetto Series 11: PerfettoSQL, Trace Processor and Regression Detection
  13. Android Perfetto Series 12: Trace Dataflow and Data Loss
  14. Android Perfetto Series 13: Perfetto SDK, Track Event and App Field Traces
  15. Android Perfetto Series 14: heapprofd and Memory Profiling
  16. Android Perfetto Series 15: Boot Traces and Long-running Field Tracing
  17. Android Perfetto Series 16: GPU, Power Counters and Hardware Bottlenecks
  18. Android Perfetto Series 17: Scenario Automation and Platform Tracing
  19. Android Perfetto Series 18: Input Response Latency
  20. Video (Bilibili) - Android Perfetto Basics and Case Studies
  21. Video (Bilibili) - Android Perfetto: Trace Graph Types - AOSP, WebView, Flutter + OEM System Optimization

This article applies the preceding methods to domain automation: the SQL methods from Part 11, quality checks from Part 12, application semantics from Part 13, and field capture from Part 15 all come together here in specific domains and platform infrastructure. Rather than explaining those methods again, we reorganize them around domain objects.

Domain analysis preserves objects and review points

Illustration: Camera stages belong to the same operation; Audio periods are measured from actual cycles. Reports retain timestamps for returning to the UI.

Domain Analysis Starts with an Object Dictionary

The difficulty with Camera and Audio is not whether a camera/audio category exists, but which objects are actually present in the trace:

Domain Objects Why identify them?
Camera App client, cameraserver, provider, HAL, request/session IDs Open, configure, preview, and capture can span different processes
Audio App audio thread, audioserver, MixerThread, FastMixer, audio HAL, Bluetooth/audio route Underruns often result from cycle jitter, irregular writes, HAL blocking, or scheduling delays

The first step in automated analysis is to generate an object dictionary: relevant processes, threads, tracks, slice names, log tags, and request/session IDs. Subsequent SQL does not guess a root cause directly; it calculates stage durations and blocking evidence around this dictionary.

The object dictionary should be script output, rather than a set of keywords that exists only in someone’s head. A Camera case may involve the app process, cameraserver, provider@2.7-service, a vendor camera daemon, a codec, and SurfaceFlinger. An Audio case may involve the app, audioserver, audio.primary, Bluetooth, and a media codec.

An initially confirmed object dictionary looks like this. SQL is only one way to generate and validate it:

These are hypothetical reviewed records, not capture results. upid/utid/track_id are local to one trace; archive its hash, machine/boot identity, schema, and SQL versions with the dictionary. Leave confidence null without a calibrated model; keyword matches are not probabilities. Package, SELinux domain, and vendor ownership each need provenance. The old HIDL provider name is only an example: Camera supports AIDL HALs from Android 13, while Audio HAL development uses AIDL from Android 14. Inspect actual service names and transports instead of inferring them from OS versions or process suffixes.

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
object_dictionary_version: camera_audio_v1
objects:
- role: camera_app_client
object_type: track
process_name: com.example.app
thread_name: main
upid: 42
utid: 51
track_id: 102
uid: 10123
package_name: com.example.app
service_instance: ""
hal_transport: ""
selinux_context_or_domain: null
vendor_owner: app-team
source_signal: track_event
confidence: null
owner_confirmed: true
- role: camera_service
object_type: process
process_name: cameraserver
vendor_owner: platform-camera
source_signal: atrace
confidence: null
owner_confirmed: true

The domain schema is the collaboration interface between the app, platform, SQL, and reports. Version it rather than scattering it across articles, scripts, and verbal agreements:

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
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: [client_instance_id, camera_id, session_id, operation_id, event_id]
- event_name: Camera#ConfigureStreamsStart
owner: platform
required_args: [client_instance_id, camera_id, session_id, operation_id, stream_id]
- event_name: Camera#FirstPreviewFrame
owner: platform
required_args: [client_instance_id, camera_id, session_id, operation_id, stream_id, surface_id]
- event_name: Camera#Opened
owner: app
required_args: [client_instance_id, camera_id, session_id, operation_id, result]
- event_name: Camera#OpenEnd
owner: app
required_args: [client_instance_id, camera_id, session_id, operation_id, event_id, result]
- event_name: Camera#ConfigureStreamsEnd
owner: platform
required_args: [client_instance_id, camera_id, session_id, operation_id, stream_id, result]
- event_name: Camera#FirstHalBuffer
owner: platform
required_args: [client_instance_id, camera_id, session_id, operation_id, stream_id]
stage_definitions:
- stage_name: open_api
start_event: Camera#OpenStart
end_event: Camera#OpenEnd
- stage_name: camera_open_callback
start_event: Camera#OpenStart
end_event: Camera#Opened
- stage_name: first_preview_marker
start_event: Camera#OpenStart
end_event: Camera#FirstPreviewFrame
report_fields:
- stage_name
- status
- start_ts
- end_ts
- dur_ms
- start_slice_id
- end_slice_id
- evidence_slice
- review_ts
- source_role

The script first lists candidate objects, then the domain owner confirms which belong to this scenario. That confirmation must be recorded in a domain_objects table or equivalently structured YAML/JSON, rather than remaining in someone’s head. Subsequent queries join only against this dictionary.

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
CREATE TABLE domain_objects AS
SELECT
'candidate' AS role,
'track' AS object_type,
p.name AS process_name,
p.upid,
p.machine_id,
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,
NULL AS confidence,
0 AS owner_confirmed
FROM thread th
LEFT JOIN process p USING (upid)
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 ('com.example.app', 'cameraserver', 'audioserver',
'surfaceflinger', '/system/bin/surfaceflinger');

Note that this uses ordinary SQLite CREATE TABLE, not CREATE PERFETTO TABLE: the latter creates a read-only table, so UPDATE cannot write back the results of manual confirmation. At a minimum, the fields must cover role, process/thread, track_id, vendor aliases, confidence, and manual confirmation status. The initial version can still be generated from keywords, but reports may use only objects with owner_confirmed = 1. Otherwise, as soon as a vendor HAL, provider, codec, or Bluetooth route changes, the automation falls back to fuzzy matching.

List candidate objects for the domain owner to review as follows:

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

A person needs to inspect this output. Automation can narrow the scope, but LIKE cannot reliably establish ownership for every provider, HAL, codec, and Bluetooth route.

Write confirmed objects back to the table or archive equivalent YAML/JSON. The following updates only individually reviewed track_id values. Replace NULL; leaving it unset updates nothing. Check upid/utid and track ownership before filling each row, rather than approving every thread in a process. Confirm Audio roles separately.

1
2
3
4
5
6
7
8
9
10
11
WITH confirmed(track_id, role) AS (
VALUES (NULL, 'camera_app_client'),
(NULL, 'camera_service'),
(NULL, 'camera_provider')
)
UPDATE domain_objects
SET owner_confirmed = 1,
role = (SELECT role FROM confirmed c WHERE c.track_id = domain_objects.track_id)
WHERE track_id IN (SELECT track_id FROM confirmed WHERE track_id IS NOT NULL)
AND (SELECT COUNT(*) FROM confirmed c
WHERE c.track_id = domain_objects.track_id) = 1;

Subsequent SQL uses only objects with owner_confirmed = 1. The candidate table narrows review scope; the confirmed table records ownership. The fixed role enum includes camera_app_client, camera_service, camera_provider, camera_hal, display_service, audio_app, audio_server, audio_mixer, audio_hal, and bluetooth_audio.

The candidate query covers thread tracks only, and keywords do not guarantee completeness. Add process-level, asynchronous, or custom tracks separately after verifying ownership; shared names do not confer confirmation. Slice queries below match exact tracks, while scheduling queries deduplicate their confirmed utid values. Create, confirm, and query the dictionary in the same Trace Processor instance. Rebuilding it loses confirmations; archive them first.

Start Configuration with a Domain Preset

First make the permission boundaries explicit:

Capability Who can use it Notes
App markers / metadata Ordinary apps android.os.Trace, NDK ATrace, Track Event, application metadata
Registered triggers Ordinary apps or test harnesses Can activate only trigger names predeclared by the platform
App profiling shell + profileable or debuggable Prefer profileable for release performance comparisons; use debuggable only for debugging
ftrace/process stats/FrameTimeline/system log shell, Traceur, trusted platform/OEM consumers Subject to SELinux and build-type restrictions; ordinary apps cannot submit arbitrary system configurations
Uploads, rate limits, privacy, preset selection Platform/OEM diagnostics workflow Requires authorization, redaction, retention periods, and auditing

The following is a lab_domain_debug configuration, not a low-overhead field preset. It retains sched/freq/idle, Camera/Audio/hal/binder atrace, FrameTimeline, process stats, and logs for development devices, userdebug, or laboratory reproduction. Reduce the field version to necessary data sources; enable logs, binder_lock, and high-frequency domain events only for short, focused traces.

Check configuration against the actual Android and Perfetto versions. On Android 12+, prefer /data/misc/perfetto-configs; shell stdin redirection and a direct file open by perfetto follow different permission paths. A visible camera frame still needs target-layer/present evidence; FrameTimeline does not cover every preview path. The 128 MiB central buffer is a laboratory starting point and already consumes the default extra-guardrails total budget. Measure write rates and window coverage. This configuration does not periodically write a file: flush_period_ms requests producer flushes, not a file save every ten seconds.

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
66
67
68
69
70
71
72
73
74
75
76
77
78
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"
# Use categories/events supported by the target device Record page or --query output.
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: "track_event"
track_event_config {
disabled_categories: "*"
enabled_categories: "camera"
enabled_categories: "audio"
}
}
# Replace with an App/platform producer name actually registered on the device; repeat as needed.
producer_name_filter: "<confirmed_producer_name>"
}

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 enables Android system categories; it does not automatically enable android.os.Trace or NDK ATrace in the app. App-side ATrace shares ATRACE_TAG_APP and requires package-specific atrace_apps configuration, with no separate category filtering. When arguments and cross-thread correlation are needed, use the Perfetto SDK Track Event approach from Part 13.

On user builds, release apps can expose supported shell-profiling access with <profileable android:shell="true" />; debuggable changes runtime characteristics. CPU/native profiling is not universally restricted to userdebug: eligibility depends on the tool, app, heap type, and platform policy. Check system-service, HAL, kernel-stack, and other-process permissions separately. Android 15/API 35 ProfilingManager lets apps request system-controlled profiling, including system traces; this does not grant arbitrary TraceConfig, consumer-socket access, or access to other apps’ raw data.

Official documentation lists android.log support for userdebug. A rooted user build may also capture logs if logd/SELinux permissions allow it, but adb root alone does not guarantee this as a general capability. On ordinary user builds or in production, do not assume logs will be in the trace. Include external logcat, bugreports, or platform-redacted logs in the same case package. Field packages should retain only allowlisted tags and window summaries; raw logs require authorization and redaction.

Category names also depend on the target system. Perfetto documentation separates ATrace into system categories and per-app events. System categories come from Android internal processes; app events share ATRACE_TAG_APP and must be enabled by package name. Do not simply copy the category list from someone else’s device into a preset. Check supported categories for the current version in the Perfetto UI Record page or the device-side configuration.

buffers.size_kb configures the Perfetto central buffer, not the ftrace per-CPU kernel ring buffer. For sched/ftrace data loss, inspect ftrace overrun/dropped/data_loss entries in stats. Adjust parameters such as ftrace_config.buffer_size_kb and drain_period_ms only when necessary, first verifying device compatibility and overhead with a short trace. Before adding vendor kernel tracepoints, check /sys/kernel/tracing/events/*/*/format, field stability, and Perfetto parser/stdlib support. Otherwise, they can only serve as candidate evidence in raw ftrace, or require changes to the Perfetto parser or a custom data source.

Do not express the quality gate as only an abstract score. At a minimum, always run this 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;

The meaning of idx varies by statistic: for traced_buf_* it is usually a buffer index, and for ftrace CPU statistics it is a CPU number; check definitions for other entries. Group findings by affected_signal: ftrace/sched, atrace/systrace, track_event, FrameTimeline, android.log, clock, and central_buffer. Explain overwritten data separately under RING_BUFFER: it may simply mean old data outside the window was overwritten, or that insufficient pre-issue evidence remains. Apply separate report downgrades for data loss, packet loss, and overwrites.

This SQL lists anomalies; it does not prove completeness. Inspect detailed configuration/startup metadata, target events, issue-window coverage, clocks, and parser compatibility before assigning data_quality=clean. Setup statistics can have info severity in v58.3, so retain the explicit list. Trace-wide or buffer statistics do not identify a particular affected HAL event. Record unknown impact separately. filter_tags filters tags, not user content inside messages; external logs also need clock alignment.

Analyze Camera by Stage

For a slow camera open, I want to follow the operation in order: when the API returns, when the HAL supplies a buffer, and when the preview actually appears. Break it into at least these stages:

Stage Owner What to examine
App initiates open App marker User action, permissions, whether the UI thread is waiting
App to cameraserver App + camera service Binder round trips, server thread-pool congestion
cameraserver to provider/HAL Platform event Provider process, HAL threads, device waits
Configure streams Platform/HAL event Stream count, resolution, Surface, HAL configure duration
First HAL buffer HAL/provider event Request submission, buffer returned from HAL
First presented frame SurfaceFlinger layer/present evidence + app marker; FrameTimeline where supported Verify that the Buffer was actually presented; SurfaceView previews cannot be judged from FrameTimeline alone
Capture/result Platform/HAL event Request ID, result callback, buffer/metadata return

These are three different measurement endpoints: API return, the first frame returned from HAL, and the first frame visible to the user. The first two primarily concern the Camera framework/HAL; the third also involves BufferQueue and SurfaceFlinger. FrameTimeline availability depends on the preview path. State which one you measured in the report. Otherwise, “open got faster” might mean only that the API returned earlier, with no improvement to the first preview frame.

Camera2 needs another distinct endpoint: return from CameraManager.openCamera() ends the call, while CameraDevice.StateCallback.onOpened() reports successful opening. Session creation and request submission follow. Below, open_api measures only the call; camera_open_callback measures OpenStart to Opened, including callback-dispatch delay, not pure HAL work. Record success, exceptions, and disconnection separately. FirstHalBuffer is also distinct from capture-result metadata and presentation.

Domain SQL must be tied to the issue window. That window can come from trigger metadata, application markers, user reproduction steps, or an interval selected manually in the UI. If owner events are missing, report missing_owner_event or stage_unavailable; do not promote a LIKE query into a stage conclusion.

1
2
3
4
5
6
7
WITH target_window(start_ts, end_ts) AS (
VALUES (NULL, NULL)
)
SELECT start_ts, end_ts, (end_ts - start_ts) / 1e9 AS duration_s
FROM target_window
WHERE typeof(start_ts) = 'integer' AND typeof(end_ts) = 'integer'
AND end_ts > start_ts;

For stable events, select one operation from one client instance and propagate client_instance_id/camera_id/session_id/operation_id across processes, matching the target stream and surface separately. This SQL assumes zero-duration Track Event markers using the schema names, or equivalent normalized events retaining original IDs. ATrace async slices do not directly satisfy that contract. Replace NULL with integer-nanosecond boundaries and confirmed IDs; the half-open window must contain the full operation. Missing, duplicate, reversed, or explicitly non-successful endpoints do not produce successful-stage durations. Retain original slice/track/argument evidence for review.

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
66
67
68
69
70
71
72
73
74
WITH target_window(start_ts, end_ts) AS (
VALUES (NULL, NULL)
),
target_operation(client_instance_id, camera_id, session_id, operation_id, stream_id, surface_id) AS (
VALUES (NULL, NULL, NULL, NULL, NULL, NULL)
),
events AS (
SELECT s.id, s.track_id, s.name, s.ts,
EXTRACT_ARG(s.arg_set_id, 'debug.result') AS result
FROM slice s
CROSS JOIN target_window w
CROSS JOIN target_operation o
WHERE typeof(w.start_ts) = 'integer' AND typeof(w.end_ts) = 'integer'
AND w.end_ts > w.start_ts AND s.ts >= w.start_ts AND s.ts < w.end_ts
AND s.dur = 0
AND CAST(EXTRACT_ARG(s.arg_set_id, 'debug.client_instance_id') AS TEXT) = o.client_instance_id
AND CAST(EXTRACT_ARG(s.arg_set_id, 'debug.camera_id') AS TEXT) = o.camera_id
AND CAST(EXTRACT_ARG(s.arg_set_id, 'debug.session_id') AS TEXT) = o.session_id
AND CAST(EXTRACT_ARG(s.arg_set_id, 'debug.operation_id') AS TEXT) = o.operation_id
AND (s.name IN ('Camera#OpenStart', 'Camera#OpenEnd', 'Camera#Opened')
OR CAST(EXTRACT_ARG(s.arg_set_id, 'debug.stream_id') AS TEXT) = o.stream_id)
AND (s.name != 'Camera#FirstPreviewFrame'
OR CAST(EXTRACT_ARG(s.arg_set_id, 'debug.surface_id') AS TEXT) = o.surface_id)
AND EXISTS (
SELECT 1 FROM domain_objects d
WHERE d.owner_confirmed = 1 AND d.track_id = s.track_id
AND d.role IN ('camera_app_client', 'camera_service', 'camera_provider',
'camera_hal', 'display_service')
)
),
stage_pairs(stage_name, start_name, end_name, require_success) AS (
VALUES
('open_api', 'Camera#OpenStart', 'Camera#OpenEnd', 1),
('camera_open_callback', 'Camera#OpenStart', 'Camera#Opened', 1),
('configure_streams', 'Camera#ConfigureStreamsStart', 'Camera#ConfigureStreamsEnd', 1),
('first_hal_buffer', 'Camera#OpenStart', 'Camera#FirstHalBuffer', 0),
('first_preview_marker', 'Camera#OpenStart', 'Camera#FirstPreviewFrame', 0)
),
stages AS (
SELECT p.stage_name, p.require_success,
COUNT(CASE WHEN e.name = p.start_name THEN 1 END) AS start_count,
COUNT(CASE WHEN e.name = p.end_name THEN 1 END) AS end_count,
GROUP_CONCAT(e.id) AS candidate_slice_ids,
MAX(CASE WHEN e.name = p.start_name THEN e.id END) AS start_slice_id,
MAX(CASE WHEN e.name = p.end_name THEN e.id END) AS end_slice_id,
MAX(CASE WHEN e.name = p.start_name THEN e.ts END) AS start_ts,
MAX(CASE WHEN e.name = p.end_name THEN e.ts END) AS end_ts,
MAX(CASE WHEN e.name = p.end_name THEN e.result END) AS end_result
FROM stage_pairs p
LEFT JOIN events e ON e.name IN (p.start_name, p.end_name)
GROUP BY p.stage_name, p.require_success
),
classified AS (
SELECT *, CASE
WHEN NOT EXISTS (SELECT 1 FROM target_window w, target_operation o
WHERE typeof(w.start_ts) = 'integer' AND typeof(w.end_ts) = 'integer'
AND w.end_ts > w.start_ts AND o.client_instance_id IS NOT NULL
AND o.camera_id IS NOT NULL AND o.session_id IS NOT NULL
AND o.operation_id IS NOT NULL AND o.stream_id IS NOT NULL
AND o.surface_id IS NOT NULL) THEN 'invalid_selection'
WHEN start_count = 0 OR end_count = 0 THEN 'stage_unavailable'
WHEN start_count != 1 OR end_count != 1 OR end_ts < start_ts THEN 'ambiguous_or_invalid'
WHEN require_success = 1 AND end_result IS NULL THEN 'missing_result'
WHEN require_success = 1 AND end_result != 'success' THEN 'non_success'
ELSE 'marker_measured' END AS status
FROM stages
)
SELECT stage_name, status, start_count, end_count, candidate_slice_ids,
CASE WHEN start_count = 1 THEN start_slice_id END AS start_slice_id,
CASE WHEN end_count = 1 THEN end_slice_id END AS end_slice_id,
CASE WHEN start_count = 1 THEN start_ts END AS start_ts,
CASE WHEN end_count = 1 THEN end_ts END AS end_ts,
CASE WHEN status = 'marker_measured' THEN (end_ts - start_ts) / 1e6 END AS dur_ms
FROM classified;

If stable events have not yet been fully instrumented, fall back to a candidate-slice query. This query only establishes candidate stages. Focus on process_name, thread_name, slice_name, and dur_ms; it cannot directly replace the stage definitions above:

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(start_ts, end_ts) AS (
VALUES (NULL, NULL)
)
SELECT s.id AS slice_id, s.track_id, s.ts, s.dur,
CASE WHEN s.dur >= 0 THEN s.dur / 1e6 END AS dur_ms,
CASE WHEN s.dur >= 0 THEN MAX(0, MIN(s.ts + s.dur, w.end_ts) - MAX(s.ts, w.start_ts)) END AS overlap_ns,
CASE WHEN s.dur = -1 THEN 'unclosed_candidate' ELSE 'observed' END AS status,
s.name AS slice_name, th.name AS thread_name, p.name AS process_name,
th.utid, th.tid, p.upid, p.pid, p.machine_id
FROM slice s
JOIN thread_track tt ON s.track_id = tt.id
JOIN thread th USING (utid)
LEFT JOIN process p USING (upid)
CROSS JOIN target_window w
WHERE typeof(w.start_ts) = 'integer' AND typeof(w.end_ts) = 'integer'
AND w.end_ts > w.start_ts
AND EXISTS (
SELECT 1 FROM domain_objects d
WHERE d.track_id = s.track_id AND d.owner_confirmed = 1
AND d.role IN ('camera_app_client', 'camera_service', 'camera_provider', 'camera_hal')
)
AND ((s.dur > 0 AND s.ts < w.end_ts AND s.ts + s.dur > w.start_ts)
OR (s.dur = 0 AND s.ts >= w.start_ts AND s.ts < w.end_ts)
OR (s.dur = -1 AND s.ts < w.end_ts))
ORDER BY s.dur DESC
LIMIT 100;

If this query does not provide enough stage information, there are usually two options: add android.os.Trace / ATRACE / Track Event instrumentation, or have the platform add stable event names at key stages in CameraService, the provider, and HAL. Validate camera_id/session_id/request_id in event arguments. With Track Event, obtain them from args/debug annotations or flow IDs rather than relying only on event names.

Candidate SQL retains raw nanoseconds, slice/track/upid/utid, and window overlap. Zero-duration markers are selected by timestamp; dur=-1 remains an unclosed candidate without a duration calculation. Inspect other invalid negative values separately. Top-N truncation is for reading, not proof of complete stage coverage.

Analyze Audio by Cycle

Audio issues depend even more on periodic behavior than Camera issues. One long slice does not necessarily cause an audible problem. Jitter across several consecutive mixer cycles, irregular app write intervals, and blocking HAL writes are more directly relevant to underruns or dropouts.

An Audio report should first examine whether the tail of the cycle-duration distribution grows, whether anomalies occur consecutively, and whether they coincide with underrun logs, route changes, or the time of an audible problem. The longest slices are supporting clues only.

Audio candidate slices must also be restricted to the issue window and should preferentially use domain_objects, preventing historical noise from the entire trace from entering the analysis:

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(start_ts, end_ts) AS (
VALUES (NULL, NULL)
)
SELECT s.id AS slice_id, s.track_id, s.ts, s.dur,
CASE WHEN s.dur >= 0 THEN s.dur / 1e6 END AS dur_ms,
CASE WHEN s.dur >= 0 THEN MAX(0, MIN(s.ts + s.dur, w.end_ts) - MAX(s.ts, w.start_ts)) END AS overlap_ns,
CASE WHEN s.dur = -1 THEN 'unclosed_candidate' ELSE 'observed' END AS status,
s.name AS slice_name, th.name AS thread_name, p.name AS process_name,
th.utid, th.tid, p.upid, p.pid, p.machine_id
FROM slice s
JOIN thread_track tt ON s.track_id = tt.id
JOIN thread th USING (utid)
LEFT JOIN process p USING (upid)
CROSS JOIN target_window w
WHERE typeof(w.start_ts) = 'integer' AND typeof(w.end_ts) = 'integer'
AND w.end_ts > w.start_ts
AND EXISTS (
SELECT 1 FROM domain_objects d
WHERE d.track_id = s.track_id AND d.owner_confirmed = 1
AND d.role IN ('audio_app', 'audio_server', 'audio_mixer', 'audio_hal', 'bluetooth_audio')
)
AND ((s.dur > 0 AND s.ts < w.end_ts AND s.ts + s.dur > w.start_ts)
OR (s.dur = 0 AND s.ts >= w.start_ts AND s.ts < w.end_ts)
OR (s.dur = -1 AND s.ts < w.end_ts))
ORDER BY s.ts, s.id
LIMIT 200;

Inspect scheduling intervals for confirmed audio_mixer threads. Preemption can split one work cycle into multiple sched rows, so adjacent Running starts do not define a mixer period. The query preserves utid/upid/ucpu and raw nanoseconds, calculating gaps only between neighboring completed records in the window. Leave the last interval unset without a next row. Correlate thread_state, HAL, underruns, and routes; missing sched records prevent interpreting surviving neighbors as complete scheduling history.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
WITH target_window(start_ts, end_ts) AS (
VALUES (NULL, NULL)
),
mixer_runs AS (
SELECT ss.id, ss.utid, th.upid, ss.ucpu, ss.ts, ss.dur,
th.name AS thread_name,
LEAD(ss.ts) OVER (PARTITION BY ss.utid ORDER BY ss.ts, ss.id) AS next_ts
FROM sched ss
JOIN thread th USING (utid)
CROSS JOIN target_window w
WHERE typeof(w.start_ts) = 'integer' AND typeof(w.end_ts) = 'integer'
AND w.end_ts > w.start_ts AND ss.dur > 0
AND ss.ts < w.end_ts AND ss.ts + ss.dur > w.start_ts
AND EXISTS (SELECT 1 FROM domain_objects d
WHERE d.utid = ss.utid AND d.owner_confirmed = 1 AND d.role = 'audio_mixer')
)
SELECT id AS sched_id, utid, upid, ucpu, ts, dur, next_ts, thread_name,
dur / 1e6 AS full_running_ms,
CASE WHEN next_ts >= ts + dur THEN (next_ts - ts) / 1e6 END AS sched_start_interval_ms,
CASE WHEN next_ts >= ts + dur THEN (next_ts - (ts + dur)) / 1e6 END AS off_cpu_gap_ms
FROM mixer_runs
ORDER BY utid, ts, id;

sched_start_interval_ms and off_cpu_gap_ms describe scheduling only. To measure mixer periods, use a confirmed application/platform slice emitted once per cycle, such as the AudioFlinger#MixerCycle event defined below. Restrict to one track and route generation, then calculate adjacent start intervals in time order. Do not compare arbitrary context switches with audio buffer deadlines.

After confirming that AudioFlinger#MixerCycle has been instrumented or mapped, calculate actual cycle start intervals:

This example additionally requires integer cycle_seq and route_generation. Change the generation after route changes, playback restarts, or mixer reconfiguration; leave periods unset for sequence gaps or duplicate timestamps. Select one confirmed track and generation, and output time order to preserve consecutive anomalies. Without those fields, report candidate intervals only. Slice duration is wall-clock work span, potentially including waits, not CPU Running time.

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
WITH target(start_ts, end_ts, track_id, route_generation) AS (
VALUES (NULL, NULL, NULL, NULL)
),
raw_cycles AS (
SELECT s.id, s.track_id, s.ts, s.dur, tt.utid,
EXTRACT_ARG(s.arg_set_id, 'debug.cycle_seq') AS cycle_seq
FROM slice s
JOIN thread_track tt ON s.track_id = tt.id
CROSS JOIN target w
WHERE typeof(w.start_ts) = 'integer' AND typeof(w.end_ts) = 'integer'
AND w.end_ts > w.start_ts AND s.ts >= w.start_ts AND s.ts < w.end_ts
AND s.track_id = w.track_id AND s.name = 'AudioFlinger#MixerCycle'
AND CAST(EXTRACT_ARG(s.arg_set_id, 'debug.route_generation') AS TEXT) = w.route_generation
AND EXISTS (SELECT 1 FROM domain_objects d
WHERE d.track_id = s.track_id AND d.owner_confirmed = 1 AND d.role = 'audio_mixer')
),
cycles AS (
SELECT *, COUNT(*) OVER (PARTITION BY track_id, ts) AS timestamp_rows,
COUNT(*) OVER (PARTITION BY track_id, cycle_seq) AS sequence_rows
FROM raw_cycles
),
pairs AS (
SELECT *, LEAD(ts) OVER win AS next_ts, LEAD(cycle_seq) OVER win AS next_seq,
LEAD(timestamp_rows) OVER win AS next_timestamp_rows,
LEAD(sequence_rows) OVER win AS next_sequence_rows
FROM cycles
WINDOW win AS (PARTITION BY track_id ORDER BY ts, id)
)
SELECT track_id, utid, id AS slice_id, ts, dur, cycle_seq, next_ts,
CASE WHEN dur >= 0 THEN dur / 1e6 END AS cycle_wall_ms,
CASE WHEN timestamp_rows = 1 AND next_timestamp_rows = 1
AND sequence_rows = 1 AND next_sequence_rows = 1
AND typeof(cycle_seq) = 'integer' AND typeof(next_seq) = 'integer'
AND next_seq = cycle_seq + 1 AND next_ts > ts AND dur >= 0 AND ts + dur <= next_ts
THEN (next_ts - ts) / 1e6 END AS mixer_cycle_period_ms
FROM pairs
ORDER BY track_id, ts, id;

Do not assign period_ms a fixed threshold across devices. It depends on sample rate, buffer size, fast path, Bluetooth route, offload, and how AAudio/OpenSL ES/AudioTrack is used. A more reliable method is to establish a baseline from clean playback on the same device and route, then compare whether the tail grows in the problematic capture, whether it grows across consecutive cycles, and whether that coincides with underrun logs or audible-problem timestamps.

Calculate Running and Runnable Separately

Many domain reports mix thread states together. Parts 09 and 11 already explain Running/Runnable definitions, the meaning of R+, and the prerequisites for scheduling evidence: complete sched data and no ftrace loss in stats. We will not repeat them here. Domain analysis differs in only one respect: the threads being measured come from the domain_objects dictionary with owner_confirmed = 1, rather than guesses based on thread names.

This SQL outputs both Running and Runnable for domain threads within the issue window:

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
WITH target_window(start_ts, end_ts) AS (
VALUES (NULL, NULL)
),
domain_threads AS (
SELECT th.utid, th.name AS thread_name, p.name AS process_name, p.upid, p.machine_id
FROM thread th LEFT JOIN process p USING (upid)
WHERE EXISTS (SELECT 1 FROM domain_objects d
WHERE d.utid = th.utid AND d.owner_confirmed = 1)
),
state_totals AS (
SELECT s.utid,
SUM(MIN(s.ts + s.dur, w.end_ts) - MAX(s.ts, w.start_ts)) AS covered_ns,
SUM(CASE WHEN s.state = 'Running'
THEN MIN(s.ts + s.dur, w.end_ts) - MAX(s.ts, w.start_ts) ELSE 0 END) AS running_ns,
SUM(CASE WHEN s.state IN ('R', 'R+')
THEN MIN(s.ts + s.dur, w.end_ts) - MAX(s.ts, w.start_ts) ELSE 0 END) AS runnable_ns
FROM thread_state s
CROSS JOIN target_window w
WHERE typeof(w.start_ts) = 'integer' AND typeof(w.end_ts) = 'integer'
AND w.end_ts > w.start_ts AND s.dur > 0 AND s.ts < w.end_ts AND s.ts + s.dur > w.start_ts
GROUP BY s.utid
)
SELECT
t.utid, t.upid, t.machine_id, w.start_ts, w.end_ts, s.covered_ns, t.process_name, t.thread_name,
CASE WHEN typeof(w.start_ts) != 'integer' OR typeof(w.end_ts) != 'integer'
OR w.end_ts <= w.start_ts THEN 'invalid_window'
WHEN s.covered_ns = w.end_ts - w.start_ts THEN 'full'
WHEN s.covered_ns IS NULL THEN 'missing' ELSE 'partial' END AS sched_coverage,
CASE WHEN s.covered_ns = w.end_ts - w.start_ts THEN s.running_ns / 1e6 END AS running_ms,
CASE WHEN s.covered_ns = w.end_ts - w.start_ts THEN s.runnable_ns / 1e6 END AS runnable_ms
FROM domain_threads t
CROSS JOIN target_window w
LEFT JOIN state_totals s USING (utid)
ORDER BY runnable_ms DESC, running_ms DESC;

This uses Running/Runnable states in thread_state for a consistent calculation. Durations remain blank when records are missing or cover only part of the window. full means only interval coverage and does not replace stats checks. If a thread was alive for only part of the window, narrow the window before calculating again.

Coverage assumes non-overlapping state intervals for each utid. Downgrade overlaps or invalid durations rather than allowing sums to hide gaps. Trace Processor normalizes event clocks; external markers/logs still need same-boot identity and a demonstrated clock conversion. Wall-clock or MONOTONIC timestamps cannot simply be relabeled as the same nanosecond timeline.

If Runnable time is high, first examine CPU contention, thread priority, frequency, and wakeup sources. If Running time is high, consider CPU profiling or function-level hotspots.

Use Logs to Add Stage Semantics

Logs are useful for adding request IDs, session IDs, route changes, underruns, and error codes. When the trace includes android.log, restrict it to the issue window as well:

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(start_ts, end_ts) AS (
VALUES (NULL, NULL)
)
SELECT
l.id AS log_id, l.ts, l.utid, th.upid, p.machine_id,
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 w
WHERE typeof(w.start_ts) = 'integer' AND typeof(w.end_ts) = 'integer'
AND w.end_ts > w.start_ts AND l.ts >= w.start_ts
AND l.ts < w.end_ts
AND EXISTS (SELECT 1 FROM domain_objects d
WHERE d.upid = th.upid AND d.owner_confirmed = 1)
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, l.id;

Do not treat logs as the sole evidence. They name the stages; durations and blocking still need to be traced back to slices, sched, thread_state, Binder, and HAL threads.

Filtering by confirmed process still yields candidates: shared services can log other clients or requests, so match session/operation/route next. Logs without process/thread identity are excluded here and should be listed as unattributed evidence. Zero rows do not prove that no underrun occurred.

Reports Must Lead Back to the UI

Automated reports should output more than SQL tables. Use four fixed sections:

Section Output
Data quality Trace duration, stats anomalies, presence of key data sources
Object dictionary Relevant processes, threads, tracks, log tags, request/session IDs, confirmation status
Stage metrics Camera open/config/HAL buffer/presented frame; Audio write/mix/output cycles
Review points ts, dur, process, thread, slice/log names

An illustrative report structure (the numbers below are examples, not measurements from a supplied trace):

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: not_evaluated
target_window: 123.000s..124.200s
phase:
open_api: 42 ms
configure_streams: 98 ms
first_hal_buffer: 286 ms
first_preview_marker_candidate: 486 ms # target layer/present not yet verified
observations_not_yet_attributed:
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

Every anomaly in the report should lead back to Perfetto UI. Conclusions without timestamps cannot be reviewed by someone else.

For domain owners, a report must point to reviewable intervals rather than stop at “the script says Camera HAL is slow.” For example: provider configure took 98 ms, the request ID was 17, a Binder thread was Runnable for 41 ms at the same time, and there was no data loss. The script assembles the context; the final conclusion must still be verifiable against the UI and source code paths.

The server and case package also need summary.json. The following retains illustrative durations but leaves unverified quality, attribution, and redaction fields unknown or not_evaluated. Real reports must supply hashes, trace/device versions, object IDs, windows, and evidence locations. The two examples are not output from one measured run:

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
66
67
68
69
70
71
72
73
74
75
76
77
78
{
"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:...",
"analysis_processor_version": "v58.3",
"trigger": "app_camera_open_slow",
"target_window": {
"start_ts": 123000000000,
"end_ts": 124200000000,
"source": "track_event",
"clock_domain": "trace_processor_normalized_ns"
},
"data_quality": "not_evaluated",
"required_signals_available": null,
"missing_required_signals": null,
"stats_findings": null,
"object_dictionary_version": "camera_audio_v1",
"domain_objects": [
"camera_app_client",
"camera_service",
"camera_provider"
],
"stage_metrics": [
{
"stage_name": "configure_streams",
"status": "illustrative",
"start_ts": 123612000000,
"end_ts": 123710000000,
"dur_ms": 98,
"source_role": "camera_provider",
"evidence_slice": "configure_streams",
"review_ts": 123612000000,
"start_slice_id": null,
"end_slice_id": null
}
],
"blocking_evidence": [
{
"type": "runnable",
"process": "cameraserver",
"thread": "Binder:1234",
"dur_ms": 41,
"source_sql": "sql/domain_thread_state.sql",
"status": "illustrative_observation_not_causal",
"upid": null,
"utid": null,
"interval_start_ts": null,
"interval_end_ts": null
}
],
"app_marker_status": "not_evaluated",
"platform_event_status": "not_evaluated",
"fallback_used": "none",
"evidence_grade": "ungraded",
"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": "not_evaluated",
"raw_trace_available": null,
"log_excerpt_policy": "window_only",
"retention_until": "2026-05-07T15:30:12+08:00",
"next_action": "owner_review",
"example_only": true,
"device_service_version": null,
"trace_sha256": null
}

Add Stable Events in the App and Platform

Name drift is a major problem for Camera/Audio automation. Slice names can vary across Android versions, vendor HALs, camera modules, and audio routes. Add stable events in code under your control:

Event Owner Required args Purpose Privacy boundary
Camera#OpenStart App client_instance_id, camera_id, session_id, operation_id, event_id Start of open_api No user content
Camera#OpenEnd App client_instance_id, camera_id, session_id, operation_id, event_id, result End of open_api Allowlisted error codes
Camera#Opened App client_instance_id, camera_id, session_id, operation_id, result Successful onOpened callback Allowlisted result
Camera#ConfigureStreamsEnd platform client_instance_id, camera_id, session_id, operation_id, stream_id, result Configure endpoint Allowlisted result
Camera#FirstHalBuffer platform client_instance_id, camera_id, session_id, operation_id, stream_id First valid target-stream buffer No image content
Camera#ConfigureStreamsStart Platform client_instance_id, camera_id, session_id, operation_id, stream_id Start of configure stage Resolution/format may be retained
Camera#FirstPreviewFrame Platform/App client_instance_id, camera_id, session_id, operation_id, stream_id, surface_id Candidate first visible frame No image content
AudioFlinger#MixerCycle platform cycle_seq, route_generation One loop on a confirmed track No media content
Audio#TrackStart App/Platform track_id, route, sample_rate, buffer_frames Start of audio cycles Route represented as an enum
Audio#WriteBuffer App track_id, frames, buffer_level App write cadence No media content
Audio#Underrun Platform track_id, route, sample_rate, buffer_frames, underrun_count Underrun event Allowlisted routes/tags
Audio#RouteChange Platform old_route, new_route, reason Route change No unique device identifiers

For Camera markers, this example defines result as the string success or an explicit error enum. Success on OpenEnd means only a normal call return. Audio-domain track_id identifies an application audio track, not a Trace Processor track; map them explicitly.

These events can come from android.os.Trace, NDK ATrace, Perfetto SDK Track Event, or platform ATRACE. Keep event names stable; do not concatenate dynamic IDs into them.

With ATrace, focus on stable slice names and async cookies. Prefer Track Event arguments, flow IDs, logs/side tables, or metadata in the same case for ID information. Counters should contain interpretable numeric values such as queue depth, buffer level, and underrun count. For richer arguments and cross-thread correlation, prefer Perfetto SDK Track Event as described in Part 13.

A platform-specific custom data source is a lower priority. Build one only for high-frequency, strongly structured data or information that ordinary slices/counters cannot represent. Its manifest must specify the data source name, producer process, SELinux domain, TraceConfig, parser, and SQL compatibility policy.

Expand Domain Methods into an Issue Checklist

Camera and Audio are only examples. To use Perfetto reliably as a team, translate common symptoms into capture presets, collectors, build types, analysis paths, and output evidence.

Issue Base data sources Additional domain signals Default output
UI jank sched, freq, idle, gfx, view, wm, input, process_stats, FrameTimeline binder_driver, app ATrace/Track Event Problematic frames, App/HWUI/SurfaceFlinger stages, thread states
Slow tap response input, sched, freq, view, wm, binder_driver, FrameTimeline App visual-state counters, high-speed camera comparison Input to first visible change
App startup sched, freq, am, wm, view, binder_driver, FrameTimeline log, CPU profiling, app ATrace/Track Event Startup stages, first frame, longest waits
ANR sched, freq, binder_driver, am, wm lock, IO, CPU profiling; authorized android.log where supported Main-thread wait evidence
Slow Binder sched, binder_driver, binder_lock, process_stats system_server logs, interface events Top N slow transactions, server state
Native memory process_stats heapprofd, log, application triggers Allocation stacks and growth trends
Camera sched, freq, camera, hal, binder_driver, SurfaceFlinger layer/present; FrameTimeline where supported log, CPU profiling, platform events Open/config/HAL buffer/first visible frame
Audio sched, freq, audio, hal, binder_driver Authorized logs, CPU profiling, platform events Cycle jitter around underruns
Power/thermal management sched, freq, idle, android.power, thermal GPU counters, devfreq, power rails; authorized android.log where supported Resource trends and anomalous windows

Every preset needs an owner, version, purpose, default window, collector, build boundaries, data sources, overhead level, privacy level, and quality gate. Without these fields, presets quickly become a pile of configurations that nobody dares to delete:

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
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
- linux.process_stats
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: unmeasured
data_rate_budget_mb_s: 1.0 # Proposed budget; not a measured rate
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.pb
config_format: binary_proto
config_hash: sha256:...
review_status: draft
required_signals:
- FrameTimeline
- sched
- process_stats
derived_tables_required:
- thread_state
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

Ordinary apps cannot submit arbitrary system TraceConfigs or directly own the consumer socket. They can emit markers/triggers and metadata, and request policy-controlled profiling through ProfilingManager on API 35+. Full platform sessions are normally owned by OEM diagnostics, Traceur, shell, or laboratory tooling. Validate request acceptance and usable results separately.

On user builds, Traceur, shell, and authorized platform/OEM diagnostics are common collection entry points; the exact consumers and permissions depend on the device. Arbitrary ordinary apps or unauthorized diagnostics apps cannot directly control the system-level Perfetto consumer socket. The proposed team policy distributes reviewed binary TraceConfigs or preset IDs. Binary format helps machine compatibility; it is not authorization or redaction. Validate allowed data sources and fields as well.

Before production upload, require appropriate authorization, access control, retention limits, and verified redaction. trace_filter acts at service ReadBuffers for both IPC readback and write_into_file; it does not prove sensitive fields never entered producer or central buffers. The early Android 12 implementation was broken, Android 13 reintroduced it, and Android 14 added a string-filtering bytecode version. Generate and verify rules for the target service; unmatched strings can remain unchanged. Record rule versions and output hashes. A tag allowlist does not redact message content.

Reports also need evidence grades for their conclusions:

Grade Wording Required fields
Confirmed X blocked Y within window A owner_confirmed=true, complete ordered stage events, required_signals_available=true, data_quality=clean, direct dependency/wait evidence and alternative-cause checks, ui_review_points>=1, source_sql
Tentative The evidence points more toward X Primary evidence holds; missing_evidence lists missing source code, logs, or domain state
Not supported The evidence does not currently support X signal_present=true and metrics do not support that direction; this cannot be inferred from missing data
Downgraded This trace cannot determine X degrade_reason specifies data loss, a missing data source, or insufficient samples

These grades protect report quality. Many engineering disagreements stem not from the trace itself, but from assigning the wrong conclusion grade: evidence that supports only a tentative direction is presented as confirmed.

Complete stages and clean data establish observations, not automatically that X blocked Y. For example, 41 ms of Runnable time across a window and a 98 ms configure span do not prove that one Binder call was delayed by 41 ms. Recompute within the target dependency interval and establish the wait chain. Attribution to a policy or hardware change also needs matched controls or A/B evidence.

What the Platform and OEM Need to Build

Apps can add application semantics, but many issues occur outside their view: blocked system_server Binder calls, AudioFlinger underruns, Camera HAL blocking, Power HAL policy changes, scheduler CPU migrations, insufficient GPU execution tracks, or evidence scattered through a bugreport. The platform needs to standardize four things: presets, triggers, evidence-package format, and the automated analysis entry point.

Engineers should not have to write an ad hoc TraceConfig every time. Manage TraceConfig versions like an interface, with review, testing, and releases in a repository:

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

A Perfetto infrastructure should have at least three layers:

Layer Use case Collector Capture characteristics Typical data sources
field Production/staged-rollout issues on user builds Platform/OEM diagnostics components or Traceur; apps emit only markers/triggers Low overhead, ring buffer, stop trigger for a short window sched, process stats, a few app ATrace events, necessary counters
lab Laboratory reproduction and version comparisons shell, test frameworks, platform scripts Controlled devices, repeatable scripts, stable metrics FrameTimeline, freq, idle, Binder, domain log excerpts
deep Focused investigation userdebug/root, platform engineering tools Short duration, high overhead, manual operation heapprofd, linux.perf, GPU counters, additional HAL events

The proposed field baseline excludes deep presets to avoid enabling unmeasured laboratory configurations for long periods. This is a team default, not a Perfetto prohibition on production profiling. heapprofd, linux.perf, or system-managed profiling can have controlled field uses, with separate review of target scope, sampling, duration, perturbation, and privacy. They do not inherit a low-overhead label from this example.

Document the boundaries of deep presets separately:

  • linux.perf is a sampling profiler, not a complete event stream. By default, restrict it to short windows, low sampling rates, and target processes/threads, and document unwind, kernel-frame, root/userdebug/kptr restrictions.
  • Before using vendor tracepoints, check /sys/kernel/tracing/events/*/*/format, field stability, and ftrace_setup_errors. Unknown events can serve only as raw events or candidate evidence.
  • This example field policy excludes linux.perf, heapprofd, GPU counters, and high-frequency syscall/pagefault events. Necessary counters must specify sampling period, units, supported devices, and upload privacy level.
  • Each preset needs measured write rates and a data-loss baseline. A cost_level is no substitute for a real budget.

A ring buffer is useful for retaining a short window around a trigger; it cannot preserve all context indefinitely. Long traces need the background capture, segmented storage, and explicit exit conditions from Part 15.

The trigger registry also needs consolidation. App agents, Framework watchdog, AudioFlinger, CameraService, Thermal/Power, and laboratory tools may send only registered trigger names. The platform then applies rate limits by device, user, version, scenario, network, and storage state. Once a name enters the platform, do not change it casually: doing so breaks server indexes, SQL templates, historical trends, and alert rules.

Registration here is a team protocol: a producer can send an unconfigured name, but no matching session action follows. Acceptance also depends on trigger configuration, timeout, sampling, rate limits, and optional producer_name_regex. A send returning does not prove a trace was captured. Names are not secrets or credentials, and producer-name filtering does not replace platform identity checks.

Integrate Bugreport with Perfetto as well. bugreport_score > 0 marks a running trace session as a Bugreport candidate; sessions with bugreport_score <= 0 are excluded. When Android dumpstate calls perfetto --save-for-bugreport, it selects the highest-scoring candidate trace and saves it to the Bugreport path. Android 12/13 saves the candidate trace and stops the original session early; Android 14 or later creates a read-only snapshot and lets the original session continue. bugreport_filename is an Android 15 / Perfetto v42+ field for output names under --save-all-for-bugreport; save-all differs from selecting only the highest score. A positive score makes the session eligible for cloning by other UIDs; it does not remove same-UID restrictions on detached-session reattachment. Record that access boundary. Saving a trace does not itself package or upload a bugreport.

Build a system instrumentation dictionary before adding code:

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

This platform dictionary and the Camera#* names in the earlier domain schema are two distinct layers. CameraService#Open and CameraHAL#FirstResult are instrumentation implementation names inside individual services; Camera#OpenStart and Camera#FirstHalBuffer are interface names used for analysis and reporting. Match the layers through the owner field in the schema’s stable_events and a mapping table. SQL and reports use only schema names; renaming an implementation event requires updating only the mapping, not the analysis layer.

These implementation names are hypothetical too. The owner field does not execute a mapping: define begin/end semantics, units, argument propagation, success/failure, and original slice/track IDs. FirstResult may be metadata only and cannot simply be renamed FirstHalBuffer. The earlier SQL reads schema markers directly; implementation-only events require a validated normalization step first.

Names must be stable and their meaning suitable for SQL aggregation. Put dynamic information in arguments, counters, or metadata, rather than concatenating it into event names. Plaintext URLs, contacts, message content, search terms, and geographic locations must not enter trace events.

The server needs at least five functions: file acceptance checks, evidence-reliability checks, scenario summaries, aggregation indexes, and a manual review entry point. Before anyone opens the UI, the report should already identify the preset, trigger, trigger window, quality-gate status, relevant processes and threads, and the owner indicated by the machine-generated summary.

Represent processing status as an explicit state machine:

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

This state machine constrains the provenance of evidence: every conclusion can be traced back to the same preset, SQL, and evidence grade. After a version upgrade, renamed fields, missing events, or unavailable data sources can trigger a downgrade directly at quality_checked, preventing reports from continuing to emit apparently certain conclusions.

Common Pitfalls

  • Matching only LIKE '%camera%' misses providers, vendor HALs, codecs, and media processes.
  • Enabling only atrace_categories misses app-side android.os.Trace; use atrace_apps or Track Event.
  • Ordinary apps cannot submit arbitrary system configurations; besides markers/triggers, API 35+ offers system-managed ProfilingManager requests.
  • Prefer FrameTimeline for ordinary View UI, startup, and tap-response analysis. If the target uses SurfaceView or the device has no corresponding frame data, inspect target layer/present evidence and state that the frame metric is unavailable.
  • Design for the documented userdebug support of android.log. On rooted user devices, verify permissions and actual data; prepare alternative sources for ordinary user builds.
  • Slow Binder is a symptom. The server may be waiting for HAL, a lock, IO, or downstream hardware.
  • Analyze Audio cycle jitter rather than only the longest slices.
  • Separate Camera open, configure, preview, and capture rather than combining them into one duration.
  • For Running, use actual execution intervals (sched or Running in thread_state); for Runnable, use R/R+ in thread_state. Calculate them separately.
  • An agent or script organizes evidence; it cannot replace UI review and domain-owner judgment.
  • Distinguish API return, HAL buffer return, and visible presentation when measuring the first camera frame.
  • Establish Audio period thresholds against a baseline for the device, route, and buffer configuration.
  • Increasingly heavy production presets create power, privacy, and stability problems.
  • Without a system instrumentation dictionary, event-name drift invalidates SQL and historical trends.
  • Keeping Bugreports and traces separate forces engineers to locate the same time window manually across multiple attachments.

Summary

For domain problems such as Camera and Audio, standardize a preset, build an object dictionary, write SQL around stages and cycles, produce reports with timestamps, and return to the UI to review anomalies.

The same approach applies to WebView, Flutter, games, and vendor-defined system services. Only the object dictionary and metric templates change. The foundation remains trace quality, process/thread ownership, stage durations, scheduling states, and log semantics. Add platform support for presets, triggers, Bugreport, system instrumentation, server summaries, and permission boundaries, and Perfetto can grow from an individual tool into a team capability.

SmartPerfetto combines scenario queries with evidence review. Check its repository for currently supported scenarios and limits; the six-week update review describes the design at the time of writing.

References

  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

Source revision checked: v58.3 (2026-10-14).

About Me and the Blog

Follow Android Performance.

CATALOG
  1. 1. Perfetto Series Catalog
  2. 2. Domain Analysis Starts with an Object Dictionary
  3. 3. Start Configuration with a Domain Preset
  4. 4. Analyze Camera by Stage
  5. 5. Analyze Audio by Cycle
  6. 6. Calculate Running and Runnable Separately
  7. 7. Use Logs to Add Stage Semantics
  8. 8. Reports Must Lead Back to the UI
  9. 9. Add Stable Events in the App and Platform
  10. 10. Expand Domain Methods into an Issue Checklist
  11. 11. What the Platform and OEM Need to Build
  12. 12. Common Pitfalls
  13. 13. Summary
  14. 14. References
  15. 15. About Me and the Blog