# Investigating scroll/animation jank
You are noticing janks when you are scrolling a page, or during an animation.
You want to investigate the jank. This document gives a quick overview of how
you can do this.
[TOC]
## Collecting a trace {#collecting-a-trace}
The best way to debug a janky scroll/animation is to collect a trace during the
event. There are detailed instructions for doing this
[here](http://www.chromium.org/developers/how-tos/trace-event-profiling-tool/recording-tracing-runs),
but briefly, to collect a trace:
* Close as many tabs/windows as possible. When a trace is collected, it
collects traces from _all_ running processes. Having a lot of tabs usually
means there are a lot of processes too, and traces from all these processes
can make it difficult to debug/investigate.
* If the jank can be reproduced reliably, then a good option is to start a
new instance of chrome in a test-only temporary profile (using
--user-data-dir command-line flag).
* Open up chrome:tracing in a new tab (or in a new window).
* Click ‘Record’:
![](debug-janks-resources/click-record-button.png)
* Select ‘Rendering’ in the dialog, and click ‘Record’:
![](debug-janks-resources/click-record.png)
* Switch to the tab with the page in question.
* Perform the action, e.g. scroll, or allow the animation to happen. Do this a
few times to increase the chances of janks happening.
* Note that you cannot collect trace for too long, because the
trace-buffer can fill up pretty quickly.
* Once you have noticed a jank, you can switch back to the chrome:tracing
page, and click on ‘Stop’.
![](debug-janks-resources/click-stop.png)
* Click ‘Save’ to save the trace. This step is optional, but can be useful
when sharing the trace with other developers, or attaching to a crbug, etc..
![](debug-janks-resources/click-save.png)
## Quick tips for the trace-viewer {#quick-tips-for-the-trace-viewer}
This document does not explain all the ways of navigating the trace-viewer, but
some quick tips:
* The trace includes trace-events from all the processes. This means the
browser process, the gpu process, the utility processes, all the renderer
processes, etc. Often, when you are investigating a jank, many of these
processes are not particularly relevant. The most important processes to
track are: the browser process, the gpu process, and the renderer process
hosting the page you are investigating. You can click on ‘Processes’ to
filter out the processes that are not interesting/relevant.
![](debug-janks-resources/click-processes.png)
* You can also click on the close-buttons for each process separately to hide
them. You can always click on ‘Processes’ any time to show the process again
if you want.
![](debug-janks-resources/click-process-close.png)
* You can turn on ‘flow’ events, which are sometimes very useful (M79 or
earlier; see more below).
![](debug-janks-resources/click-flow-check.png)
* In some cases, there can be too many types of flow events. In newer versions
of chrome (M80+), there is a ‘Flow events’ button you can click to select
which flow events you are interested in. For janks in animations, ‘viz’
flow-events would be useful. For janks in touch/wheel scrolls, the ‘viz’ and
‘input’ flow-events would be useful.
![](debug-janks-resources/click-flow-events.png)
## Find the Jank {#find-the-jank}
We have instrumented the code to record `FrameSequenceTracker` trace-events for
scrolling, or animations. There can be many `FrameSequenceTracker` events, but
each one carries the name of the sequence (e.g. TouchScroll, WheelScroll,
MainThreadAnimation, CompositorAnimation, RAF etc.)
In the trace you recorded, you will need to find the `FrameSequenceTracker`
corresponding to the janky scroll/animation. Each such event reports the number
of frames expected and the number of frames actually produced that became
visible.
![](debug-janks-resources/frame-sequence-tracker.png)
From these numbers, you can determine how many frames were dropped during the
sequence. For example, in the above screenshot, the scroll lasted for almost 1
second (59 frames), but only 54 frames were produced. This means there were 5
dropped frames during this scroll.
The next step is to figure out when these frames were dropped during the
sequence. To find this, look for the `FramePresented` sub-events for the
`FrameSequenceTracker` event.
![](debug-janks-resources/frame-presented-traces.png)
In the above screenshot, frames were presented at `A`, `B`, and `C`. Within this
duration, it appears a frame was presented every vsync. So there were no dropped
frames here. Contrast this with the following screenshot, where frames were
presented at `A`, `B`, `C`, `D`, `E`, `F` etc. The duration between `D` and `E`
is roughly double the duration between other `FramePresented` events as a result
of a dropped frame.
![](debug-janks-resources/frame-presented-dropped.png)
Now would be a good time to turn on flow events for `viz`. You can track the
flow events to see what may have caused the frame to be dropped. For example,
see the following screenshot:
![](debug-janks-resources/frame-submitted-not-submitted.png)
A begin-frame is received at ~5828ms, and if we follow the flow-arrows, we see
that a corresponding compositor-frame is submitted at ~5837ms. However, for the
next begin-frame received at ~5842ms, we see that there is no outgoing flow
from there. Looking more closely at these trace-events:
![](debug-janks-resources/frame-not-submitted-reason.png)
We see that the frame was deliberately skipped over to recover latency (notice
the `SkipBeginImplFrameToReduceLatency` event). At this point, we would want to
look at what else is happening around that time. You can use the
timing-selection in the trace-viewer to help with this:
![](debug-janks-resources/timing-selection.png)
The interesting things to look for are usually the `Compositor` thread and
`CrRendererMain` threads under the same process, or the `CrGpuMain` thread and
`VizCompositorThread` threads under the GPU Process. The
`Graphics.Pipeline.DrawAndSwap` trace-events (in the GPU process) usually give a
good idea of what is happening in the GPU process. Similarly, the
`PipelineReporter` events in the renderer process also give a good overview of
how each begin-frame message is handled.
![](debug-janks-resources/show-gpu-process-tracks.png)
## Examples {#examples}
Let’s look at some examples of janks:
### Example 1: jank caused by main-thread {#example-1-jank-caused-by-main-thread}
![](debug-janks-resources/missed-frame-main-thread-timer.png)
In the above screenshot: the `FramePresented` event is ~62ms long, i.e.
there were 3 dropped frames here. Looking closely, we can see that the
compositor thread receives all the begin-frame messages, including for the
dropped frames. The main-thread is blocked running JavaScript (likely a
timer-callback) and can't handle the begin-frames before the frame deadlines. We
can assume there are no pending updates for the compositor-thread to handle
(i.e. there are no compositor-driven animations/scrolls in-progress at the
moment), so the compositor must wait on the main-thread before it is able to
submit a new frame. Following is the same screenshot with some annotations:
![](debug-janks-resources/missed-frame-main-thread-timer-annotation.png)
The begin-frames are received in `A`, `B`, `C`. The compositor is unable to
produce any updates for `A` and `B` because the main-thread is blocked running
some javascript code. Once the JS processing ends, the begin-frame received in
`C` is handled in the main-thread at `D`. After the main-thread is done handling
the begin-frame (note that it is called BeginMainFrame in the code to denote
that it is for the main-thread), the corresponding update is dispatched to the
display-compositor in `E`. However, the submitted frame misses the deadline in
the display compositor (not seen in this screenshot), and is therefore not
presented. A fourth begin-frame is received (`F`), and a frame is submitted in
response to that (`G`), which is then finally presented. Thus, there are three
dropped frames (no frames produced for `A` and `B`, and updated frame for `C` is
delayed and presented to the user with `F`) caused by the long-runner javascript
in the main-thread.
### Example 2: jank caused by gpu-process {#example-2-jank-caused-by-gpu-process}
![](debug-janks-resources/jank-because-gpu-process.png)
In the above screenshot (from a different trace), you can see a begin-frame sent
from the display-compositor (`VizCompositorThread`) to the renderer in `A`, the
renderer receives the begin-frame in `B`, and submits a CompositorFrame in `C`,
received back in the display compositor in `D`. The display-compositor starts
drawing the frame in `E`, and the gpu-main thread does a swap at `F`. For the next
frame, the display-compositor sends a begin-frame at `G`, and receives the
compositor-frame from the renderer in `H`, and draws the frame in `I`. However, the
gpu-main thread is blocked doing other work (from the renderer, in this case,
notice the `FullImage` trace-event), and so the swap-request from the
display-compositor gets delayed (notice the long `WaitForSwap` trace-event), and
finally happens at `J`.
_Still have more questions? Please feel free to send questions to
[[email protected]](mailto:[email protected])._