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