1// Copyright (C) 2023 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 m from 'mithril'; 16import {Icons} from '../../base/semantic_icons'; 17import {duration, Time, time} from '../../base/time'; 18import {exists} from '../../base/utils'; 19import {SliceSqlId} from '../../components/sql_utils/core_types'; 20import {Engine} from '../../trace_processor/engine'; 21import {LONG, NUM, STR} from '../../trace_processor/query_result'; 22import {Anchor} from '../../widgets/anchor'; 23import { 24 CauseProcess, 25 CauseThread, 26 ScrollJankCauseMap, 27} from './scroll_jank_cause_map'; 28import {scrollTo} from '../../public/scroll_helper'; 29import {Trace} from '../../public/trace'; 30 31const UNKNOWN_NAME = 'Unknown'; 32 33export interface EventLatencyStage { 34 name: string; 35 // Slice id of the top level EventLatency slice (not a stage). 36 eventLatencyId: SliceSqlId; 37 ts: time; 38 dur: duration; 39} 40 41export interface EventLatencyCauseThreadTracks { 42 // A thread may have multiple tracks associated with it (e.g. from ATrace 43 // events). 44 trackIds: number[]; 45 thread: CauseThread; 46 causeDescription: string; 47} 48 49export async function getScrollJankCauseStage( 50 engine: Engine, 51 eventLatencyId: SliceSqlId, 52): Promise<EventLatencyStage | undefined> { 53 const queryResult = await engine.query(` 54 SELECT 55 IFNULL(cause_of_jank, '${UNKNOWN_NAME}') AS causeOfJank, 56 IFNULL(sub_cause_of_jank, '${UNKNOWN_NAME}') AS subCauseOfJank, 57 IFNULL(substage.ts, -1) AS ts, 58 IFNULL(substage.dur, -1) AS dur 59 FROM chrome_janky_frame_presentation_intervals 60 JOIN descendant_slice(event_latency_id) substage 61 WHERE event_latency_id = ${eventLatencyId} 62 AND substage.name = COALESCE(sub_cause_of_jank, cause_of_jank) 63 `); 64 65 const causeIt = queryResult.iter({ 66 causeOfJank: STR, 67 subCauseOfJank: STR, 68 ts: LONG, 69 dur: LONG, 70 }); 71 72 for (; causeIt.valid(); causeIt.next()) { 73 const causeOfJank = causeIt.causeOfJank; 74 const subCauseOfJank = causeIt.subCauseOfJank; 75 76 if (causeOfJank == '' || causeOfJank == UNKNOWN_NAME) return undefined; 77 const cause = subCauseOfJank == UNKNOWN_NAME ? causeOfJank : subCauseOfJank; 78 const stageDetails: EventLatencyStage = { 79 name: cause, 80 eventLatencyId: eventLatencyId, 81 ts: Time.fromRaw(causeIt.ts), 82 dur: causeIt.dur, 83 }; 84 85 return stageDetails; 86 } 87 88 return undefined; 89} 90 91export async function getEventLatencyCauseTracks( 92 engine: Engine, 93 scrollJankCauseStage: EventLatencyStage, 94): Promise<EventLatencyCauseThreadTracks[]> { 95 const threadTracks: EventLatencyCauseThreadTracks[] = []; 96 const causeDetails = ScrollJankCauseMap.getEventLatencyDetails( 97 scrollJankCauseStage.name, 98 ); 99 if (causeDetails === undefined) return threadTracks; 100 101 for (const cause of causeDetails.jankCauses) { 102 switch (cause.process) { 103 case CauseProcess.RENDERER: 104 case CauseProcess.BROWSER: 105 case CauseProcess.GPU: 106 const tracksForProcess = await getChromeCauseTracks( 107 engine, 108 scrollJankCauseStage.eventLatencyId, 109 cause.process, 110 cause.thread, 111 ); 112 for (const track of tracksForProcess) { 113 track.causeDescription = cause.description; 114 threadTracks.push(track); 115 } 116 break; 117 case CauseProcess.UNKNOWN: 118 default: 119 break; 120 } 121 } 122 123 return threadTracks; 124} 125 126async function getChromeCauseTracks( 127 engine: Engine, 128 eventLatencySliceId: number, 129 processName: CauseProcess, 130 threadName: CauseThread, 131): Promise<EventLatencyCauseThreadTracks[]> { 132 const queryResult = await engine.query(` 133 INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_cause_utils; 134 135 SELECT DISTINCT 136 utid, 137 id AS trackId 138 FROM thread_track 139 WHERE utid IN ( 140 SELECT DISTINCT 141 utid 142 FROM chrome_select_scroll_jank_cause_thread( 143 ${eventLatencySliceId}, 144 '${processName}', 145 '${threadName}' 146 ) 147 ); 148 `); 149 150 const it = queryResult.iter({ 151 utid: NUM, 152 trackId: NUM, 153 }); 154 155 const threadsWithTrack: {[id: number]: EventLatencyCauseThreadTracks} = {}; 156 const utids: number[] = []; 157 for (; it.valid(); it.next()) { 158 const utid = it.utid; 159 if (!(utid in threadsWithTrack)) { 160 threadsWithTrack[utid] = { 161 trackIds: [it.trackId], 162 thread: threadName, 163 causeDescription: '', 164 }; 165 utids.push(utid); 166 } else { 167 threadsWithTrack[utid].trackIds.push(it.trackId); 168 } 169 } 170 171 return utids.map((each) => threadsWithTrack[each]); 172} 173 174export function getCauseLink( 175 trace: Trace, 176 threadTracks: EventLatencyCauseThreadTracks, 177 tracksByTrackId: Map<number, string>, 178 ts: time | undefined, 179 dur: duration | undefined, 180): m.Child { 181 const trackUris: string[] = []; 182 for (const trackId of threadTracks.trackIds) { 183 const track = tracksByTrackId.get(trackId); 184 if (track === undefined) { 185 return `Could not locate track ${trackId} for thread ${threadTracks.thread} in the global state`; 186 } 187 trackUris.push(track); 188 } 189 190 if (trackUris.length == 0) { 191 return `No valid tracks for thread ${threadTracks.thread}.`; 192 } 193 194 // Fixed length of a container to ensure that the icon does not overlap with 195 // the text due to table formatting. 196 return m( 197 `div[style='width:250px']`, 198 m( 199 Anchor, 200 { 201 icon: Icons.UpdateSelection, 202 onclick: () => { 203 scrollTo({ 204 track: {uri: trackUris[0], expandGroup: true}, 205 }); 206 if (exists(ts) && exists(dur)) { 207 scrollTo({ 208 time: { 209 start: ts, 210 end: Time.fromRaw(ts + dur), 211 viewPercentage: 0.3, 212 }, 213 }); 214 trace.selection.selectArea({ 215 start: ts, 216 end: Time.fromRaw(ts + dur), 217 trackUris, 218 }); 219 } 220 }, 221 }, 222 threadTracks.thread, 223 ), 224 ); 225} 226