1// Copyright (C) 2020 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use size 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 {Engine} from '../common/engine'; 16import {featureFlags} from '../common/feature_flags'; 17import {NUM, STR_NULL} from '../common/query_result'; 18import {Area} from '../common/state'; 19import {fromNs} from '../common/time'; 20import {Flow, globals} from '../frontend/globals'; 21import {publishConnectedFlows, publishSelectedFlows} from '../frontend/publish'; 22import { 23 ACTUAL_FRAMES_SLICE_TRACK_KIND, 24 Config as ActualConfig, 25} from '../tracks/actual_frames'; 26import { 27 Config as SliceConfig, 28 SLICE_TRACK_KIND, 29} from '../tracks/chrome_slices'; 30 31import {Controller} from './controller'; 32 33export interface FlowEventsControllerArgs { 34 engine: Engine; 35} 36 37const SHOW_INDIRECT_PRECEDING_FLOWS_FLAG = featureFlags.register({ 38 id: 'showIndirectPrecedingFlows', 39 name: 'Show indirect preceding flows', 40 description: 'Show indirect preceding flows (connected through ancestor ' + 41 'slices) when a slice is selected.', 42 defaultValue: false, 43}); 44 45 46export class FlowEventsController extends Controller<'main'> { 47 private lastSelectedSliceId?: number; 48 private lastSelectedArea?: Area; 49 private lastSelectedKind: 'CHROME_SLICE'|'AREA'|'NONE' = 'NONE'; 50 51 constructor(private args: FlowEventsControllerArgs) { 52 super('main'); 53 54 // Create |CHROME_CUSTOME_SLICE_NAME| helper, which combines slice name 55 // and args for some slices (scheduler tasks and mojo messages) for more 56 // helpful messages. 57 // In the future, it should be replaced with this a more scalable and 58 // customisable solution. 59 // Note that a function here is significantly faster than a join. 60 this.args.engine.query(` 61 SELECT CREATE_FUNCTION( 62 'CHROME_CUSTOM_SLICE_NAME(slice_id LONG)', 63 'STRING', 64 'select case 65 when name="Receive mojo message" then 66 printf("Receive mojo message (interface=%s, hash=%s)", 67 EXTRACT_ARG(arg_set_id, 68 "chrome_mojo_event_info.mojo_interface_tag"), 69 EXTRACT_ARG(arg_set_id, "chrome_mojo_event_info.ipc_hash")) 70 when name="ThreadControllerImpl::RunTask" or 71 name="ThreadPool_RunTask" then 72 printf("RunTask(posted_from=%s:%s)", 73 EXTRACT_ARG(arg_set_id, "task.posted_from.file_name"), 74 EXTRACT_ARG(arg_set_id, "task.posted_from.function_name")) 75 end 76 from slice where id=$slice_id' 77 );`); 78 } 79 80 queryFlowEvents(query: string, callback: (flows: Flow[]) => void) { 81 this.args.engine.query(query).then((result) => { 82 const flows: Flow[] = []; 83 const it = result.iter({ 84 beginSliceId: NUM, 85 beginTrackId: NUM, 86 beginSliceName: STR_NULL, 87 beginSliceChromeCustomName: STR_NULL, 88 beginSliceCategory: STR_NULL, 89 beginSliceStartTs: NUM, 90 beginSliceEndTs: NUM, 91 beginDepth: NUM, 92 beginThreadName: STR_NULL, 93 beginProcessName: STR_NULL, 94 endSliceId: NUM, 95 endTrackId: NUM, 96 endSliceName: STR_NULL, 97 endSliceChromeCustomName: STR_NULL, 98 endSliceCategory: STR_NULL, 99 endSliceStartTs: NUM, 100 endSliceEndTs: NUM, 101 endDepth: NUM, 102 endThreadName: STR_NULL, 103 endProcessName: STR_NULL, 104 name: STR_NULL, 105 category: STR_NULL, 106 id: NUM, 107 }); 108 for (; it.valid(); it.next()) { 109 const beginSliceId = it.beginSliceId; 110 const beginTrackId = it.beginTrackId; 111 const beginSliceName = 112 it.beginSliceName === null ? 'NULL' : it.beginSliceName; 113 const beginSliceChromeCustomName = 114 it.beginSliceChromeCustomName === null ? 115 undefined : 116 it.beginSliceChromeCustomName; 117 const beginSliceCategory = 118 it.beginSliceCategory === null ? 'NULL' : it.beginSliceCategory; 119 const beginSliceStartTs = fromNs(it.beginSliceStartTs); 120 const beginSliceEndTs = fromNs(it.beginSliceEndTs); 121 const beginDepth = it.beginDepth; 122 const beginThreadName = 123 it.beginThreadName === null ? 'NULL' : it.beginThreadName; 124 const beginProcessName = 125 it.beginProcessName === null ? 'NULL' : it.beginProcessName; 126 127 const endSliceId = it.endSliceId; 128 const endTrackId = it.endTrackId; 129 const endSliceName = 130 it.endSliceName === null ? 'NULL' : it.endSliceName; 131 const endSliceChromeCustomName = it.endSliceChromeCustomName === null ? 132 undefined : 133 it.endSliceChromeCustomName; 134 const endSliceCategory = 135 it.endSliceCategory === null ? 'NULL' : it.endSliceCategory; 136 const endSliceStartTs = fromNs(it.endSliceStartTs); 137 const endSliceEndTs = fromNs(it.endSliceEndTs); 138 const endDepth = it.endDepth; 139 const endThreadName = 140 it.endThreadName === null ? 'NULL' : it.endThreadName; 141 const endProcessName = 142 it.endProcessName === null ? 'NULL' : it.endProcessName; 143 144 // Category and name present only in version 1 flow events 145 // It is most likelly NULL for all other versions 146 const category = it.category === null ? undefined : it.category; 147 const name = it.name === null ? undefined : it.name; 148 const id = it.id; 149 150 flows.push({ 151 id, 152 begin: { 153 trackId: beginTrackId, 154 sliceId: beginSliceId, 155 sliceName: beginSliceName, 156 sliceChromeCustomName: beginSliceChromeCustomName, 157 sliceCategory: beginSliceCategory, 158 sliceStartTs: beginSliceStartTs, 159 sliceEndTs: beginSliceEndTs, 160 depth: beginDepth, 161 threadName: beginThreadName, 162 processName: beginProcessName, 163 }, 164 end: { 165 trackId: endTrackId, 166 sliceId: endSliceId, 167 sliceName: endSliceName, 168 sliceChromeCustomName: endSliceChromeCustomName, 169 sliceCategory: endSliceCategory, 170 sliceStartTs: endSliceStartTs, 171 sliceEndTs: endSliceEndTs, 172 depth: endDepth, 173 threadName: endThreadName, 174 processName: endProcessName, 175 }, 176 dur: endSliceStartTs - beginSliceEndTs, 177 category, 178 name, 179 }); 180 } 181 callback(flows); 182 }); 183 } 184 185 sliceSelected(sliceId: number) { 186 if (this.lastSelectedKind === 'CHROME_SLICE' && 187 this.lastSelectedSliceId === sliceId) { 188 return; 189 } 190 this.lastSelectedSliceId = sliceId; 191 this.lastSelectedKind = 'CHROME_SLICE'; 192 193 const connectedFlows = SHOW_INDIRECT_PRECEDING_FLOWS_FLAG.get() ? 194 `( 195 select * from directly_connected_flow(${sliceId}) 196 union 197 select * from preceding_flow(${sliceId}) 198 )` : 199 `directly_connected_flow(${sliceId})`; 200 201 const query = ` 202 select 203 f.slice_out as beginSliceId, 204 t1.track_id as beginTrackId, 205 t1.name as beginSliceName, 206 CHROME_CUSTOM_SLICE_NAME(t1.slice_id) as beginSliceChromeCustomName, 207 t1.category as beginSliceCategory, 208 t1.ts as beginSliceStartTs, 209 (t1.ts+t1.dur) as beginSliceEndTs, 210 t1.depth as beginDepth, 211 (thread_out.name || ' ' || thread_out.tid) as beginThreadName, 212 (process_out.name || ' ' || process_out.pid) as beginProcessName, 213 f.slice_in as endSliceId, 214 t2.track_id as endTrackId, 215 t2.name as endSliceName, 216 CHROME_CUSTOM_SLICE_NAME(t2.slice_id) as endSliceChromeCustomName, 217 t2.category as endSliceCategory, 218 t2.ts as endSliceStartTs, 219 (t2.ts+t2.dur) as endSliceEndTs, 220 t2.depth as endDepth, 221 (thread_in.name || ' ' || thread_in.tid) as endThreadName, 222 (process_in.name || ' ' || process_in.pid) as endProcessName, 223 extract_arg(f.arg_set_id, 'cat') as category, 224 extract_arg(f.arg_set_id, 'name') as name, 225 f.id as id 226 from ${connectedFlows} f 227 join slice t1 on f.slice_out = t1.slice_id 228 join slice t2 on f.slice_in = t2.slice_id 229 left join thread_track track_out on track_out.id = t1.track_id 230 left join thread thread_out on thread_out.utid = track_out.utid 231 left join thread_track track_in on track_in.id = t2.track_id 232 left join thread thread_in on thread_in.utid = track_in.utid 233 left join process process_out on process_out.upid = thread_out.upid 234 left join process process_in on process_in.upid = thread_in.upid 235 `; 236 this.queryFlowEvents( 237 query, (flows: Flow[]) => publishConnectedFlows(flows)); 238 } 239 240 areaSelected(areaId: string) { 241 const area = globals.state.areas[areaId]; 242 if (this.lastSelectedKind === 'AREA' && this.lastSelectedArea && 243 this.lastSelectedArea.tracks.join(',') === area.tracks.join(',') && 244 this.lastSelectedArea.end === area.end && 245 this.lastSelectedArea.start === area.start) { 246 return; 247 } 248 249 this.lastSelectedArea = area; 250 this.lastSelectedKind = 'AREA'; 251 252 const trackIds: number[] = []; 253 254 for (const uiTrackId of area.tracks) { 255 const track = globals.state.tracks[uiTrackId]; 256 if (track === undefined) { 257 continue; 258 } 259 if (track.kind === SLICE_TRACK_KIND) { 260 trackIds.push((track.config as SliceConfig).trackId); 261 } else if (track.kind === ACTUAL_FRAMES_SLICE_TRACK_KIND) { 262 const actualConfig = track.config as ActualConfig; 263 for (const trackId of actualConfig.trackIds) { 264 trackIds.push(trackId); 265 } 266 } 267 } 268 269 const tracks = `(${trackIds.join(',')})`; 270 271 const startNs = area.start; 272 const endNs = area.end; 273 274 const query = ` 275 select 276 f.slice_out as beginSliceId, 277 t1.track_id as beginTrackId, 278 t1.name as beginSliceName, 279 CHROME_CUSTOM_SLICE_NAME(t1.slice_id) as beginSliceChromeCustomName, 280 t1.category as beginSliceCategory, 281 t1.ts as beginSliceStartTs, 282 (t1.ts+t1.dur) as beginSliceEndTs, 283 t1.depth as beginDepth, 284 NULL as beginThreadName, 285 NULL as beginProcessName, 286 f.slice_in as endSliceId, 287 t2.track_id as endTrackId, 288 t2.name as endSliceName, 289 CHROME_CUSTOM_SLICE_NAME(t2.slice_id) as endSliceChromeCustomName, 290 t2.category as endSliceCategory, 291 t2.ts as endSliceStartTs, 292 (t2.ts+t2.dur) as endSliceEndTs, 293 t2.depth as endDepth, 294 NULL as endThreadName, 295 NULL as endProcessName, 296 extract_arg(f.arg_set_id, 'cat') as category, 297 extract_arg(f.arg_set_id, 'name') as name, 298 f.id as id 299 from flow f 300 join slice t1 on f.slice_out = t1.slice_id 301 join slice t2 on f.slice_in = t2.slice_id 302 where 303 (t1.track_id in ${tracks} 304 and (t1.ts+t1.dur <= ${endNs} and t1.ts+t1.dur >= ${startNs})) 305 or 306 (t2.track_id in ${tracks} 307 and (t2.ts <= ${endNs} and t2.ts >= ${startNs})) 308 `; 309 this.queryFlowEvents(query, (flows: Flow[]) => publishSelectedFlows(flows)); 310 } 311 312 refreshVisibleFlows() { 313 const selection = globals.state.currentSelection; 314 if (!selection) { 315 this.lastSelectedKind = 'NONE'; 316 publishConnectedFlows([]); 317 publishSelectedFlows([]); 318 return; 319 } 320 321 // TODO(b/155483804): This is a hack as annotation slices don't contain 322 // flows. We should tidy this up when fixing this bug. 323 if (selection && selection.kind === 'CHROME_SLICE' && 324 selection.table !== 'annotation') { 325 this.sliceSelected(selection.id); 326 } else { 327 publishConnectedFlows([]); 328 } 329 330 if (selection && selection.kind === 'AREA') { 331 this.areaSelected(selection.areaId); 332 } else { 333 publishSelectedFlows([]); 334 } 335 } 336 337 run() { 338 this.refreshVisibleFlows(); 339 } 340} 341