• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2022 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import {uuidv4Sql} from '../../base/uuid';
16import {generateSqlWithInternalLayout} from '../../components/sql_utils/layout';
17import {Trace} from '../../public/trace';
18import {PerfettoPlugin} from '../../public/plugin';
19import {
20  createEventLatencyTrack,
21  JANKY_LATENCY_NAME,
22} from './event_latency_track';
23import {createScrollJankV3Track} from './scroll_jank_v3_track';
24import {ScrollJankCauseMap} from './scroll_jank_cause_map';
25import {TrackNode} from '../../public/workspace';
26import SqlModulesPlugin from '../dev.perfetto.SqlModules';
27import {createScrollTimelineModel} from './scroll_timeline_model';
28import {createQuerySliceTrack} from '../../components/tracks/query_slice_track';
29import {createFlatColoredDurationTrack} from './flat_colored_duration_track';
30import {createTopLevelScrollTrack} from './scroll_track';
31import {createScrollTimelineTrack} from './scroll_timeline_track';
32import {LONG, NUM, STR} from '../../trace_processor/query_result';
33import {SourceDataset} from '../../trace_processor/dataset';
34import {DatasetSliceTrack} from '../../components/tracks/dataset_slice_track';
35import {escapeQuery} from '../../trace_processor/query_utils';
36import {ThreadSliceDetailsPanel} from '../../components/details/thread_slice_details_tab';
37
38export default class implements PerfettoPlugin {
39  static readonly id = 'org.chromium.ChromeScrollJank';
40  static readonly dependencies = [SqlModulesPlugin];
41
42  async onTraceLoad(ctx: Trace): Promise<void> {
43    const group = new TrackNode({
44      title: 'Chrome Scroll Jank',
45      sortOrder: -30,
46      isSummary: true,
47    });
48    await this.addTopLevelScrollTrack(ctx, group);
49    await this.addEventLatencyTrack(ctx, group);
50    await this.addScrollJankV3ScrollTrack(ctx, group);
51    await ScrollJankCauseMap.initialize(ctx.engine);
52    await this.addScrollTimelineTrack(ctx, group);
53    await this.addVsyncTracks(ctx, group);
54    ctx.workspace.addChildInOrder(group);
55    group.expand();
56  }
57
58  private async addTopLevelScrollTrack(
59    ctx: Trace,
60    group: TrackNode,
61  ): Promise<void> {
62    await ctx.engine.query(`
63      INCLUDE PERFETTO MODULE chrome.chrome_scrolls;
64      INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_offsets;
65      INCLUDE PERFETTO MODULE chrome.event_latency;
66    `);
67
68    const uri = 'org.chromium.ChromeScrollJank#toplevelScrolls';
69    const title = 'Chrome Scrolls';
70
71    ctx.tracks.registerTrack({
72      uri,
73      title,
74      track: createTopLevelScrollTrack(ctx, uri),
75    });
76
77    const track = new TrackNode({uri, title});
78    group.addChildInOrder(track);
79  }
80
81  private async addEventLatencyTrack(
82    ctx: Trace,
83    group: TrackNode,
84  ): Promise<void> {
85    const subTableSql = generateSqlWithInternalLayout({
86      columns: ['id', 'ts', 'dur', 'track_id', 'name'],
87      source: 'chrome_event_latencies',
88      ts: 'ts',
89      dur: 'dur',
90      whereClause: `
91        event_type IN (
92          'FIRST_GESTURE_SCROLL_UPDATE',
93          'GESTURE_SCROLL_UPDATE',
94          'INERTIAL_GESTURE_SCROLL_UPDATE')
95        AND is_presented`,
96    });
97
98    // Table name must be unique - it cannot include '-' characters or begin
99    // with a numeric value.
100    const baseTable = `table_${uuidv4Sql()}_janky_event_latencies_v3`;
101    const tableDefSql = `CREATE TABLE ${baseTable} AS
102        WITH
103        event_latencies AS MATERIALIZED (
104          ${subTableSql}
105        ),
106        latency_stages AS (
107          SELECT
108            stage.id,
109            stage.ts,
110            stage.dur,
111            stage.track_id,
112            stage.name,
113            stage.depth,
114            event.id as event_latency_id,
115            event.depth as event_latency_depth
116          FROM event_latencies event
117          JOIN descendant_slice(event.id) stage
118          UNION ALL
119          SELECT
120            event.id,
121            event.ts,
122            event.dur,
123            event.track_id,
124            IIF(
125              id IN (SELECT id FROM chrome_janky_event_latencies_v3),
126              '${JANKY_LATENCY_NAME}',
127              name
128            ) as name,
129            0 as depth,
130            event.id as event_latency_id,
131            event.depth as event_latency_depth
132          FROM event_latencies event
133        ),
134        -- Event latencies have already had layout computed, but the width of event latency can vary (3 or 4),
135        -- so we have to compute the max stage depth for each event latency depth to compute offset for each
136        -- event latency row.
137        event_latency_height_per_row AS (
138          SELECT
139            event_latency_depth,
140            MAX(depth) AS max_depth
141          FROM latency_stages
142          GROUP BY event_latency_depth
143        ),
144        -- Compute the offset for each event latency depth using max depth info for each depth.
145        event_latency_layout_offset AS (
146          SELECT
147            event_latency_depth,
148            -- As the sum is exclusive, it will return NULL for the first row — we need to set it to 0 explicitly.
149            IFNULL(
150              SUM(max_depth + 1) OVER (
151                ORDER BY event_latency_depth
152                ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
153              ),
154            0) as offset
155          FROM event_latency_height_per_row
156        )
157      SELECT
158        stage.id,
159        stage.ts,
160        stage.dur,
161        stage.name,
162        stage.depth + (
163          (
164            SELECT offset.offset
165            FROM event_latencies event
166            JOIN event_latency_layout_offset offset ON event.depth = offset.event_latency_depth
167            WHERE id = stage.event_latency_id
168          )
169        ) AS depth
170      FROM latency_stages stage;`;
171
172    await ctx.engine.query(
173      `INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_intervals`,
174    );
175    await ctx.engine.query(tableDefSql);
176
177    const uri = 'org.chromium.ChromeScrollJank#eventLatency';
178    const title = 'Chrome Scroll Input Latencies';
179
180    ctx.tracks.registerTrack({
181      uri,
182      title,
183      track: createEventLatencyTrack(ctx, uri, baseTable),
184    });
185
186    const track = new TrackNode({uri, title});
187    group.addChildInOrder(track);
188  }
189
190  private async addScrollJankV3ScrollTrack(
191    ctx: Trace,
192    group: TrackNode,
193  ): Promise<void> {
194    await ctx.engine.query(
195      `INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_intervals`,
196    );
197
198    const uri = 'org.chromium.ChromeScrollJank#scrollJankV3';
199    const title = 'Chrome Scroll Janks';
200
201    ctx.tracks.registerTrack({
202      uri,
203      title,
204      track: createScrollJankV3Track(ctx, uri),
205    });
206
207    const track = new TrackNode({uri, title});
208    group.addChildInOrder(track);
209  }
210
211  private async addScrollTimelineTrack(
212    ctx: Trace,
213    group: TrackNode,
214  ): Promise<void> {
215    const uri = 'org.chromium.ChromeScrollJank#scrollTimeline';
216    const title = 'Chrome Scroll Timeline';
217
218    const tableName =
219      'scrolltimelinetrack_org_chromium_ChromeScrollJank_scrollTimeline';
220    const model = await createScrollTimelineModel(ctx.engine, tableName, uri);
221
222    ctx.tracks.registerTrack({
223      uri,
224      title,
225      track: createScrollTimelineTrack(ctx, model),
226    });
227
228    const track = new TrackNode({uri, title});
229    group.addChildInOrder(track);
230  }
231
232  private async addVsyncTracks(ctx: Trace, group: TrackNode) {
233    const vsyncTable = '_chrome_scroll_jank_plugin_vsyncs';
234    await ctx.engine.query(`
235      INCLUDE PERFETTO MODULE chrome.chrome_scrolls;
236
237      CREATE PERFETTO TABLE ${vsyncTable} AS
238      SELECT
239        id,
240        ts,
241        dur,
242        track_id,
243        name
244      FROM slice
245      WHERE name = 'Extend_VSync'`);
246
247    {
248      // Add a track for the VSync slices.
249      const uri = 'org.chromium.ChromeScrollJank#ChromeVsync';
250      const track = await createQuerySliceTrack({
251        trace: ctx,
252        data: {sqlSource: `SELECT * FROM ${vsyncTable}`},
253        argColumns: ['id', 'track_id', 'ts', 'dur'],
254        uri,
255      });
256      const title = 'Chrome VSync';
257      ctx.tracks.registerTrack({uri, title, track});
258      group.addChildInOrder(new TrackNode({uri, title}));
259    }
260
261    {
262      // Add a track which tracks the differences between VSyncs.
263      const uri = 'org.chromium.ChromeScrollJank#ChromeVsyncDelta';
264      const track = createFlatColoredDurationTrack(
265        ctx,
266        uri,
267        `(SELECT id, ts, LEAD(ts) OVER (ORDER BY ts) - ts as dur FROM ${vsyncTable})`,
268      );
269      const title = 'Chrome VSync delta';
270      ctx.tracks.registerTrack({uri, title, track});
271      group.addChildInOrder(new TrackNode({uri, title}));
272    }
273
274    {
275      // Add a track which tracks the differences between inputs.
276      const uri = 'org.chromium.ChromeScrollJank#ChromeInputDelta';
277      const track = createFlatColoredDurationTrack(
278        ctx,
279        uri,
280        `(SELECT
281          ROW_NUMBER() OVER () AS id,
282          generation_ts AS ts,
283          LEAD(generation_ts) OVER (ORDER BY generation_ts) - generation_ts as dur
284        FROM chrome_scroll_update_info
285        WHERE generation_ts IS NOT NULL)`,
286      );
287      const title = 'Chrome input delta';
288      ctx.tracks.registerTrack({uri, title, track});
289      group.addChildInOrder(new TrackNode({uri, title}));
290    }
291
292    {
293      const steps = [
294        {
295          column: 'scroll_update_created_slice_id',
296          name: 'Step: send event (UI, ScrollUpdate)',
297        },
298        {
299          column: 'compositor_dispatch_slice_id',
300          name: 'Step: compositor dispatch (ScrollUpdate)',
301        },
302        {
303          column: 'compositor_resample_slice_id',
304          name: 'Step: resample input',
305        },
306        {
307          column: 'compositor_generate_compositor_frame_slice_id',
308          name: 'Step: generate frame (compositor)',
309        },
310        {
311          column: 'viz_receive_compositor_frame_slice_id',
312          name: 'Step: receive frame (viz)',
313        },
314      ];
315
316      for (const step of steps) {
317        const uri = `org.chromium.ChromeScrollJank#chrome_scroll_update_info.${step.column}`;
318        const track = new DatasetSliceTrack({
319          trace: ctx,
320          uri,
321          dataset: new SourceDataset({
322            schema: {
323              id: NUM,
324              ts: LONG,
325              dur: LONG,
326              name: STR,
327            },
328            src: `
329              WITH slice_ids AS MATERIALIZED (
330                SELECT DISTINCT ${step.column} AS slice_id
331                FROM chrome_scroll_update_info
332                WHERE ${step.column} IS NOT NULL
333              )
334              SELECT
335                slice.id,
336                slice.ts,
337                slice.dur,
338                 ${escapeQuery(step.name)} AS name
339              FROM slice_ids
340              JOIN slice USING (slice_id)
341            `,
342          }),
343          detailsPanel: () => new ThreadSliceDetailsPanel(ctx),
344        });
345        ctx.tracks.registerTrack({uri, title: step.name, track});
346        group.addChildInOrder(new TrackNode({uri, title: step.name}));
347      }
348    }
349  }
350}
351