1 // Copyright (C) 2018 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 15 import {fromNs} from '../../common/time'; 16 import { 17 TrackController, 18 trackControllerRegistry 19 } from '../../controller/track_controller'; 20 21 import {Config, Data, SLICE_TRACK_KIND} from './common'; 22 23 class ChromeSliceTrackController extends TrackController<Config, Data> { 24 static readonly kind = SLICE_TRACK_KIND; 25 private busy = false; 26 private setup = false; 27 28 onBoundsChange(start: number, end: number, resolution: number): void { 29 this.update(start, end, resolution); 30 } 31 32 private async update(start: number, end: number, resolution: number) { 33 // TODO: we should really call TraceProcessor.Interrupt() at this point. 34 if (this.busy) return; 35 36 const startNs = Math.round(start * 1e9); 37 const endNs = Math.round(end * 1e9); 38 // Ns in 1px width. We want all slices smaller than 1px to be grouped. 39 const minNs = Math.round(resolution * 1e9); 40 const LIMIT = 10000; 41 this.busy = true; 42 43 if (!this.setup) { 44 await this.query( 45 `create virtual table ${this.tableName('window')} using window;`); 46 47 await this.query( 48 `create view ${this.tableName('small')} as ` + 49 `select ts,dur,depth,cat,name from slices ` + 50 `where utid = ${this.config.utid} ` + 51 `and ts >= ${startNs} - dur ` + 52 `and ts <= ${endNs} ` + 53 `and dur < ${minNs} ` + 54 `order by ts ` + 55 `limit ${LIMIT};`); 56 57 await this.query(`create virtual table ${this.tableName('span')} using 58 span_join(${this.tableName('small')}, 59 ${this.tableName('window')});`); 60 61 this.setup = true; 62 } 63 64 const windowDurNs = Math.max(1, endNs - startNs); 65 66 this.query(`update ${this.tableName('window')} set 67 window_start=${startNs}, 68 window_dur=${windowDurNs}, 69 quantum=${minNs}`); 70 71 await this.query(`drop view if exists ${this.tableName('small')}`); 72 await this.query(`drop view if exists ${this.tableName('big')}`); 73 await this.query(`drop view if exists ${this.tableName('summary')}`); 74 75 await this.query( 76 `create view ${this.tableName('small')} as ` + 77 `select ts,dur,depth,cat,name from slices ` + 78 `where utid = ${this.config.utid} ` + 79 `and ts >= ${startNs} - dur ` + 80 `and ts <= ${endNs} ` + 81 `and dur < ${minNs} ` + 82 `order by ts `); 83 84 await this.query( 85 `create view ${this.tableName('big')} as ` + 86 `select ts,dur,depth,cat,name from slices ` + 87 `where utid = ${this.config.utid} ` + 88 `and ts >= ${startNs} - dur ` + 89 `and ts <= ${endNs} ` + 90 `and dur >= ${minNs} ` + 91 `order by ts `); 92 93 await this.query(`create view ${this.tableName('summary')} as select 94 min(ts) as ts, 95 ${minNs} as dur, 96 depth, 97 cat, 98 'Busy' as name 99 from ${this.tableName('span')} 100 group by cat, depth, quantum_ts 101 limit ${LIMIT};`); 102 103 const query = `select * from ${this.tableName('summary')} UNION ` + 104 `select * from ${this.tableName('big')} order by ts`; 105 106 const rawResult = await this.query(query); 107 this.busy = false; 108 109 if (rawResult.error) { 110 throw new Error(`Query error "${query}": ${rawResult.error}`); 111 } 112 113 const numRows = +rawResult.numRecords; 114 115 const slices: Data = { 116 start, 117 end, 118 resolution, 119 strings: [], 120 starts: new Float64Array(numRows), 121 ends: new Float64Array(numRows), 122 depths: new Uint16Array(numRows), 123 titles: new Uint16Array(numRows), 124 categories: new Uint16Array(numRows), 125 }; 126 127 const stringIndexes = new Map<string, number>(); 128 function internString(str: string) { 129 let idx = stringIndexes.get(str); 130 if (idx !== undefined) return idx; 131 idx = slices.strings.length; 132 slices.strings.push(str); 133 stringIndexes.set(str, idx); 134 return idx; 135 } 136 137 for (let row = 0; row < numRows; row++) { 138 const cols = rawResult.columns; 139 const startSec = fromNs(+cols[0].longValues![row]); 140 slices.starts[row] = startSec; 141 slices.ends[row] = startSec + fromNs(+cols[1].longValues![row]); 142 slices.depths[row] = +cols[2].longValues![row]; 143 slices.categories[row] = internString(cols[3].stringValues![row]); 144 slices.titles[row] = internString(cols[4].stringValues![row]); 145 } 146 if (numRows === LIMIT) { 147 slices.end = slices.ends[slices.ends.length - 1]; 148 } 149 this.publish(slices); 150 } 151 152 private async query(query: string) { 153 const result = await this.engine.query(query); 154 if (result.error) { 155 console.error(`Query error "${query}": ${result.error}`); 156 throw new Error(`Query error "${query}": ${result.error}`); 157 } 158 return result; 159 } 160 } 161 162 163 trackControllerRegistry.register(ChromeSliceTrackController); 164