xref: /aosp_15_r20/external/perfetto/ui/src/plugins/org.chromium.ChromeScrollJank/index.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
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 {EventLatencyTrack, JANKY_LATENCY_NAME} from './event_latency_track';
20import {ScrollJankV3Track} from './scroll_jank_v3_track';
21import {TopLevelScrollTrack} from './scroll_track';
22import {ScrollJankCauseMap} from './scroll_jank_cause_map';
23import {TrackNode} from '../../public/workspace';
24
25export default class implements PerfettoPlugin {
26  static readonly id = 'org.chromium.ChromeScrollJank';
27  async onTraceLoad(ctx: Trace): Promise<void> {
28    const group = new TrackNode({
29      title: 'Chrome Scroll Jank',
30      sortOrder: -30,
31      isSummary: true,
32    });
33    await this.addTopLevelScrollTrack(ctx, group);
34    await this.addEventLatencyTrack(ctx, group);
35    await this.addScrollJankV3ScrollTrack(ctx, group);
36    await ScrollJankCauseMap.initialize(ctx.engine);
37    ctx.workspace.addChildInOrder(group);
38    group.expand();
39  }
40
41  private async addTopLevelScrollTrack(
42    ctx: Trace,
43    group: TrackNode,
44  ): Promise<void> {
45    await ctx.engine.query(`
46      INCLUDE PERFETTO MODULE chrome.chrome_scrolls;
47      INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_offsets;
48      INCLUDE PERFETTO MODULE chrome.event_latency;
49    `);
50
51    const uri = 'org.chromium.ChromeScrollJank#toplevelScrolls';
52    const title = 'Chrome Scrolls';
53
54    ctx.tracks.registerTrack({
55      uri,
56      title,
57      track: new TopLevelScrollTrack(ctx, uri),
58    });
59
60    const track = new TrackNode({uri, title});
61    group.addChildInOrder(track);
62  }
63
64  private async addEventLatencyTrack(
65    ctx: Trace,
66    group: TrackNode,
67  ): Promise<void> {
68    const subTableSql = generateSqlWithInternalLayout({
69      columns: ['id', 'ts', 'dur', 'track_id', 'name'],
70      sourceTable: 'chrome_event_latencies',
71      ts: 'ts',
72      dur: 'dur',
73      whereClause: `
74        event_type IN (
75          'FIRST_GESTURE_SCROLL_UPDATE',
76          'GESTURE_SCROLL_UPDATE',
77          'INERTIAL_GESTURE_SCROLL_UPDATE')
78        AND is_presented`,
79    });
80
81    // Table name must be unique - it cannot include '-' characters or begin
82    // with a numeric value.
83    const baseTable = `table_${uuidv4Sql()}_janky_event_latencies_v3`;
84    const tableDefSql = `CREATE TABLE ${baseTable} AS
85        WITH
86        event_latencies AS MATERIALIZED (
87          ${subTableSql}
88        ),
89        latency_stages AS (
90          SELECT
91            stage.id,
92            stage.ts,
93            stage.dur,
94            stage.track_id,
95            stage.name,
96            stage.depth,
97            event.id as event_latency_id,
98            event.depth as event_latency_depth
99          FROM event_latencies event
100          JOIN descendant_slice(event.id) stage
101          UNION ALL
102          SELECT
103            event.id,
104            event.ts,
105            event.dur,
106            event.track_id,
107            IIF(
108              id IN (SELECT id FROM chrome_janky_event_latencies_v3),
109              '${JANKY_LATENCY_NAME}',
110              name
111            ) as name,
112            0 as depth,
113            event.id as event_latency_id,
114            event.depth as event_latency_depth
115          FROM event_latencies event
116        ),
117        -- Event latencies have already had layout computed, but the width of event latency can vary (3 or 4),
118        -- so we have to compute the max stage depth for each event latency depth to compute offset for each
119        -- event latency row.
120        event_latency_height_per_row AS (
121          SELECT
122            event_latency_depth,
123            MAX(depth) AS max_depth
124          FROM latency_stages
125          GROUP BY event_latency_depth
126        ),
127        -- Compute the offset for each event latency depth using max depth info for each depth.
128        event_latency_layout_offset AS (
129          SELECT
130            event_latency_depth,
131            -- As the sum is exclusive, it will return NULL for the first row — we need to set it to 0 explicitly.
132            IFNULL(
133              SUM(max_depth + 1) OVER (
134                ORDER BY event_latency_depth
135                ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
136              ),
137            0) as offset
138          FROM event_latency_height_per_row
139        )
140      SELECT
141        stage.id,
142        stage.ts,
143        stage.dur,
144        stage.name,
145        stage.depth + (
146          (
147            SELECT offset.offset
148            FROM event_latencies event
149            JOIN event_latency_layout_offset offset ON event.depth = offset.event_latency_depth
150            WHERE id = stage.event_latency_id
151          )
152        ) AS depth
153      FROM latency_stages stage;`;
154
155    await ctx.engine.query(
156      `INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_intervals`,
157    );
158    await ctx.engine.query(tableDefSql);
159
160    const uri = 'org.chromium.ChromeScrollJank#eventLatency';
161    const title = 'Chrome Scroll Input Latencies';
162
163    ctx.tracks.registerTrack({
164      uri,
165      title,
166      track: new EventLatencyTrack(ctx, uri, baseTable),
167    });
168
169    const track = new TrackNode({uri, title});
170    group.addChildInOrder(track);
171  }
172
173  private async addScrollJankV3ScrollTrack(
174    ctx: Trace,
175    group: TrackNode,
176  ): Promise<void> {
177    await ctx.engine.query(
178      `INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_intervals`,
179    );
180
181    const uri = 'org.chromium.ChromeScrollJank#scrollJankV3';
182    const title = 'Chrome Scroll Janks';
183
184    ctx.tracks.registerTrack({
185      uri,
186      title,
187      track: new ScrollJankV3Track(ctx, uri),
188    });
189
190    const track = new TrackNode({uri, title});
191    group.addChildInOrder(track);
192  }
193}
194