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