1// Copyright (C) 2019 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 {Engine} from '../common/engine'; 16import {CurrentSearchResults, SearchSummary} from '../common/search_data'; 17import {TimeSpan} from '../common/time'; 18 19import {Controller} from './controller'; 20import {App} from './globals'; 21 22export interface SearchControllerArgs { 23 engine: Engine; 24 app: App; 25} 26 27 28export class SearchController extends Controller<'main'> { 29 private engine: Engine; 30 private app: App; 31 private previousSpan: TimeSpan; 32 private previousResolution: number; 33 private previousSearch: string; 34 private updateInProgress: boolean; 35 private setupInProgress: boolean; 36 37 constructor(args: SearchControllerArgs) { 38 super('main'); 39 this.engine = args.engine; 40 this.app = args.app; 41 this.previousSpan = new TimeSpan(0, 1); 42 this.previousSearch = ''; 43 this.updateInProgress = false; 44 this.setupInProgress = true; 45 this.previousResolution = 1; 46 this.setup().finally(() => { 47 this.setupInProgress = false; 48 this.run(); 49 }); 50 } 51 52 private async setup() { 53 await this.query(`create virtual table search_summary_window 54 using window;`); 55 await this.query(`create virtual table search_summary_sched_span using 56 span_join(sched PARTITIONED cpu, search_summary_window);`); 57 await this.query(`create virtual table search_summary_slice_span using 58 span_join(slice PARTITIONED track_id, search_summary_window);`); 59 } 60 61 run() { 62 if (this.setupInProgress || this.updateInProgress) { 63 return; 64 } 65 66 const visibleState = this.app.state.frontendLocalState.visibleState; 67 const omniboxState = this.app.state.frontendLocalState.omniboxState; 68 if (visibleState === undefined || omniboxState === undefined || 69 omniboxState.mode === 'COMMAND') { 70 return; 71 } 72 const newSpan = new TimeSpan(visibleState.startSec, visibleState.endSec); 73 const newSearch = omniboxState.omnibox; 74 const newResolution = visibleState.resolution; 75 if (this.previousSpan.contains(newSpan) && 76 this.previousResolution === newResolution && 77 newSearch === this.previousSearch) { 78 return; 79 } 80 this.previousSpan = new TimeSpan( 81 Math.max(newSpan.start - newSpan.duration, 0), 82 newSpan.end + newSpan.duration); 83 this.previousResolution = newResolution; 84 this.previousSearch = newSearch; 85 if (newSearch === '' || newSearch.length < 4) { 86 this.app.publish('Search', { 87 tsStarts: new Float64Array(0), 88 tsEnds: new Float64Array(0), 89 count: new Uint8Array(0), 90 }); 91 this.app.publish('SearchResult', { 92 sliceIds: new Float64Array(0), 93 tsStarts: new Float64Array(0), 94 utids: new Float64Array(0), 95 sources: [], 96 trackIds: [], 97 totalResults: 0, 98 }); 99 return; 100 } 101 102 const startNs = Math.round(newSpan.start * 1e9); 103 const endNs = Math.round(newSpan.end * 1e9); 104 this.updateInProgress = true; 105 const computeSummary = 106 this.update(newSearch, startNs, endNs, newResolution).then(summary => { 107 this.app.publish('Search', summary); 108 }); 109 110 const computeResults = 111 this.specificSearch(newSearch).then(searchResults => { 112 this.app.publish('SearchResult', searchResults); 113 }); 114 115 Promise.all([computeSummary, computeResults]) 116 .finally(() => { 117 this.updateInProgress = false; 118 this.run(); 119 }); 120 } 121 122 onDestroy() {} 123 124 private async update( 125 search: string, startNs: number, endNs: number, 126 resolution: number): Promise<SearchSummary> { 127 const quantumNs = Math.round(resolution * 10 * 1e9); 128 129 startNs = Math.floor(startNs / quantumNs) * quantumNs; 130 131 await this.query(`update search_summary_window set 132 window_start=${startNs}, 133 window_dur=${endNs - startNs}, 134 quantum=${quantumNs} 135 where rowid = 0;`); 136 137 const rawUtidResult = await this.query(`select utid from thread join process 138 using(upid) where thread.name like "%${search}%" or process.name like "%${ 139 search}%"`); 140 141 const utids = [...rawUtidResult.columns[0].longValues!]; 142 143 const cpus = await this.engine.getCpus(); 144 const maxCpu = Math.max(...cpus, -1); 145 146 const rawResult = await this.query(` 147 select 148 (quantum_ts * ${quantumNs} + ${startNs})/1e9 as tsStart, 149 ((quantum_ts+1) * ${quantumNs} + ${startNs})/1e9 as tsEnd, 150 min(count(*), 255) as count 151 from ( 152 select 153 quantum_ts 154 from search_summary_sched_span 155 where utid in (${utids.join(',')}) and cpu <= ${maxCpu} 156 union all 157 select 158 quantum_ts 159 from search_summary_slice_span 160 where name like '%${search}%' 161 ) 162 group by quantum_ts 163 order by quantum_ts;`); 164 165 const numRows = +rawResult.numRecords; 166 const summary = { 167 tsStarts: new Float64Array(numRows), 168 tsEnds: new Float64Array(numRows), 169 count: new Uint8Array(numRows) 170 }; 171 172 const columns = rawResult.columns; 173 for (let row = 0; row < numRows; row++) { 174 summary.tsStarts[row] = +columns[0].doubleValues![row]; 175 summary.tsEnds[row] = +columns[1].doubleValues![row]; 176 summary.count[row] = +columns[2].longValues![row]; 177 } 178 return summary; 179 } 180 181 private async specificSearch(search: string) { 182 // TODO(hjd): we should avoid recomputing this every time. This will be 183 // easier once the track table has entries for all the tracks. 184 const cpuToTrackId = new Map(); 185 const engineTrackIdToTrackId = new Map(); 186 for (const track of Object.values(this.app.state.tracks)) { 187 if (track.kind === 'CpuSliceTrack') { 188 cpuToTrackId.set((track.config as {cpu: number}).cpu, track.id); 189 continue; 190 } 191 if (track.kind === 'ChromeSliceTrack' || 192 track.kind === 'AsyncSliceTrack') { 193 engineTrackIdToTrackId.set( 194 (track.config as {trackId: number}).trackId, track.id); 195 continue; 196 } 197 } 198 199 const rawUtidResult = await this.query(`select utid from thread join process 200 using(upid) where thread.name like "%${search}%" or process.name like "%${ 201 search}%"`); 202 const utids = [...rawUtidResult.columns[0].longValues!]; 203 204 const rawResult = await this.query(` 205 select 206 id as slice_id, 207 ts, 208 'cpu' as source, 209 cpu as source_id, 210 utid 211 from sched where utid in (${utids.join(',')}) 212 union 213 select 214 slice_id, 215 ts, 216 'track' as source, 217 track_id as source_id, 218 0 as utid 219 from slice 220 inner join track on slice.track_id = track.id 221 and slice.name like '%${search}%' 222 order by ts`); 223 224 const numRows = +rawResult.numRecords; 225 226 const searchResults: CurrentSearchResults = { 227 sliceIds: new Float64Array(numRows), 228 tsStarts: new Float64Array(numRows), 229 utids: new Float64Array(numRows), 230 trackIds: [], 231 sources: [], 232 totalResults: +numRows, 233 }; 234 235 const columns = rawResult.columns; 236 for (let row = 0; row < numRows; row++) { 237 const source = columns[2].stringValues![row]; 238 const sourceId = +columns[3].longValues![row]; 239 let trackId = undefined; 240 if (source === 'cpu') { 241 trackId = cpuToTrackId.get(sourceId); 242 } else if (source === 'track') { 243 trackId = engineTrackIdToTrackId.get(sourceId); 244 } 245 246 if (trackId === undefined) { 247 searchResults.totalResults--; 248 continue; 249 } 250 251 searchResults.trackIds.push(trackId); 252 searchResults.sources.push(source); 253 searchResults.sliceIds[row] = +columns[0].longValues![row]; 254 searchResults.tsStarts[row] = +columns[1].longValues![row]; 255 searchResults.utids[row] = +columns[4].longValues![row]; 256 } 257 return searchResults; 258 } 259 260 261 private async query(query: string) { 262 const result = await this.engine.query(query); 263 return result; 264 } 265} 266