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 {slowlyCountRows} from '../common/query_iterator'; 17import {Area} from '../common/state'; 18import {fromNs, toNs} from '../common/time'; 19import {Flow} from '../frontend/globals'; 20import { 21 ACTUAL_FRAMES_SLICE_TRACK_KIND, 22 Config as ActualConfig 23} from '../tracks/actual_frames/common'; 24import { 25 Config as SliceConfig, 26 SLICE_TRACK_KIND 27} from '../tracks/chrome_slices/common'; 28 29import {Controller} from './controller'; 30import {globals} from './globals'; 31 32export interface FlowEventsControllerArgs { 33 engine: Engine; 34} 35 36export class FlowEventsController extends Controller<'main'> { 37 private lastSelectedSliceId?: number; 38 private lastSelectedArea?: Area; 39 private lastSelectedKind: 'CHROME_SLICE'|'AREA'|'NONE' = 'NONE'; 40 41 constructor(private args: FlowEventsControllerArgs) { 42 super('main'); 43 } 44 45 queryFlowEvents(query: string, callback: (flows: Flow[]) => void) { 46 this.args.engine.query(query).then(res => { 47 const flows: Flow[] = []; 48 for (let i = 0; i < slowlyCountRows(res); i++) { 49 const beginSliceId = res.columns[0].longValues![i]; 50 const beginTrackId = res.columns[1].longValues![i]; 51 const beginSliceName = res.columns[2].stringValues![i]; 52 const beginSliceCategory = res.columns[3].stringValues![i]; 53 const beginSliceStartTs = fromNs(res.columns[4].longValues![i]); 54 const beginSliceEndTs = fromNs(res.columns[5].longValues![i]); 55 const beginDepth = res.columns[6].longValues![i]; 56 57 const endSliceId = res.columns[7].longValues![i]; 58 const endTrackId = res.columns[8].longValues![i]; 59 const endSliceName = res.columns[9].stringValues![i]; 60 const endSliceCategory = res.columns[10].stringValues![i]; 61 const endSliceStartTs = fromNs(res.columns[11].longValues![i]); 62 const endSliceEndTs = fromNs(res.columns[12].longValues![i]); 63 const endDepth = res.columns[13].longValues![i]; 64 65 // Category and name present only in version 1 flow events 66 // It is most likelly NULL for all other versions 67 const category = res.columns[14].isNulls![i] ? 68 undefined : 69 res.columns[14].stringValues![i]; 70 const name = res.columns[15].isNulls![i] ? 71 undefined : 72 res.columns[15].stringValues![i]; 73 const id = res.columns[16].longValues![i]; 74 75 flows.push({ 76 id, 77 begin: { 78 trackId: beginTrackId, 79 sliceId: beginSliceId, 80 sliceName: beginSliceName, 81 sliceCategory: beginSliceCategory, 82 sliceStartTs: beginSliceStartTs, 83 sliceEndTs: beginSliceEndTs, 84 depth: beginDepth 85 }, 86 end: { 87 trackId: endTrackId, 88 sliceId: endSliceId, 89 sliceName: endSliceName, 90 sliceCategory: endSliceCategory, 91 sliceStartTs: endSliceStartTs, 92 sliceEndTs: endSliceEndTs, 93 depth: endDepth 94 }, 95 category, 96 name 97 }); 98 } 99 callback(flows); 100 }); 101 } 102 103 sliceSelected(sliceId: number) { 104 if (this.lastSelectedKind === 'CHROME_SLICE' && 105 this.lastSelectedSliceId === sliceId) { 106 return; 107 } 108 this.lastSelectedSliceId = sliceId; 109 this.lastSelectedKind = 'CHROME_SLICE'; 110 111 const query = ` 112 select 113 f.slice_out, t1.track_id, t1.name, 114 t1.category, t1.ts, (t1.ts+t1.dur), t1.depth, 115 f.slice_in, t2.track_id, t2.name, 116 t2.category, t2.ts, (t2.ts+t2.dur), t2.depth, 117 extract_arg(f.arg_set_id, 'cat'), 118 extract_arg(f.arg_set_id, 'name'), 119 f.id 120 from directly_connected_flow(${sliceId}) f 121 join slice t1 on f.slice_out = t1.slice_id 122 join slice t2 on f.slice_in = t2.slice_id 123 `; 124 this.queryFlowEvents( 125 query, (flows: Flow[]) => globals.publish('ConnectedFlows', flows)); 126 } 127 128 areaSelected(areaId: string) { 129 const area = globals.state.areas[areaId]; 130 if (this.lastSelectedKind === 'AREA' && this.lastSelectedArea && 131 this.lastSelectedArea.tracks.join(',') === area.tracks.join(',') && 132 this.lastSelectedArea.endSec === area.endSec && 133 this.lastSelectedArea.startSec === area.startSec) { 134 return; 135 } 136 137 this.lastSelectedArea = area; 138 this.lastSelectedKind = 'AREA'; 139 140 const trackIds: number[] = []; 141 142 for (const uiTrackId of area.tracks) { 143 const track = globals.state.tracks[uiTrackId]; 144 if (track === undefined) { 145 continue; 146 } 147 if (track.kind === SLICE_TRACK_KIND) { 148 trackIds.push((track.config as SliceConfig).trackId); 149 } else if (track.kind === ACTUAL_FRAMES_SLICE_TRACK_KIND) { 150 const actualConfig = track.config as ActualConfig; 151 for (const trackId of actualConfig.trackIds) { 152 trackIds.push(trackId); 153 } 154 } 155 } 156 157 const tracks = `(${trackIds.join(',')})`; 158 159 const startNs = toNs(area.startSec); 160 const endNs = toNs(area.endSec); 161 162 const query = ` 163 select 164 f.slice_out, t1.track_id, t1.name, 165 t1.category, t1.ts, (t1.ts+t1.dur), t1.depth, 166 f.slice_in, t2.track_id, t2.name, 167 t2.category, t2.ts, (t2.ts+t2.dur), t2.depth, 168 extract_arg(f.arg_set_id, 'cat'), 169 extract_arg(f.arg_set_id, 'name'), 170 f.id 171 from flow f 172 join slice t1 on f.slice_out = t1.slice_id 173 join slice t2 on f.slice_in = t2.slice_id 174 where 175 (t1.track_id in ${tracks} 176 and (t1.ts+t1.dur <= ${endNs} and t1.ts+t1.dur >= ${startNs})) 177 or 178 (t2.track_id in ${tracks} 179 and (t2.ts <= ${endNs} and t2.ts >= ${startNs})) 180 `; 181 this.queryFlowEvents( 182 query, (flows: Flow[]) => globals.publish('SelectedFlows', flows)); 183 } 184 185 refreshVisibleFlows() { 186 const selection = globals.state.currentSelection; 187 if (!selection) { 188 this.lastSelectedKind = 'NONE'; 189 globals.publish('ConnectedFlows', []); 190 globals.publish('SelectedFlows', []); 191 return; 192 } 193 194 if (selection && selection.kind === 'CHROME_SLICE') { 195 this.sliceSelected(selection.id); 196 } else { 197 globals.publish('ConnectedFlows', []); 198 } 199 200 if (selection && selection.kind === 'AREA') { 201 this.areaSelected(selection.areaId); 202 } else { 203 globals.publish('SelectedFlows', []); 204 } 205 } 206 207 run() { 208 this.refreshVisibleFlows(); 209 } 210} 211