EN

Android Perfetto Series 18: Response Latency in Practice, from Input Events t...

Word count: 6.5kReading time: 40 min
2026/05/04
loading

This article addresses a topic easily overshadowed by startup speed and smoothness: first-frame interaction response. It asks how long the screen takes to provide the first frame of feedback after a user action. Complete page loading and stability later in an animation require other metrics.

After tapping a button, how long until its pressed state appears? After tapping a WeChat conversation, how long until the first frame of the conversation page starts moving? After swiping a list, how long until its content follows the finger? These questions relate to slow startup and dropped frames, but need their own measurement definitions.

We use Perfetto to break down this interval: from InputReader read_time, through the App receiving the event, to the first frame’s present. The primary metric, input_to_present_ms, measures only input read to the associated frame’s present. It does not cover touch IC latency, delays before driver reporting, display scanning, or panel response. By the end, you should be able to define a start, endpoint, capture configuration, SQL metrics, and supplementary App markers for a tap or scroll scenario.

The article examines response latency through four independent measurements—read/dispatch, handling, ACK→first frame, and scroll responsiveness. Each comes with capture configuration, the first tracks to inspect in the UI, and SQL. It then covers five common attribution paths and the respective boundaries of high-speed cameras and Perfetto.

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 Covers Only the First Frame After an Interaction

The earlier Systrace response-speed series covered response latency more broadly: startup, navigation, screen on/off, unlocking, a busy system, Binder, and other scenarios. The smoothness series covered the stability of consecutive frames. Parts 07 and 08 of the Perfetto series also broke down the frame path through MainThread, RenderThread, Vsync, and SurfaceFlinger.

This article adds just one segment:

1
2
3
4
5
6
7
8
User input
-> InputReader reads the event
-> InputDispatcher dispatches it to the target window
-> The App main thread receives input
-> The App handles input and requests the next frame
-> RenderThread submits a buffer
-> SurfaceFlinger composites and presents
-> The user sees the first frame of feedback

We consistently call the primary metric input_to_present_ms: from android_input_events.read_time to the associated frame’s present, represented by end_to_end_latency_dur in SQL. It answers “how long after the tap does the first frame of feedback appear?”, rather than “how long until the whole page loads?” or “does the animation drop frames?” The standard library’s present is a candidate association: is_speculative_frame=false means only that an input-handling slice overlaps a doFrame on the same thread. It proves neither business causality nor a visual change on the target layer. Queries below therefore output estimated_input_to_present_ms. Only after separately checking the target layer, frame token, actual present, and business state can it be promoted to confirmed input_to_present_ms.

If you start from event_time or a business marker, report a separate field; do not mix it with input_to_present_ms for comparisons.

Timeline from input to candidate present

Illustration: ACK and frame display are different stages. A candidate present still needs confirmation against the layer and business state; it cannot directly stand for the first user-visible frame.

Break Response Latency into Four Metrics

Discussions of slow response often mix different concepts. Before analyzing an interaction, separate it into four metrics.

Metric Start End Question it answers
Dispatch latency InputDispatcher sends the event App receives the event Are system dispatch and InputChannel slow?
Handling latency App receives the event App sends ACK Is App input handling slow?
input_to_present_ms Input read (read_time) Associated frame present How soon does the user see the first frame of feedback?
Completion latency Business trigger Page or animation becomes stable How long does the entire operation take?

Users are especially sensitive to input_to_present_ms. Fast initial feedback followed by content arriving in stages feels responsive. Slow initial feedback can feel like “nothing happened when I tapped,” even if the subsequent animation is stable.

Taps and scrolls also have different endpoints:

  • Button tap: the endpoint may be the pressed-state present, the first popup frame’s present, or the first page-transition frame’s present.
  • Conversation tap: it may be the first frame of the conversation page’s entry animation, or completion of drawing the first message’s content.
  • Finger scroll: it should be the present at which content first visibly moves.

Perfetto helps locate internal system timing, but whether the first frame actually contains a visual change often needs calibration with App markers or external visual tools.

Also distinguish three “input times”:

  • event_time: when the input event occurred. Earlier than read_time, but still not the touch firmware, electrical signal, or an externally visible starting point.
  • read_time: when InputReader reads the event. The end-to-end field in android_input_events measures from here to present.
  • dispatch_ts / receive_ts: when InputDispatcher sends the event and the App receives it, used to separate system dispatch from App handling.

Do not label all of these “from the tap” in reports. If measurement starts at read_time, say input read; if it starts at a business click handler, say App marker. Numbers with different starting points are not directly comparable.

Before writing SQL, establish the scenario’s object dictionary. At minimum, include the target package, InputChannel, target window, target layer, business markers, and expected endpoint:

1
2
3
4
5
6
7
8
9
10
11
scenario_id: conversation_click
process_name: com.example.app
input_channel: com.example.app/com.example.ChatActivity
target_window: ChatActivity
target_layer: TX - com.example.app/com.example.ChatActivity#0
business_markers:
click: Conversation#Click
transition_start: Conversation#TransitionStart
first_content_drawn: Conversation#FirstContentDrawn
expected_endpoint: transition_first_presented_frame
owner_confirmed: true

Automation can first generate candidate objects from package and window names, but reports should use only manually confirmed objects. Otherwise, when a trace contains multiple windows, popups, SurfaceViews, or overlays, SQL can easily associate an input event with the wrong layer.

Handle multiple displays and foldables explicitly in the dictionary. Input events are dispatched by display: external monitors, multi-display automotive systems, and a foldable’s inner and outer displays can contain windows and channels with identical names at the same time. android_motion_events / android_key_events have a display_id column, allowing event IDs to identify the destination display. The round-trip table, android_input_events, has no display column. When raw inputevent data is available, normalize the event ID representation before cross-checking those two tables. ATrace string IDs may be hexadecimal, whereas raw event_id values are integers; do not directly join the columns as equal strings. Folding and unfolding can also trigger display switches and window recreation, replacing layer and channel names. Do not combine different postures into one baseline. Record posture (folded/unfolded) as a separate dimension in the scenario dictionary, and compare runs only within the same posture.

Version interaction scenarios as schemas too. A minimal contract for a tap scenario can look like this:

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
schema_version: input_response_v1
scenario_id: conversation_click
interaction_type: click
start_event_policy:
preferred: ACTION_UP
fallback: Conversation#Click
endpoint_policy:
visual_endpoint: transition_first_presented_frame
business_state_marker: Conversation#FirstContentDrawn
process_name: com.example.app
input_channel: com.example.app/com.example.ChatActivity
target_layer: TX - com.example.app/com.example.ChatActivity#0
required_markers:
- Conversation#Click
- Conversation#TransitionStart
required_counters: []
association_method:
primary: android_input_events.frame_id
fallback: target_layer_present_after_marker
fallback_policy:
missing_inputevent: app_marker_plus_frametimeline
missing_frametimeline: app_marker_plus_external_video
evidence_grade_rules:
confirmed: verified_target_layer_present_and_business_state
likely: frame_associated_but_no_external_video
degraded: missing_inputevent_or_frametimeline
privacy_notes:
- do_not_log_conversation_id
- channel_and_layer_name_allowed

For scrolling, refine the endpoint from “frame present” to “the present where content first visibly moves”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
schema_version: input_response_v1
scenario_id: feed_first_scroll
interaction_type: scroll
start_event_policy:
preferred: first_effective_ACTION_MOVE
touch_slop_px: device_scaled_touch_slop # Read from ViewConfiguration; do not hardcode 8px across devices
endpoint_policy:
visual_endpoint: first_scroll_offset_change_present
required_counters:
- Feed#ScrollOffsetY
sample_policy:
scroll_offset_counter: each_nonzero_consumed_scroll_delta
report_fields:
- first_effective_move_ts
- first_offset_change_ts
- offset_delta_px
- move_to_offset_change_ms
- offset_change_to_present_ms

Trace Capture Configuration

Capture has two tiers:

Preset Build/scenario Data sources Reporting scope
lab_input_debug userdebug/eng, short laboratory window input/view/gfx ATrace, FrameTimeline, sched, App markers, plus raw android.input.inputevent Round trip + candidate input-to-present; visual endpoint confirmed separately
field_input_response_low_overhead user/field, platform-controlled capture App markers, FrameTimeline/sched, input/view ATrace where available by version, trigger metadata Round trip when ATrace is complete; otherwise fall back to App-marker measurement

Local or laboratory analysis can enable the more complete input data source. This short-trace configuration reproduces tap/scroll response, with particular attention to android.input.inputevent and atrace_categories: "input":

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
buffers {
size_kb: 98304
fill_policy: RING_BUFFER
}

duration_ms: 10000
flush_period_ms: 5000

data_sources {
config {
name: "linux.ftrace"
ftrace_config {
ftrace_events: "sched/sched_switch"
ftrace_events: "sched/sched_waking"
ftrace_events: "sched/sched_wakeup_new"
ftrace_events: "power/cpu_frequency"
ftrace_events: "power/cpu_idle"
atrace_categories: "input"
atrace_categories: "view"
atrace_categories: "gfx"
atrace_categories: "wm"
atrace_categories: "am"
atrace_categories: "binder_driver"
atrace_apps: "com.example.app"
}
}
}

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: 1000
}
}
}

data_sources {
config {
name: "linux.sys_stats"
sys_stats_config {
cpufreq_period_ms: 1000
}
}
}

data_sources {
config {
name: "linux.system_info"
}
}

data_sources {
config {
name: "android.input.inputevent"
android_input_event_config {
mode: TRACE_MODE_TRACE_ALL
trace_dispatcher_input_events: true
trace_dispatcher_window_dispatch: true
}
}
}

android.input.inputevent targets debuggable system builds—controlled environments such as userdebug/eng. TRACE_MODE_TRACE_ALL records input events handled by the system and is suitable only for local devices and testing.

Do not depend on android.input.inputevent on field/user builds. To reduce privacy exposure even in a debuggable, controlled environment, use TRACE_MODE_USE_RULES, strict matching rules, review of secure/IME/spy windows, and access controls. Rules mode does not turn it into an ordinary production-capture capability. Input events may be delivered to the foreground window, SystemUI, IME, or spy windows simultaneously; poorly designed match_any_packages / match_all_packages rules can broaden collection scope.

Field presets must include secure windows, IME, spy windows, multi-target dispatch, redaction, retention periods, and access controls in their review criteria.

An ordinary App cannot start system ftrace, FrameTimeline, or android.input.inputevent sessions itself. <profileable android:shell="true" /> is useful for allowing local profiling of release builds with CPU/memory profilers. App tracing is available by default for all Apps on Android 12+; on Android 11 and earlier, android.os.Trace still requires attention to profileable/debuggable boundaries.

It does not, however, grant an ordinary App on a user build permission to start system inputevent, ftrace, or SurfaceFlinger sessions itself.

android.input includes two different sources. android_key_events/android_motion_events come from raw android.input.inputevent. In contrast, android_input_events reconstructs round trips and frame associations from ATrace slices such as sendMessage/receiveMessage, deliverInputEvent, and UnwantedInteractionBlocker::notifyMotion. Do not conflate them. Round trips may exist without raw inputevent enabled; enabling only raw inputevent without ATrace does not automatically produce round trips. Actual availability depends on the system version, the presence of these instrumentation points, and whether categories such as input/view/gfx were captured.

Define fallbacks in advance:

Missing signal What can still be reported What cannot be reported
Raw android.input.inputevent missing Complete ATrace can still reconstruct round trips and candidate frame associations Raw motion/key details and display information
Input ATrace send/receive or ACK slices missing App markers, FrameTimeline/scheduling context Complete round trips; raw inputevent tables cannot directly replace them
FrameTimeline missing Input round trips, App markers, thread and SurfaceFlinger clues Associated frame, input_to_present_ms
SurfaceView/games/video App markers, layers, external video, vendor tools First visible change established solely through FrameTimeline
Data loss / clock sync anomalies Local observations from surviving evidence Strong conclusions and precise cross-source durations

State FrameTimeline’s limits too: it requires Android 12 or later. The official documentation currently says SurfaceView is unsupported, so video, games, and maps cannot rely on FrameTimeline alone to identify the first visible frame change. Calibrate with layers, App markers, external video, or vendor graphics tools.

input_to_present_ms shows only that an input event was associated with a frame’s present. Whether this was the target UI’s first visible change still needs confirmation through the target layer, business markers, scroll counters, screenshots/video, or domain tools. A populated frame_id is not sufficient proof of visual change either.

For deeper local investigation, Winscope’s SurfaceFlinger layers/transactions sources can help inspect window and layer state. These sources can be heavy, particularly MODE_ACTIVE and buffer/HWC trace flags, which substantially increase data volume. Reserve them for short laboratory traces.

Field investigations still rely on the controlled presets, triggers, business markers, and privacy rules from Parts 13, 15, and 17.

Run data-quality checks immediately after capture. Input analysis depends heavily on timing order and FrameTimeline associations; ftrace or central-buffer data loss requires downgrading the report:

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
SELECT name, idx, severity, source, value
FROM stats
WHERE value != 0
AND (
severity IN ('error', 'data_loss')
OR name GLOB 'ftrace_cpu_*overrun*'
OR name GLOB 'ftrace_cpu_*dropped*'
OR name GLOB 'traced_buf_*packet_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',
'android_input_event_parse_errors',
'frame_timeline_event_parser_errors',
'frame_timeline_unpaired_end_event',
'graphics_frame_event_parser_errors',
'clock_sync_failure',
'invalid_clock_snapshots'
)
)
ORDER BY name, idx;

Group these anomalies under inputevent, FrameTimeline, ftrace/sched, central_buffer, and clock_sync. An anomaly in any critical group must lower the evidence grade for input_to_present_ms.

linux.sys_stats adds CPU frequency samples, but frequency assessment still needs idle state, cluster/cpuset, uclamp, thermal conditions, the frequency state immediately before the window, and whether the target thread actually ran on the relevant CPU/cluster. This preset does not enable IRQ, softirq, or workqueue events by default. If you suspect hardirq, kworker, or softirq interference, use a separate short-window specialist preset.

Keep IRQ/softirq/workqueue events in that short-window preset only: irq/irq_handler_entry, irq/irq_handler_exit, irq/softirq_entry, irq/softirq_exit, irq/softirq_raise, workqueue/workqueue_execute_start, and workqueue/workqueue_execute_end. Check ftrace overrun/dropped after capture; do not put these events in the default field preset.

Which Tracks to Inspect First in Perfetto UI

For a tap or scroll response, inspect in this order:

  1. InputReader / InputDispatcher: verify that the event was read and dispatched to the target window.
  2. App main thread: verify arrival and whether the thread became Running promptly.
  3. Choreographer#doFrame: determine whether the state change advances into the next frame through Input, Insets Animation, Animation, Traversal, or Commit. Input receive/ACK need not occur inside doFrame; batched input may also be consumed through CALLBACK_INPUT.
  4. RenderThread: after the UI thread submits work, check for stalls in stages such as syncFrameState, DrawFrame, dequeueBuffer, and queueBuffer.
  5. FrameTimeline: check the associated frame’s expected/actual timelines, token, layer_name, and association method.
  6. SurfaceFlinger: check timely buffer latching and whether HWC/GPU composition, acquire/release/present fences, or other layers affect final presentation.
  7. CPU scheduling/frequency: check the main thread, RenderThread, and InputDispatcher for Runnable waits, insufficient frequency, or abnormal CPU migration.

Do not start by guessing from the longest slice. Connect the input event to the first frame of feedback, then determine where the time went.

Tap Scenarios: From DOWN/UP to the First Frame

Choose the starting point first. Products define “tap response” differently.

Scenario Recommended start Recommended end
Button pressed state ACTION_DOWN or input read First pressed-state frame present
Tap confirmation ACTION_UP or click handler Target UI’s first frame present
Page navigation Click handler / business marker First transition frame present
Content available Click handler / business marker First-screen content marker

For “tap a conversation to enter the chat page,” inspect Perfetto in these steps:

  1. Find the corresponding ACTION_UP in android_input_events or on the InputDispatcher track.
  2. Measure dispatch to App receive to determine whether system dispatch is slow.
  3. Check whether the App main thread is scheduled promptly after receiving the event.
  4. Check the click handler for Binder, IO, lock waits, synchronous layout, and image decoding.
  5. Look for waits introduced by the startActivity Binder call, Activity launch, window creation, focus changes, starting window, transition, and target-layer visibility.
  6. Check whether the next Choreographer#doFrame advances state through Animation, Traversal, or Commit.
  7. Inspect RenderThread, FrameTimeline, and SurfaceFlinger to confirm the associated frame actually presents.
  8. Return to App markers and the object dictionary to confirm that this is the conversation page’s first user-visible frame.

The easiest mistake here is that Perfetto can show an App drawing a frame that contains no visual change. The end of the App actual timeline means the App completed and submitted the frame; it does not mean the user has seen it.

To confirm first-frame feedback, return to the frame associated with android_input_events.end_to_end_latency_dur, the SurfaceFlinger actual/display frame, layer_name, token/flow, and external evidence. Without business markers, the analyst must judge from UI screenshots, layer names, or external video.

Specify the event contract before implementing it:

Event Type Thread Meaning Report fields Privacy
Conversation#Click slice UI thread Click-handler execution window business_marker_ts, click_handler_dur No conversation ID
Conversation#TransitionStart short slice milestone UI thread Business-defined transition start transition_marker_ts No page parameters
Conversation#FirstContentDrawn short slice milestone UI thread / render callback Candidate business completion of first-screen content content_marker_ts, content_marker_seen No content text
Feed#ScrollOffsetY counter UI thread Visible list displacement first_offset_change_ts, offset_delta_px Numeric values only

Conversation#FirstContentDrawn is a business_state_marker, not a visual_endpoint. The final report must include present_ts, associated_frame_id, layer_name, and association_method. Without a present association, report only content_marker_seen=true; do not output input_to_present_ms.

The following code adds three business markers to the conversation-tap scenario. Pay attention to naming: event names are fixed, and dynamic IDs do not enter trace sections.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fun onConversationClick(conversationId: String) {
Trace.beginSection("Conversation#Click")
try {
openConversation(conversationId)
} finally {
Trace.endSection()
}
}

fun onConversationTransitionStart() {
Trace.beginSection("Conversation#TransitionStart")
Trace.endSection()
}

fun onConversationFirstContentDrawn() {
Trace.beginSection("Conversation#FirstContentDrawn")
Trace.endSection()
}

These markers represent click handling, transition start, and first-screen content completion. The latter two are milestone-style short slices: reports use only their ts, not dur as business duration. Use Perfetto SDK Track Event when you need strict instants, arguments, or flows. Perfetto supplies system timing; markers supply business meaning. The endpoint remains the associated frame’s present.

android.os.Trace / ATrace work well for stable slice names and counters. Do not concatenate dynamic conversation IDs, page IDs, or experiment groups into section names. For arguments, flows, or cross-thread tracks, use Perfetto SDK Track Event from Part 13, or put business metadata in the same case package.

A marker represents business state only; present time comes from the associated frame. Report business_marker_ts, associated_frame_id, present_ts, and association_method separately.

Scroll Scenarios: From the First Effective MOVE to Content Displacement

Scroll response is easier to misinterpret than tap response. When a finger first moves, the system receives many MOVE events, but the App may not yet have crossed touch slop. Event coalescing, sampling, and Vsync cadence may also mean that only a later frame produces actual content displacement.

The recommended definition is:

1
First ACTION_MOVE considered effective by business logic -> Present where content first visibly moves

Analysis steps:

  1. Find the MOVE sequence after ACTION_DOWN.
  2. Exclude small MOVE events within touch slop and select the MOVE that the business logic treats as the start of scrolling.
  3. Check whether the App main thread receives this MOVE and ACKs promptly.
  4. Check whether the Input stage of Choreographer#doFrame updates the scroll offset.
  5. Check whether the next frame is submitted to RenderThread.
  6. Check the FrameTimeline present time.
  7. Confirm content displacement with an App counter or external video.

Perfetto cannot always tell “how far the content moved.” Apps you control can add a lightweight counter. The following records each nonzero consumed displacement, preserving the first change’s timestamp. If updates must be coalesced per frame, add frame-level scheduling separately and record the resulting sampling delay; do not assume onScrolled is called only once per frame. Report first_effective_move_ts, first_offset_change_ts, offset_delta_px, touch_slop_px, and sample_policy together.

1
2
3
4
5
6
7
8
9
10
11
12
13
var gestureScrollY = 0L // Reset at the start of a new gesture and record a baseline counter.
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(rv: RecyclerView, dx: Int, dy: Int) {
if (dy == 0) return
gestureScrollY += dy.toLong()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
Trace.setCounter("Feed#ScrollOffsetY", gestureScrollY)
} else {
// AndroidX accepts int; split long scrolls into segments or use an SDK counter.
androidx.tracing.Trace.setCounter("Feed#ScrollOffsetY", gestureScrollY.toInt())
}
}
})

android.os.Trace.setCounter(String, long) is available from API 29. Projects with a lower minSdk need AndroidX tracing or their own version guard. For a large scroll-offset range, use an SDK Track Event counter or bucketed values. Do not flood the trace with counters for every layout detail. Record only user-visible state such as scroll offset, player position, or the current scene ID.

computeVerticalScrollOffset() is a scrollbar estimate whose units depend on the LayoutManager; it should not be treated directly as exact displacement in pixels. Here, accumulating dy gives the content displacement consumed by this gesture. It still does not prove that the screen has moved. Item animation, nested scrolling, and data refresh need separate calibration. First calculate move_to_offset_change_ms, then verify the frame that actually carries that state and its present. Do not arbitrarily use “the next frame” as the visual endpoint.

From effective MOVE to displacement feedback

Illustration: first measure effective MOVE to scroll-state change, then verify the frame carrying that state and its present. A counter update does not prove that the screen has moved.

Quantifying Input Latency with SQL

First INCLUDE PERFETTO MODULE android.input, then check whether input ATrace can form send → receive → finish → ACK. The current implementation inner-joins these four stages. If any stage is missing, the entire event row may disappear; in particular, this cannot rule out an ANR involving a missing ACK. read_time and frame association are additional information and may be null. Round trips can still exist without FrameTimeline.

This article does not use Chrome EventLatency. Android App analysis uses android.input / android_input_events and FrameTimeline; analyze WebView/Chrome scenarios separately with Chrome modules.

Choose the target input event first, so DOWN/MOVE/UP events, IME, overlays, or multiple windows within the same process are not mixed together.

Before proceeding, inspect how actions actually appear in this trace rather than copying Android constant names. event_action is extracted from slice names in the trace. Perfetto’s own test data uses values such as HOVER_MOVE and SCROLL without an ACTION_ prefix. event_type may be MOTION or a raw value such as 0x1, depending on the system version’s InputTransport ATrace naming. It cannot tell you whether the raw inputevent data source was enabled:

1
2
3
4
5
6
7
INCLUDE PERFETTO MODULE android.input;

SELECT event_type, event_action, COUNT(*) AS n
FROM android_input_events
WHERE process_name = 'com.example.app'
GROUP BY event_type, event_action
ORDER BY n DESC;

After confirming the values, match them exactly. *UP also matches POINTER_UP, so it cannot select single-finger taps. The following selects UP according to the current slice naming and fixes the click marker to the target process. The marker and input channel must correspond to the source window receiving the tap, which is not necessarily the destination Activity after navigation:

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
INCLUDE PERFETTO MODULE android.input;

WITH marker AS (
SELECT s.ts AS marker_ts, th.upid
FROM slice s
JOIN thread_track tt ON s.track_id = tt.id
JOIN thread th USING (utid)
JOIN process p USING (upid)
WHERE s.name = 'Conversation#Click' AND p.name = 'com.example.app'
AND s.ts >= 120000000000 AND s.ts < 123000000000
ORDER BY s.ts
LIMIT 1
),
candidate_events AS (
SELECT
input_event_id,
event_seq,
event_action,
event_channel,
normalized_event_channel,
read_time,
ABS(read_time - marker_ts) AS marker_delta_ns
FROM android_input_events
CROSS JOIN marker
WHERE process_name = 'com.example.app'
AND android_input_events.upid = marker.upid
AND event_action = 'UP'
AND normalized_event_channel LIKE '%ChatActivity%'
AND read_time BETWEEN marker_ts - 500000000 AND marker_ts + 500000000
)
SELECT
input_event_id,
event_seq,
event_action,
event_channel,
normalized_event_channel,
read_time / 1e9 AS read_ts_s,
marker_delta_ns / 1e6 AS matched_marker_delta_ms,
COUNT(*) OVER () AS candidate_count,
'nearest Conversation#Click marker on confirmed channel' AS selection_reason
FROM candidate_events
ORDER BY marker_delta_ns
LIMIT 1;

Once the target event is selected, inspect the input round trip. Focus on dispatch, handling, ACK, and input-to-present:

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
INCLUDE PERFETTO MODULE android.input;

WITH target_window AS (
SELECT 120e9 AS start_ts, 123e9 AS end_ts
)
SELECT
input_event_id,
process_name,
thread_name,
event_channel,
normalized_event_channel,
event_type,
event_action,
read_time / 1e9 as read_ts_s,
dispatch_ts / 1e9 as dispatch_ts_s,
receive_ts / 1e9 as receive_ts_s,
(dispatch_ts - read_time) / 1e6 as read_to_dispatch_ms,
dispatch_latency_dur / 1e6 as dispatch_ms,
handling_latency_dur / 1e6 as handling_ms,
ack_latency_dur / 1e6 as ack_ms,
total_latency_dur / 1e6 as total_ms,
end_to_end_latency_dur / 1e6 as estimated_input_to_present_ms,
(read_time + end_to_end_latency_dur) / 1e9 as candidate_present_ts_s,
frame_id,
is_speculative_frame
FROM android_input_events
CROSS JOIN target_window
WHERE process_name = 'com.example.app'
AND normalized_event_channel LIKE '%ChatActivity%'
AND COALESCE(read_time, dispatch_ts) >= start_ts
AND COALESCE(read_time, dispatch_ts) < end_ts
ORDER BY read_time;

The fields mean:

  • dispatch_latency_dur: InputDispatcher sending the event to the App receiving it.
  • read_to_dispatch_ms: InputReader reading the event to InputDispatcher sending it. This can expose pre-dispatch queuing, policy/interception, or waits before dispatch.
  • handling_latency_dur: the framework interval from the App input channel receiving the event to finish/ACK. This is not exclusively click-handler time. To locate business handling, split it further using ViewRootImpl/InputStage slices, App markers, or click-handler sections.
  • ack_latency_dur: the App sending ACK to the system receiving ACK.
  • total_latency_dur: dispatch to ACK completion.
  • end_to_end_latency_dur: the difference between input read and the standard library’s candidate present; NULL when association fails. It needs additional checks of the target layer, frame token, frame ordering, and nonnegative duration.
  • is_speculative_frame: true means a future doFrame on the same thread was selected; false means input handling overlaps doFrame in time. It does not verify visual changes on the target layer. The current implementation then looks for present through subsequent non-dropped frames in the same process. Do not treat this as a strict causal chain from input to a specific surface token.

A NULL end_to_end_latency_dur does not necessarily mean no visual feedback occurred. The event may not have been associated with a FrameTimeline frame, or feedback may have occurred on a SurfaceView, game engine, or another layer. Return to App markers, SurfaceFlinger, external video, or domain tools in these cases; do not translate NULL into “nothing was drawn.”

When investigating slow input, first query slow round-trip events without filtering end_to_end_latency_dur:

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
INCLUDE PERFETTO MODULE android.input;

WITH target_window AS (
SELECT 120e9 AS start_ts, 123e9 AS end_ts
)
SELECT
input_event_id,
process_name,
event_action,
event_channel,
read_time / 1e9 as read_ts_s,
dispatch_ts / 1e9 as dispatch_ts_s,
receive_ts / 1e9 as receive_ts_s,
(dispatch_ts - read_time) / 1e6 as read_to_dispatch_ms,
end_to_end_latency_dur / 1e6 as estimated_input_to_present_ms,
CASE
WHEN end_to_end_latency_dur IS NULL THEN 'frame_unassociated'
WHEN is_speculative_frame THEN 'speculative_frame'
ELSE 'associated'
END AS association_status,
(read_time + end_to_end_latency_dur) / 1e9 as candidate_present_ts_s,
dispatch_latency_dur / 1e6 as dispatch_ms,
handling_latency_dur / 1e6 as handling_ms,
ack_latency_dur / 1e6 as ack_ms,
frame_id,
is_speculative_frame,
read_time
FROM android_input_events
CROSS JOIN target_window
WHERE process_name = 'com.example.app'
AND normalized_event_channel LIKE '%ChatActivity%'
AND COALESCE(read_time, dispatch_ts) >= start_ts
AND COALESCE(read_time, dispatch_ts) < end_ts
ORDER BY COALESCE(end_to_end_latency_dur, total_latency_dur) DESC
LIMIT 50;

The round-trip query selects the window with COALESCE(read_time, dispatch_ts) so events that retain dispatch/ACK are not filtered out when read_time is missing. Require end_to_end_latency_dur >= 0 additionally only when ranking candidate input-to-present durations. Association flags, the target layer, and actual present still need review.

If read_to_dispatch_ms is high, first inspect InputDispatcher state, window targets, policy/interception, focused-window waits, and IME/spy/secure windows, then system_server scheduling. Do not jump directly from high read-to-dispatch to a CPU/frequency conclusion. If handling_ms is high, return first to the App main thread and inspect ViewRootImpl/InputStage, the click handler, Binder, locks, and IO.

If handling_ms is low but input_to_present_ms is high, prioritize post-ACK App/Framework asynchronous paths, Activity/Fragment transactions, data callbacks, next-frame production, RenderThread, FrameTimeline, SurfaceFlinger, and scheduling.

Separate Running from Runnable in scheduling analysis. The prerequisites are complete sched_switch and wakeup/waking events, with no ftrace_cpu_has_data_loss, overrun/dropped, or ftrace_setup_errors. Otherwise, say only “Runnable/Running appears in the surviving evidence,” not “there was no scheduling wait.”

This example covers App main, RenderThread, InputReader/InputDispatcher in system_server, WindowManager-related threads, and SurfaceFlinger. In production engineering, drive selection through scenario_objects with role in ('input_reader','input_dispatcher','window_manager','app_main','render_thread','surfaceflinger'); thread names should be candidates only:

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
WITH target_window AS (
SELECT 120000000000 AS start_ts, 123000000000 AS end_ts
),
target_threads AS (
SELECT th.utid, p.name AS process_name, th.name AS thread_name
FROM thread th
JOIN process p USING (upid)
WHERE (
p.name = 'com.example.app'
AND (th.is_main_thread OR th.name = 'RenderThread')
) OR (
p.name = 'system_server'
AND (
th.name IN ('InputReader', 'InputDispatcher')
OR LOWER(th.name) LIKE '%window%'
)
) OR (
p.name IN ('surfaceflinger', '/system/bin/surfaceflinger')
)
),
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 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.process_name, t.thread_name,
CASE 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 target_threads t
CROSS JOIN target_window w
LEFT JOIN state_totals s USING (utid)
ORDER BY runnable_ms DESC, running_ms DESC;

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

High Runnable time means the thread wants to run but has not obtained CPU time. First inspect other runnable tasks, RT tasks, cgroup/cpuset, uclamp/capacity, thermal conditions, CPU migration, and frequency. High Running time means the thread is already consuming CPU time; then inspect function-level hotspots, Binder, locks, or layout/drawing. To understand wakeup direction, inspect wakeup information in thread_state, IRQ context, or adjacent ftrace events. A busy CPU alone is not enough for a conclusion.

Common Attribution Paths

Slow Read/Dispatch

This usually appears as high read_to_dispatch_ms or dispatch_latency_dur. Check:

  • Queuing between InputReader and InputDispatcher.
  • Whether InputDispatcher is busy.
  • Whether system_server is waiting in Runnable state.
  • Abnormal window focus, target-window, or InputChannel state.
  • Policy/interception, IME, overlays, focused-window waits, or WindowManager state transitions.
  • CPU impact from other runnable tasks, RT tasks, cgroup/cpuset/uclamp, thermal conditions, or migration policy.

These issues tend to be platform-side; the App can supply page and interaction context.

Slow Handling

This usually appears as high handling_latency_dur. Check:

  • Whether the App main thread remains Running for a long time after receiving input.
  • Heavy computation during the Input stage.
  • Synchronous Binder calls, file reads, lock waits, or waits for network callbacks in the click handler.
  • Synchronous layout, image decoding, complex Compose recomposition, or extensive RecyclerView binding.

App markers and main-thread slices can locate most of these issues.

Fast ACK, Slow First Frame

This is common: the App ACKs input quickly, but feedback still feels slow. Check:

  • Whether the App failed to request the next frame or requested it too late.
  • Whether it waits after ACK for a Binder callback, Activity/Fragment transaction, data callback, or window switch.
  • Whether Animation / Traversal is scheduled for a later Vsync.
  • Whether RenderThread CPU work stalls in syncFrameState or DrawFrame.
  • Whether dequeueBuffer / queueBuffer indicates BufferQueue backpressure.
  • Whether App GPU completion, acquire/release fences, or present fences hold up the frame.
  • Whether SurfaceFlinger/HWC/GPU composition makes the display frame later than expected.
  • Whether the main thread or RenderThread waits for CPU in Runnable state.

Analyze the input round trip and frame production separately. A fast ACK means only that the App has finished handling the input; it does not mean the first frame has appeared.

ANRs and Sub-ANR Delays

Most slow interaction responses are sub-ANR problems: the user already notices the delay, but it has not reached the system’s ANR threshold. Examine input ANRs in two main categories:

  • The event was sent to a connection, but the App does not ACK for a long time. This usually appears as abnormal handling_latency_dur or ACK-related timing.
  • A focused event keeps waiting for a focused window. Inspect WindowManager / ActivityTaskManager, window creation, focus changes, and InputDispatcher state.

“Fast ACK, slow first frame” generally does not trigger an input ANR because the input event has already been handled. It is more likely a first-frame production or window-visibility issue.

Scrolling Does Not Follow the Finger

This often appears as delayed displacement in the first frame, followed by stable subsequent frames. Check:

  • Time from the first effective MOVE to a scroll-offset counter change.
  • Time from the scroll-offset change to present.
  • Whether MOVE events were coalesced, or the App waited until the next Vsync to handle them.
  • Whether a Runnable wait on the main thread made the first frame miss the current Vsync.
  • Whether subsequent FrameTimeline frames are stable. If so, the main problem is initial latency.

Avoid looking only at jank count. When users say scrolling “does not follow my finger,” they often mean input-to-first-frame delay.

Boundaries of High-Speed Cameras and Perfetto

Perfetto records internal system timing. What the user sees also includes touch sampling, touch firmware processing, display scanning, panel response, and error from external recording. External visual measurement remains necessary for competitor comparisons, acceptance testing, or comparisons across vendors.

The recommended combination is:

1
2
3
4
5
6
7
8
High-speed camera / robotic touch actuator:
Measure user-visible latency and define external start and end points

Perfetto:
Break down internal latency to locate delays in Input, App, RenderThread, SurfaceFlinger, or CPU/GPU

App marker:
Mark business clicks, transition start, first-screen content completion, and scroll-offset changes

External measurement provides the result, Perfetto provides the cause, and App markers provide business meaning. Together they move response analysis beyond “it feels slow.”

A minimal walkthrough should look like this: locate the input_event_id for ACTION_UP; observe handling_ms = 18, read_to_dispatch_ms = 3, and dispatch_ms = 4; then see that the candidate frame arrives 50ms after the system receives ACK. Within that interval, the App main thread is Runnable for 21ms before doFrame, and FrameTimeline shows the associated frame missed one vsync.

This evidence supports “App main-thread scheduling wait likely delayed the first frame by one refresh interval.” But if is_speculative_frame = true or no external video exists, it cannot support “confirmed user-visible latency of 76ms.”

A response-latency report can use this fixed format:

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
scenario: conversation click
trace_id: camera-free-chat-click-run03
config_version: input_response_lab_v2
query_id: input_to_present_v3
collection:
mode: lab
build_type: userdebug
input_trace_mode: TRACE_MODE_TRACE_ALL
raw_trace_upload: local_only
consent: lab_device
data_quality:
stats: clean
required_signals:
android.input.inputevent: present
FrameTimeline: present
sched: present
scenario_objects:
process_name: com.example.app
input_channel: com.example.app/com.example.ChatActivity
target_layer: TX - com.example.app/com.example.ChatActivity#0
endpoint_definition: transition_first_presented_frame
measurement_window: read_time..present_ts
start_clock: read_time
end_clock: present
input_event:
input_event_id: "21847"
event_seq: 77
event_action: UP
event_time: 12.342s
read_time: 12.345s
dispatch_ts: 12.348s
receive_ts: 12.352s
event_channel: com.example.app/com.example.ChatActivity
normalized_event_channel: com.example.app/com.example.ChatActivity
selection_reason: nearest Conversation#Click marker on confirmed channel
candidate_count: 1
business_marker:
name: Conversation#TransitionStart
ts: 12.371s
associated_frame:
frame_id: 912344
association_method: doframe_interval_overlap_then_present_candidate
speculative: false
present_ts: 12.421s
internal_input_read_to_present_ms: 76
user_visible_latency_ms: null
breakdown:
read_to_dispatch: 3ms
dispatch: 4ms
handling: 18ms
ack: 1ms
frame_after_ack: 50ms
evidence:
- App main thread Runnable 21ms before doFrame
- RenderThread DrawFrame 7ms
- FrameTimeline actual present missed expected by 1 vsync
review_points:
- ts=12.345s track=android_input_events input_event_id=21847
- ts=12.389s process=com.example.app thread=main state=Runnable dur=21ms
- ts=12.421s frame_id=912344 layer=ChatActivity
evidence_grade: likely
boundary:
fallback_used: none
missing_signals: []
external_measurement:
video: not_captured
touch_actuator: none
video_fps: none
display_refresh: 120hz
sync_method: none
error_bound_ms: unknown
owner_hint: App UI + platform scheduling review
owner_reason: Runnable identifies the waiting thread, not the party responsible for CPU contention

This format requires the report to state its start, end, breakdown, evidence, and boundaries together. Evidence grades are fixed as confirmed, likely, excluded, degraded, and unknown; use these English values consistently in report fields. is_speculative_frame = true, missing FrameTimeline, missing external video, data loss, and fallback paths all lower the grade. A response-latency report cannot provide only a total duration: the total describes the delay perceived by the user, while the breakdown identifies where engineers should investigate. Without external measurement, report only internal system latency, not absolute user-visible latency.

Bringing It Together

The analysis sequence for first-frame interaction response can be standardized:

  1. Define the start: DOWN, UP, effective MOVE, or business click.
  2. Define the end: first pressed-state frame, first transition frame, first content displacement, or first-screen content completion.
  3. Use android_input_events to separate read_to_dispatch, dispatch, handling, ACK, and input_to_present_ms.
  4. Use App markers to describe business state and the associated frame’s present as the screen endpoint.
  5. Use FrameTimeline, RenderThread, SurfaceFlinger, and CPU scheduling to explain why the first frame is early or late.
  6. Calibrate user-visible latency with a high-speed camera.

Startup speed covers a long scenario, smoothness covers stability across consecutive frames, and first-frame interaction response covers the first feedback after a user action. Keeping them separate makes the Perfetto evidence clear.

References

  1. PerfettoSQL standard library - android.input
  2. TraceConfig reference - AndroidInputEventConfig
  3. Android Jank detection with FrameTimeline
  4. ATrace: Android system and app trace events
  5. Track events
  6. Trace configuration
  7. CPU Scheduling events
  8. Buffers and dataflow
  9. Capture traces with adb commands - Input
  10. Android <profileable> manifest element
  11. ANR detection in InputDispatcher
  12. ViewRootImpl input pipeline

Source revision checked: b23c67be1159e3f0b0657bd97be9e139e0d8ee2c (2026-09-19).

About Me and the Blog

Follow Android Performance.

CATALOG
  1. 1. Perfetto Series Catalog
  2. 2. This Article Covers Only the First Frame After an Interaction
  3. 3. Break Response Latency into Four Metrics
  4. 4. Trace Capture Configuration
  5. 5. Which Tracks to Inspect First in Perfetto UI
  6. 6. Tap Scenarios: From DOWN/UP to the First Frame
  7. 7. Scroll Scenarios: From the First Effective MOVE to Content Displacement
  8. 8. Quantifying Input Latency with SQL
  9. 9. Common Attribution Paths
    1. 9.1. Slow Read/Dispatch
    2. 9.2. Slow Handling
    3. 9.3. Fast ACK, Slow First Frame
    4. 9.4. ANRs and Sub-ANR Delays
    5. 9.5. Scrolling Does Not Follow the Finger
  10. 10. Boundaries of High-Speed Cameras and Perfetto
  11. 11. Bringing It Together
  12. 12. References
  13. 13. About Me and the Blog