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, toNs} from '../common/time'; 20import {Flow} 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/common'; 26import { 27 Config as SliceConfig, 28 SLICE_TRACK_KIND 29} from '../tracks/chrome_slices/common'; 30 31import {Controller} from './controller'; 32import {globals} from './globals'; 33 34export interface FlowEventsControllerArgs { 35 engine: Engine; 36} 37 38const SHOW_INDIRECT_PRECEDING_FLOWS_FLAG = featureFlags.register({ 39 id: 'showIndirectPrecedingFlows', 40 name: 'Show indirect preceding flows', 41 description: 'Show indirect preceding flows (connected through ancestor ' + 42 'slices) when a slice is selected.', 43 defaultValue: false, 44}); 45 46 47export class FlowEventsController extends Controller<'main'> { 48 private lastSelectedSliceId?: number; 49 private lastSelectedArea?: Area; 50 private lastSelectedKind: 'CHROME_SLICE'|'AREA'|'NONE' = 'NONE'; 51 52 constructor(private args: FlowEventsControllerArgs) { 53 super('main'); 54 } 55 56 queryFlowEvents(query: string, callback: (flows: Flow[]) => void) { 57 this.args.engine.query(query).then(result => { 58 const flows: Flow[] = []; 59 const it = result.iter({ 60 beginSliceId: NUM, 61 beginTrackId: NUM, 62 beginSliceName: STR_NULL, 63 beginSliceCategory: STR_NULL, 64 beginSliceStartTs: NUM, 65 beginSliceEndTs: NUM, 66 beginDepth: NUM, 67 beginThreadName: STR_NULL, 68 beginProcessName: STR_NULL, 69 endSliceId: NUM, 70 endTrackId: NUM, 71 endSliceName: STR_NULL, 72 endSliceCategory: STR_NULL, 73 endSliceStartTs: NUM, 74 endSliceEndTs: NUM, 75 endDepth: NUM, 76 endThreadName: STR_NULL, 77 endProcessName: STR_NULL, 78 name: STR_NULL, 79 category: STR_NULL, 80 id: NUM, 81 }); 82 for (; it.valid(); it.next()) { 83 const beginSliceId = it.beginSliceId; 84 const beginTrackId = it.beginTrackId; 85 const beginSliceName = 86 it.beginSliceName === null ? 'NULL' : it.beginSliceName; 87 const beginSliceCategory = 88 it.beginSliceCategory === null ? 'NULL' : it.beginSliceCategory; 89 const beginSliceStartTs = fromNs(it.beginSliceStartTs); 90 const beginSliceEndTs = fromNs(it.beginSliceEndTs); 91 const beginDepth = it.beginDepth; 92 const beginThreadName = 93 it.beginThreadName === null ? 'NULL' : it.beginThreadName; 94 const beginProcessName = 95 it.beginProcessName === null ? 'NULL' : it.beginProcessName; 96 97 const endSliceId = it.endSliceId; 98 const endTrackId = it.endTrackId; 99 const endSliceName = 100 it.endSliceName === null ? 'NULL' : it.endSliceName; 101 const endSliceCategory = 102 it.endSliceCategory === null ? 'NULL' : it.endSliceCategory; 103 const endSliceStartTs = fromNs(it.endSliceStartTs); 104 const endSliceEndTs = fromNs(it.endSliceEndTs); 105 const endDepth = it.endDepth; 106 const endThreadName = 107 it.endThreadName === null ? 'NULL' : it.endThreadName; 108 const endProcessName = 109 it.endProcessName === null ? 'NULL' : it.endProcessName; 110 111 // Category and name present only in version 1 flow events 112 // It is most likelly NULL for all other versions 113 const category = it.category === null ? undefined : it.category; 114 const name = it.name === null ? undefined : it.name; 115 const id = it.id; 116 117 flows.push({ 118 id, 119 begin: { 120 trackId: beginTrackId, 121 sliceId: beginSliceId, 122 sliceName: beginSliceName, 123 sliceCategory: beginSliceCategory, 124 sliceStartTs: beginSliceStartTs, 125 sliceEndTs: beginSliceEndTs, 126 depth: beginDepth, 127 threadName: beginThreadName, 128 processName: beginProcessName 129 }, 130 end: { 131 trackId: endTrackId, 132 sliceId: endSliceId, 133 sliceName: endSliceName, 134 sliceCategory: endSliceCategory, 135 sliceStartTs: endSliceStartTs, 136 sliceEndTs: endSliceEndTs, 137 depth: endDepth, 138 threadName: endThreadName, 139 processName: endProcessName 140 }, 141 dur: endSliceStartTs - beginSliceEndTs, 142 category, 143 name 144 }); 145 } 146 callback(flows); 147 }); 148 } 149 150 sliceSelected(sliceId: number) { 151 if (this.lastSelectedKind === 'CHROME_SLICE' && 152 this.lastSelectedSliceId === sliceId) { 153 return; 154 } 155 this.lastSelectedSliceId = sliceId; 156 this.lastSelectedKind = 'CHROME_SLICE'; 157 158 const connectedFlows = SHOW_INDIRECT_PRECEDING_FLOWS_FLAG.get() ? 159 `( 160 select * from directly_connected_flow(${sliceId}) 161 union 162 select * from preceding_flow(${sliceId}) 163 )` : 164 `directly_connected_flow(${sliceId})`; 165 166 const query = ` 167 select 168 f.slice_out as beginSliceId, 169 t1.track_id as beginTrackId, 170 t1.name as beginSliceName, 171 t1.category as beginSliceCategory, 172 t1.ts as beginSliceStartTs, 173 (t1.ts+t1.dur) as beginSliceEndTs, 174 t1.depth as beginDepth, 175 (thread_out.name || ' ' || thread_out.tid) as beginThreadName, 176 (process_out.name || ' ' || process_out.pid) as beginProcessName, 177 f.slice_in as endSliceId, 178 t2.track_id as endTrackId, 179 t2.name as endSliceName, 180 t2.category as endSliceCategory, 181 t2.ts as endSliceStartTs, 182 (t2.ts+t2.dur) as endSliceEndTs, 183 t2.depth as endDepth, 184 (thread_in.name || ' ' || thread_in.tid) as endThreadName, 185 (process_in.name || ' ' || process_in.pid) as endProcessName, 186 extract_arg(f.arg_set_id, 'cat') as category, 187 extract_arg(f.arg_set_id, 'name') as name, 188 f.id as id 189 from ${connectedFlows} f 190 join slice t1 on f.slice_out = t1.slice_id 191 join slice t2 on f.slice_in = t2.slice_id 192 left join thread_track track_out on track_out.id = t1.track_id 193 left join thread thread_out on thread_out.utid = track_out.utid 194 left join thread_track track_in on track_in.id = t2.track_id 195 left join thread thread_in on thread_in.utid = track_in.utid 196 left join process process_out on process_out.upid = thread_out.upid 197 left join process process_in on process_in.upid = thread_in.upid 198 `; 199 this.queryFlowEvents( 200 query, (flows: Flow[]) => publishConnectedFlows(flows)); 201 } 202 203 areaSelected(areaId: string) { 204 const area = globals.state.areas[areaId]; 205 if (this.lastSelectedKind === 'AREA' && this.lastSelectedArea && 206 this.lastSelectedArea.tracks.join(',') === area.tracks.join(',') && 207 this.lastSelectedArea.endSec === area.endSec && 208 this.lastSelectedArea.startSec === area.startSec) { 209 return; 210 } 211 212 this.lastSelectedArea = area; 213 this.lastSelectedKind = 'AREA'; 214 215 const trackIds: number[] = []; 216 217 for (const uiTrackId of area.tracks) { 218 const track = globals.state.tracks[uiTrackId]; 219 if (track === undefined) { 220 continue; 221 } 222 if (track.kind === SLICE_TRACK_KIND) { 223 trackIds.push((track.config as SliceConfig).trackId); 224 } else if (track.kind === ACTUAL_FRAMES_SLICE_TRACK_KIND) { 225 const actualConfig = track.config as ActualConfig; 226 for (const trackId of actualConfig.trackIds) { 227 trackIds.push(trackId); 228 } 229 } 230 } 231 232 const tracks = `(${trackIds.join(',')})`; 233 234 const startNs = toNs(area.startSec); 235 const endNs = toNs(area.endSec); 236 237 const query = ` 238 select 239 f.slice_out as beginSliceId, 240 t1.track_id as beginTrackId, 241 t1.name as beginSliceName, 242 t1.category as beginSliceCategory, 243 t1.ts as beginSliceStartTs, 244 (t1.ts+t1.dur) as beginSliceEndTs, 245 t1.depth as beginDepth, 246 NULL as beginThreadName, 247 NULL as beginProcessName, 248 f.slice_in as endSliceId, 249 t2.track_id as endTrackId, 250 t2.name as endSliceName, 251 t2.category as endSliceCategory, 252 t2.ts as endSliceStartTs, 253 (t2.ts+t2.dur) as endSliceEndTs, 254 t2.depth as endDepth, 255 NULL as endThreadName, 256 NULL as endProcessName, 257 extract_arg(f.arg_set_id, 'cat') as category, 258 extract_arg(f.arg_set_id, 'name') as name, 259 f.id as id 260 from flow f 261 join slice t1 on f.slice_out = t1.slice_id 262 join slice t2 on f.slice_in = t2.slice_id 263 where 264 (t1.track_id in ${tracks} 265 and (t1.ts+t1.dur <= ${endNs} and t1.ts+t1.dur >= ${startNs})) 266 or 267 (t2.track_id in ${tracks} 268 and (t2.ts <= ${endNs} and t2.ts >= ${startNs})) 269 `; 270 this.queryFlowEvents(query, (flows: Flow[]) => publishSelectedFlows(flows)); 271 } 272 273 refreshVisibleFlows() { 274 const selection = globals.state.currentSelection; 275 if (!selection) { 276 this.lastSelectedKind = 'NONE'; 277 publishConnectedFlows([]); 278 publishSelectedFlows([]); 279 return; 280 } 281 282 // TODO(b/155483804): This is a hack as annotation slices don't contain 283 // flows. We should tidy this up when fixing this bug. 284 if (selection && selection.kind === 'CHROME_SLICE' && 285 selection.table !== 'annotation') { 286 this.sliceSelected(selection.id); 287 } else { 288 publishConnectedFlows([]); 289 } 290 291 if (selection && selection.kind === 'AREA') { 292 this.areaSelected(selection.areaId); 293 } else { 294 publishSelectedFlows([]); 295 } 296 } 297 298 run() { 299 this.refreshVisibleFlows(); 300 } 301} 302