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 {fromNs} from '../../common/time'; 16import { 17 TrackController, 18 trackControllerRegistry 19} from '../../controller/track_controller'; 20 21import { 22 Config, 23 Data, 24 PROCESS_SCHEDULING_TRACK_KIND, 25 SliceData, 26 SummaryData 27} from './common'; 28 29class ProcessSchedulingTrackController extends TrackController<Config, Data> { 30 static readonly kind = PROCESS_SCHEDULING_TRACK_KIND; 31 private busy = false; 32 private setup = false; 33 private numCpus = 0; 34 35 onBoundsChange(start: number, end: number, resolution: number): void { 36 this.update(start, end, resolution); 37 } 38 39 private async update(start: number, end: number, resolution: number): 40 Promise<void> { 41 // TODO: we should really call TraceProcessor.Interrupt() at this point. 42 if (this.busy) return; 43 44 if (!this.config.upid) { 45 return; 46 } 47 48 const startNs = Math.round(start * 1e9); 49 const endNs = Math.round(end * 1e9); 50 51 this.busy = true; 52 if (this.setup === false) { 53 await this.query( 54 `create virtual table ${this.tableName('window')} using window;`); 55 await this.query(`create view ${this.tableName('process')} as 56 select ts, dur, cpu, utid, upid from sched join ( 57 select utid, upid from thread 58 where utid != 0 and upid = ${this.config.upid}) using(utid);`); 59 await this.query(`create virtual table ${this.tableName('span')} 60 using span_join(${this.tableName('process')} PARTITIONED cpu, 61 ${this.tableName('window')});`); 62 this.numCpus = await this.engine.getNumberOfCpus(); 63 this.setup = true; 64 } 65 66 const isQuantized = this.shouldSummarize(resolution); 67 // |resolution| is in s/px we want # ns for 20px window: 68 const bucketSizeNs = Math.round(resolution * 10 * 1e9 * 1.5); 69 let windowStartNs = startNs; 70 if (isQuantized) { 71 windowStartNs = Math.floor(windowStartNs / bucketSizeNs) * bucketSizeNs; 72 } 73 const windowDurNs = Math.max(1, endNs - windowStartNs); 74 75 this.query(`update ${this.tableName('window')} set 76 window_start=${windowStartNs}, 77 window_dur=${windowDurNs}, 78 quantum=${isQuantized ? bucketSizeNs : 0} 79 where rowid = 0;`); 80 81 if (isQuantized) { 82 this.publish(await this.computeSummary( 83 fromNs(windowStartNs), end, resolution, bucketSizeNs)); 84 } else { 85 this.publish( 86 await this.computeSlices(fromNs(windowStartNs), end, resolution)); 87 } 88 this.busy = false; 89 } 90 91 private async computeSummary( 92 start: number, end: number, resolution: number, 93 bucketSizeNs: number): Promise<SummaryData> { 94 const startNs = Math.round(start * 1e9); 95 const endNs = Math.round(end * 1e9); 96 const numBuckets = Math.ceil((endNs - startNs) / bucketSizeNs); 97 98 // cpu < numCpus improves perfomance a lot since the window table can 99 // avoid generating many rows. 100 const query = `select 101 quantum_ts as bucket, 102 sum(dur)/cast(${bucketSizeNs * this.numCpus} as float) as utilization 103 from ${this.tableName('span')} 104 where upid = ${this.config.upid} 105 and utid != 0 106 and cpu < ${this.numCpus} 107 group by quantum_ts`; 108 109 const rawResult = await this.query(query); 110 const numRows = +rawResult.numRecords; 111 112 const summary: Data = { 113 kind: 'summary', 114 start, 115 end, 116 resolution, 117 bucketSizeSeconds: fromNs(bucketSizeNs), 118 utilizations: new Float64Array(numBuckets), 119 }; 120 const cols = rawResult.columns; 121 for (let row = 0; row < numRows; row++) { 122 const bucket = +cols[0].longValues![row]; 123 summary.utilizations[bucket] = +cols[1].doubleValues![row]; 124 } 125 return summary; 126 } 127 128 private async computeSlices(start: number, end: number, resolution: number): 129 Promise<SliceData> { 130 // TODO(hjd): Remove LIMIT 131 const LIMIT = 10000; 132 133 // cpu < numCpus improves perfomance a lot since the window table can 134 // avoid generating many rows. 135 const query = `select ts,dur,cpu,utid from ${this.tableName('span')} 136 join 137 (select utid from thread where upid = ${this.config.upid}) 138 using(utid) 139 where 140 cpu < ${this.numCpus} 141 order by 142 cpu, 143 ts 144 limit ${LIMIT};`; 145 const rawResult = await this.query(query); 146 147 const numRows = +rawResult.numRecords; 148 if (numRows === LIMIT) { 149 console.warn(`More than ${LIMIT} rows returned.`); 150 } 151 const slices: SliceData = { 152 kind: 'slice', 153 start, 154 end, 155 resolution, 156 numCpus: this.numCpus, 157 starts: new Float64Array(numRows), 158 ends: new Float64Array(numRows), 159 cpus: new Uint32Array(numRows), 160 utids: new Uint32Array(numRows), 161 }; 162 163 const cols = rawResult.columns; 164 for (let row = 0; row < numRows; row++) { 165 const startSec = fromNs(+cols[0].longValues![row]); 166 slices.starts[row] = startSec; 167 slices.ends[row] = startSec + fromNs(+cols[1].longValues![row]); 168 slices.cpus[row] = +cols[2].longValues![row]; 169 slices.utids[row] = +cols[3].longValues![row]; 170 slices.end = Math.max(slices.ends[row], slices.end); 171 } 172 return slices; 173 } 174 175 private async query(query: string) { 176 const result = await this.engine.query(query); 177 if (result.error) { 178 console.error(`Query error "${query}": ${result.error}`); 179 throw new Error(`Query error "${query}": ${result.error}`); 180 } 181 return result; 182 } 183 184 onDestroy(): void { 185 if (this.setup) { 186 this.query(`drop table ${this.tableName('window')}`); 187 this.query(`drop table ${this.tableName('span')}`); 188 this.setup = false; 189 } 190 } 191} 192 193trackControllerRegistry.register(ProcessSchedulingTrackController); 194