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 { 22 Config, 23 CPU_SLICE_TRACK_KIND, 24 Data, 25 SliceData, 26 SummaryData 27 } from './common'; 28 29 class CpuSliceTrackController extends TrackController<Config, Data> { 30 static readonly kind = CPU_SLICE_TRACK_KIND; 31 private busy = false; 32 private setup = false; 33 34 onBoundsChange(start: number, end: number, resolution: number): void { 35 this.update(start, end, resolution); 36 } 37 38 private async update(start: number, end: number, resolution: number): 39 Promise<void> { 40 // TODO: we should really call TraceProcessor.Interrupt() at this point. 41 if (this.busy) return; 42 43 const startNs = Math.round(start * 1e9); 44 const endNs = Math.round(end * 1e9); 45 46 this.busy = true; 47 if (this.setup === false) { 48 await this.query( 49 `create virtual table ${this.tableName('window')} using window;`); 50 await this.query(`create virtual table ${this.tableName('span')} 51 using span_join(sched PARTITIONED cpu, 52 ${this.tableName('window')});`); 53 this.setup = true; 54 } 55 56 const isQuantized = this.shouldSummarize(resolution); 57 // |resolution| is in s/px we want # ns for 10px window: 58 const bucketSizeNs = Math.round(resolution * 10 * 1e9); 59 let windowStartNs = startNs; 60 if (isQuantized) { 61 windowStartNs = Math.floor(windowStartNs / bucketSizeNs) * bucketSizeNs; 62 } 63 const windowDurNs = Math.max(1, endNs - windowStartNs); 64 65 this.query(`update ${this.tableName('window')} set 66 window_start=${windowStartNs}, 67 window_dur=${windowDurNs}, 68 quantum=${isQuantized ? bucketSizeNs : 0} 69 where rowid = 0;`); 70 71 if (isQuantized) { 72 this.publish(await this.computeSummary( 73 fromNs(windowStartNs), end, resolution, bucketSizeNs)); 74 } else { 75 this.publish( 76 await this.computeSlices(fromNs(windowStartNs), end, resolution)); 77 } 78 this.busy = false; 79 } 80 81 private async computeSummary( 82 start: number, end: number, resolution: number, 83 bucketSizeNs: number): Promise<SummaryData> { 84 const startNs = Math.round(start * 1e9); 85 const endNs = Math.round(end * 1e9); 86 const numBuckets = Math.ceil((endNs - startNs) / bucketSizeNs); 87 88 const query = `select 89 quantum_ts as bucket, 90 sum(dur)/cast(${bucketSizeNs} as float) as utilization 91 from ${this.tableName('span')} 92 where cpu = ${this.config.cpu} 93 and utid != 0 94 group by quantum_ts`; 95 96 const rawResult = await this.query(query); 97 const numRows = +rawResult.numRecords; 98 99 const summary: Data = { 100 kind: 'summary', 101 start, 102 end, 103 resolution, 104 bucketSizeSeconds: fromNs(bucketSizeNs), 105 utilizations: new Float64Array(numBuckets), 106 }; 107 const cols = rawResult.columns; 108 for (let row = 0; row < numRows; row++) { 109 const bucket = +cols[0].longValues![row]; 110 summary.utilizations[bucket] = +cols[1].doubleValues![row]; 111 } 112 return summary; 113 } 114 115 private async computeSlices(start: number, end: number, resolution: number): 116 Promise<SliceData> { 117 // TODO(hjd): Remove LIMIT 118 const LIMIT = 10000; 119 120 const query = `select ts,dur,utid,row_id from ${this.tableName('span')} 121 where cpu = ${this.config.cpu} 122 and utid != 0 123 limit ${LIMIT};`; 124 const rawResult = await this.query(query); 125 126 const numRows = +rawResult.numRecords; 127 const slices: SliceData = { 128 kind: 'slice', 129 start, 130 end, 131 resolution, 132 ids: new Float64Array(numRows), 133 starts: new Float64Array(numRows), 134 ends: new Float64Array(numRows), 135 utids: new Uint32Array(numRows), 136 }; 137 138 const cols = rawResult.columns; 139 for (let row = 0; row < numRows; row++) { 140 const startSec = fromNs(+cols[0].longValues![row]); 141 slices.starts[row] = startSec; 142 slices.ends[row] = startSec + fromNs(+cols[1].longValues![row]); 143 slices.utids[row] = +cols[2].longValues![row]; 144 slices.ids[row] = +cols[3].longValues![row]; 145 } 146 if (numRows === LIMIT) { 147 slices.end = slices.ends[slices.ends.length - 1]; 148 } 149 return 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 onDestroy(): void { 162 if (this.setup) { 163 this.query(`drop table ${this.tableName('window')}`); 164 this.query(`drop table ${this.tableName('span')}`); 165 this.setup = false; 166 } 167 } 168 } 169 170 trackControllerRegistry.register(CpuSliceTrackController); 171