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