chromium/docs/speed/debug-janks.md

# 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])._