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 an associated frame’s present. This gives a candidate internal delay, estimated_input_to_present_ms. Only after confirming the target layer and that the business state visibly changed on that frame should a report call it input_to_present_ms. Neither value covers 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.
Version note (reviewed October 2026): Configuration and SQL are checked against Perfetto v58.3. Record Android, device-producer, and analysis-tool versions separately. Input round trips,
frame_id, and candidate present do not form a complete visual causal chain. Version-specific association limits are documented below. Schemas, marker names, thresholds, and report numbers are examples requiring scenario validation, not device measurements.
Perfetto Series Catalog
- Android Perfetto Series Catalog
- Android Perfetto Series 1: Introduction to Perfetto
- Android Perfetto Series 2: Capturing Perfetto Traces
- Android Perfetto Series 3: Familiarizing with the Perfetto View
- Android Perfetto Series 4: Opening Large Traces via Command Line
- Android Perfetto Series 5: Choreographer-based Rendering Flow
- Android Perfetto Series 6: Why 120Hz? Advantages and Challenges
- Android Perfetto Series 7: MainThread and RenderThread Deep Dive
- Android Perfetto Series 8: Understanding Vsync and Performance Analysis
- Android Perfetto Series 9: Interpreting CPU Information
- Android Perfetto Series 10: Binder Scheduling and Lock Contention
- Android Perfetto Series 11: PerfettoSQL, Trace Processor and Regression Detection
- Android Perfetto Series 12: Trace Dataflow and Data Loss
- Android Perfetto Series 13: Perfetto SDK, Track Event and App Field Traces
- Android Perfetto Series 14: heapprofd and Memory Profiling
- Android Perfetto Series 15: Boot Traces and Long-running Field Tracing
- Android Perfetto Series 16: GPU, Power Counters and Hardware Bottlenecks
- Android Perfetto Series 17: Scenario Automation and Platform Tracing
- Android Perfetto Series 18: Input Response Latency
- Video (Bilibili) - Android Perfetto Basics and Case Studies
- 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 | User input |
android_input_events.end_to_end_latency_dur runs from read_time to the associated frame’s present. The standard library’s association is still a candidate: is_speculative_frame=false means only that an input-handling slice overlaps a doFrame on the same thread. It does not prove a visual change on the target layer. Queries below therefore output estimated_input_to_present_ms. Only after checking the frame token, target layer, actual present, and business state should a report call it 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.
v58.3 has two further implementation limits. Its present subquery filters by upid and app_ts at or after the candidate frame, then uses LIMIT 1 without ORDER BY, so it does not guarantee the earliest present. The final frame join uses event_seq without also constraining the channel. Recheck original slices for multiple channels, duplicate candidates, or sequence reuse. frame_id is the Vsync ID associated with doFrame; it is not guaranteed to be the surface/display token producing the candidate present. A single row still needs target-layer verification.

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? |
estimated_input_to_present_ms |
Input read (read_time) |
Associated frame present | When did the candidate frame present? Confirm the target layer and business state before calling it visible feedback. |
| Completion latency | Business trigger | Page or animation becomes stable | How long does the entire operation take? |
Report first-feedback delay separately from completion time. Early pressed-state feedback followed by later content differs from a long interval without visible feedback; assess its effect within the scenario and with external measurements.
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: event time extracted from an ATrace name. It normally precedesread_timein the input path, but v58.3 only converts the embedded string to an integer here; it does not convert that embedded clock. Do not subtract timestamps across clocks or treat this as an external touch onset.read_time: whenInputReaderreads the event. The end-to-end field inandroid_input_eventsmeasures 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.
The library derives read_time from the ts of UnwantedInteractionBlocker::notifyMotion, an instrumentation point in the Android input pipeline, not touch sampling. This implementation matches motion only; a key event can have a complete round trip but no read_time/event_time. MotionEvent.getEventTime() publicly uses milliseconds and the uptime timebase. External records need units, boot identity, and clock mapping; do not treat them directly as Trace Processor nanoseconds.
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 | scenario_id: conversation_click |
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.
Also archive the trace hash, upid, receive-track ID, full raw channel, and window/layer lifetimes. Normalized channels can lose instance prefixes and are display labels, not unique keys. ChatActivity and layer names here are hypothetical. When navigation starts in another Activity, record the input-receiving window separately from the endpoint window.
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 | schema_version: input_response_v1 |
For scrolling, refine the endpoint from “frame present” to “the present where content first visibly moves”:
1 | schema_version: input_response_v1 |
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 | buffers { |
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.
In v58.3, the first matching rule wins; conditions within a rule are ANDed. A rule with no matchers matches every event, and unmatched events are omitted. Allowlists generally use match_all_packages and denylists match_any_packages, with multi-target behavior verified. Redacted events omit coordinates or key/scan codes, so those fields cannot also support exact displacement reconstruction. This configuration does not enable trace_evdev_events and therefore does not directly capture kernel evdev input.
Ordinary apps cannot directly submit arbitrary system TraceConfigs or access the consumer socket. Release builds can expose supported shell-profiling access with <profileable android:shell="true" />. Check app ATrace eligibility against the OS, app settings, and atrace_apps rather than promising capture for every app on Android 12–17. Android 15/API 35 ProfilingManager can request system-managed profiling, including system traces; it does not expose raw inputevent or arbitrary system configuration.
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.
estimated_input_to_present_ms only records the library’s candidate difference from input to a 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 | SELECT name, idx, severity, source, value |
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.
An anomaly list is not a completeness gate. Check source registration and actual events, startup-error details, target-window coverage, clocks, and parser versions. Empty stats do not establish clean data. Ring-buffer overwrites may precede the target window; determine the affected scope. The 96 MiB buffer and polling periods are starting settings requiring overhead/coverage measurements. Flushes here do not periodically save a file.
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:
InputReader/ InputDispatcher: verify that the event was read and dispatched to the target window.- App main thread: verify arrival and whether the thread became Running promptly.
Choreographer#doFrame: determine whether the state change advances into the next frame throughInput,Insets Animation,Animation,Traversal, orCommit. Input receive/ACK need not occur inside doFrame; batched input may also be consumed throughCALLBACK_INPUT.- RenderThread: after the UI thread submits work, check for stalls in stages such as
syncFrameState,DrawFrame,dequeueBuffer, andqueueBuffer. - FrameTimeline: check the associated frame’s expected/actual timelines, token,
layer_name, and association method. - SurfaceFlinger: check timely buffer latching and whether HWC/GPU composition, acquire/release/present fences, or other layers affect final presentation.
- 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.
I would rather leave the visual endpoint unconfirmed than take the next present timestamp as proof that the tap changed the screen. That shortcut produces a precise number for the wrong layer surprisingly easily.
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:
- Find the corresponding ACTION_UP in
android_input_eventsor on the InputDispatcher track. - Measure dispatch to App receive to determine whether system dispatch is slow.
- Check whether the App main thread is scheduled promptly after receiving the event.
- Check the click handler for Binder, IO, lock waits, synchronous layout, and image decoding.
- Look for waits introduced by the
startActivityBinder call, Activity launch, window creation, focus changes, starting window, transition, and target-layer visibility. - Check whether the next
Choreographer#doFrameadvances state throughAnimation,Traversal, orCommit. - Inspect RenderThread, FrameTimeline, and SurfaceFlinger to confirm the associated frame actually presents.
- 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 | Accumulated consumed 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 | fun onConversationClick(conversationId: String) { |
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.
Import android.os.Trace and integrate these fragments with existing app callbacks. openConversation is an app function, not a standalone runnable example. Pair begin/end on the same thread. try/finally measures the synchronous click call, not asynchronous page loading. Repeated or concurrent interactions need case-local operation IDs or flows instead of selecting the first same-name marker; IDs must not contain conversation content or user identifiers.
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:
- Find the MOVE sequence after ACTION_DOWN.
- Exclude small MOVE events within touch slop and select the MOVE that the business logic treats as the start of scrolling.
- Check whether the App main thread receives this MOVE and ACKs promptly.
- Check whether the
Inputstage ofChoreographer#doFrameupdates the scroll offset. - Check whether the next frame is submitted to RenderThread.
- Check the FrameTimeline present time.
- 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 | var gestureScrollY = 0L |
android.os.Trace.setCounter(String, long) starts at API 29; this example assumes Android 12+. It records one RecyclerView while its state is DRAGGING, excluding flings and non-dragging states. Programmatic scrolling during a drag still requires application-side source attribution. Reset and record a baseline at a new DOWN, then end at UP/CANCEL. Parent interception or custom gesture handling requires matching lifecycle integration. Layout changes can also call onScrolled with zero dx/dy; callback counts do not equal MOVE counts.
Integrate this recorder with existing touch dispatch: call beginFeedGesture before delivering the target DOWN to RecyclerView, and endFeedGesture after delivering UP/CANCEL, also ending on parent interruption. Run it on the UI thread, register the listener once, and remove it with removeOnScrollListener when the view is destroyed. Copying the listener alone does not connect gesture boundaries.
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.
Record the slop axis/tool type, pointer ID, history/batching, and resampling policy rather than selecting the first MOVE beyond a fixed pixel distance. The counter reflects cumulative state when the callback runs. Identical counter names across list instances can mix; use distinct tracks or instrument only the target instance, preserving gesture/operation boundaries.

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. Do not assume the extracted action has 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 | INCLUDE PERFETTO MODULE android.input; |
Inspect values before filtering candidates. *UP also matches POINTER_UP, while v58.3 derives event_action through the frame-association branch, so a missing association can leave it NULL. Filtering only UP cannot prove absence of input. The query below uses a confirmed click slice_id, upid, full raw channel, and receive track, listing candidates before the marker within an explicitly chosen window. It does not automatically select the nearest row. Replace NULL with actual trace-local values; choose the window for the interaction.
1 | INCLUDE PERFETTO MODULE android.input; |
After confirming an event, identify its round trip with the full channel, event_seq, dispatch_ts, and upid, not input_event_id or normalized channel alone. The following lists all candidates in the target window and counts rows sharing one dispatch identity, exposing association multiplication. Filter by the reviewed composite key for a single-event report; retain and downgrade duplicate or negative-duration evidence.
1 | INCLUDE PERFETTO MODULE android.input; |
The fields mean:
dispatch_latency_dur: InputDispatcher sending the event to the App receiving it.read_to_dispatch_ms:InputReaderreading 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;NULLwhen 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, rank round trips by total_latency_dur without filtering end_to_end_latency_dur:
1 | INCLUDE PERFETTO MODULE android.input; |
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.
This window selects event starts in a half-open interval, not every overlapping dispatch/handling span; expand it for boundary investigations. rows_for_dispatch counts only surviving selected rows, not globally deduplicated inputs. In v58.3, tid/thread_name actually come from the finish side; resolve the receiving thread through receive_track_id and thread_track. The slowest 50 completed round trips still omit unacknowledged events; inspect raw InputDispatcher evidence and logs separately.
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 candidate estimated_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.”
Scheduling targets should come from the reviewed scenario dictionary: app main, RenderThread, InputReader/InputDispatcher, relevant WindowManager, and SurfaceFlinger threads. Fill explicit utid/role pairs below; NULL matches nothing. Add or remove confirmed rows rather than treating every SurfaceFlinger thread as relevant. Use the target input/dependency interval; accumulated Running or Runnable across processes cannot be summed into end-to-end latency.
1 | WITH target_window(start_ts, end_ts) AS ( |
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.
Coverage sums assume non-overlapping intervals per utid; downgrade invalid overlaps first. Running means the scheduler placed the thread on a CPU, not that every nanosecond executed app instructions; IRQs need separate evidence. High Running does not prove sufficient or insufficient frequency, and blocked Binder/lock time is not CPU execution time.
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
InputReaderand 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
Inputstage. - 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/Traversalis scheduled for a later Vsync. - Whether RenderThread CPU work stalls in
syncFrameStateorDrawFrame. - Whether
dequeueBuffer/queueBufferindicates 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 input-channel finish protocol has advanced; 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. An event with no ACK may be absent from the fully joined
android_input_eventsround-trip table; inspect InputDispatcher, relevant logs where available, and the bugreport instead of treating missing rows as a fast response. - 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 | High-speed camera / robotic touch actuator: |
High-speed video supplies externally observed endpoints; Perfetto separates internal stages, and app markers identify business actions. Calibrate their time domains instead of treating internal present as the photographed pixel transition. Temporal agreement alone does not establish a cause.
Define contact/actuator onset, target pixel region, exposure/rolling shutter, frame timestamps, scanout location, and synchronization residuals. 1000/fps is a sampling interval, not a bound on total experimental error. Check dropped/interpolated frames, slow-motion playback time, and variable refresh rates. Report repeated trials and distributions rather than treating one illustrative 76 ms value as a device conclusion.
The following numbers are hypothetical report examples, not device measurements: handling is 18 ms, read-to-dispatch 3 ms, dispatch 4 ms, and system ACK adds 1 ms. The candidate frame occurs 50 ms after that ACK. A 21 ms Runnable interval and a late frame would be observations requiring dependency and endpoint checks, not proof that scheduling caused the full delay.
Such observations can support an investigation of scheduling delay, but do not establish a one-refresh causal penalty. A speculative frame or missing external measurement cannot support a claim of confirmed 76 ms user-visible latency.
A response-latency report can use this fixed format:
1 | example_only: true |
Grade internal observations separately from external visible measurements. Missing video leaves absolute user-visible latency unmeasured, but does not automatically invalidate an independently supported dispatch interval. Rename a metric when a fallback changes its endpoints. Assign confirmed/likely/excluded/degraded/unknown per claim and coverage scope; missing data cannot justify excluded. This example demonstrates fields and arithmetic only, without device or visual acceptance.
A response-latency report needs more than one total duration: breaking it into stages tells us which team to involve in the next investigation.
Bringing It Together
The analysis sequence for first-frame interaction response can be standardized:
- Define the start: DOWN, UP, effective MOVE, or business click.
- Define the end: first pressed-state frame, first transition frame, first content displacement, or first-screen content completion.
- Use
android_input_eventsto separateread_to_dispatch, dispatch, handling, ACK, and the candidateestimated_input_to_present_ms. - Confirm the target layer and visual-state change with App markers and, where needed, external video before treating the associated frame’s present as the screen endpoint.
- Use FrameTimeline, RenderThread, SurfaceFlinger, and CPU scheduling to explain why the first frame is early or late.
- 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
- PerfettoSQL standard library - android.input
- TraceConfig reference - AndroidInputEventConfig
- Android Jank detection with FrameTimeline
- ATrace: Android system and app trace events
- Track events
- Trace configuration
- CPU Scheduling events
- Buffers and dataflow
- Capture traces with adb commands - Input
- Android
<profileable>manifest element - ANR detection in InputDispatcher
- ViewRootImpl input pipeline
Source revision checked: v58.3 (2026-10-14).
- ATrace parsing and frame association in android.input
- Official input round-trip tests
- Trace API
- RecyclerView.OnScrollListener
- MotionEvent timebase
- ProfilingManager
About Me and the Blog
Follow Android Performance.