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 {NUM, QueryResult} from '../../common/query_result'; 17import {fromNs, toNs} from '../../common/time'; 18import { 19 TrackController, 20 trackControllerRegistry 21} from '../../controller/track_controller'; 22 23import { 24 Config, 25 Data, 26 PROCESS_SCHEDULING_TRACK_KIND, 27} from './common'; 28 29// This summary is displayed for any processes that have CPU scheduling activity 30// associated with them. 31class ProcessSchedulingTrackController extends TrackController<Config, Data> { 32 static readonly kind = PROCESS_SCHEDULING_TRACK_KIND; 33 34 private maxCpu = 0; 35 private maxDurNs = 0; 36 private cachedBucketNs = Number.MAX_SAFE_INTEGER; 37 38 async onSetup() { 39 await this.createSchedView(); 40 41 const cpus = await this.engine.getCpus(); 42 43 // A process scheduling track should only exist in a trace that has cpus. 44 assertTrue(cpus.length > 0); 45 this.maxCpu = Math.max(...cpus) + 1; 46 47 const result = (await this.query(` 48 select ifnull(max(dur), 0) as maxDur, count(1) as count 49 from ${this.tableName('process_sched')} 50 `)).iter({maxDur: NUM, count: NUM}); 51 assertTrue(result.valid()); 52 this.maxDurNs = result.maxDur; 53 54 const rowCount = result.count; 55 const bucketNs = this.cachedBucketSizeNs(rowCount); 56 if (bucketNs === undefined) { 57 return; 58 } 59 await this.query(` 60 create table ${this.tableName('process_sched_cached')} as 61 select 62 (ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs} as cached_tsq, 63 ts, 64 max(dur) as dur, 65 cpu, 66 utid 67 from ${this.tableName('process_sched')} 68 group by cached_tsq, cpu 69 order by cached_tsq, cpu 70 `); 71 this.cachedBucketNs = bucketNs; 72 } 73 74 async onBoundsChange(start: number, end: number, resolution: number): 75 Promise<Data> { 76 assertTrue(this.config.upid !== null); 77 78 // The resolution should always be a power of two for the logic of this 79 // function to make sense. 80 const resolutionNs = toNs(resolution); 81 assertTrue(Math.log2(resolutionNs) % 1 === 0); 82 83 const startNs = toNs(start); 84 const endNs = toNs(end); 85 86 // ns per quantization bucket (i.e. ns per pixel). /2 * 2 is to force it to 87 // be an even number, so we can snap in the middle. 88 const bucketNs = 89 Math.max(Math.round(resolutionNs * this.pxSize() / 2) * 2, 1); 90 91 const queryRes = await this.queryData(startNs, endNs, bucketNs); 92 const numRows = queryRes.numRows(); 93 const slices: Data = { 94 kind: 'slice', 95 start, 96 end, 97 resolution, 98 length: numRows, 99 maxCpu: this.maxCpu, 100 starts: new Float64Array(numRows), 101 ends: new Float64Array(numRows), 102 cpus: new Uint32Array(numRows), 103 utids: new Uint32Array(numRows), 104 }; 105 106 const it = queryRes.iter({ 107 tsq: NUM, 108 ts: NUM, 109 dur: NUM, 110 cpu: NUM, 111 utid: NUM, 112 }); 113 114 for (let row = 0; it.valid(); it.next(), row++) { 115 const startNsQ = it.tsq; 116 const startNs = it.ts; 117 const durNs = it.dur; 118 const endNs = startNs + durNs; 119 120 let endNsQ = Math.floor((endNs + bucketNs / 2 - 1) / bucketNs) * bucketNs; 121 endNsQ = Math.max(endNsQ, startNsQ + bucketNs); 122 123 slices.starts[row] = fromNs(startNsQ); 124 slices.ends[row] = fromNs(endNsQ); 125 slices.cpus[row] = it.cpu; 126 slices.utids[row] = it.utid; 127 slices.end = Math.max(slices.ends[row], slices.end); 128 } 129 return slices; 130 } 131 132 private queryData(startNs: number, endNs: number, bucketNs: number): 133 Promise<QueryResult> { 134 const isCached = this.cachedBucketNs <= bucketNs; 135 const tsq = isCached ? `cached_tsq / ${bucketNs} * ${bucketNs}` : 136 `(ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs}`; 137 const queryTable = isCached ? this.tableName('process_sched_cached') : 138 this.tableName('process_sched'); 139 const constraintColumn = isCached ? 'cached_tsq' : 'ts'; 140 return this.query(` 141 select 142 ${tsq} as tsq, 143 ts, 144 max(dur) as dur, 145 cpu, 146 utid 147 from ${queryTable} 148 where 149 ${constraintColumn} >= ${startNs - this.maxDurNs} and 150 ${constraintColumn} <= ${endNs} 151 group by tsq, cpu 152 order by tsq, cpu 153 `); 154 } 155 156 private async createSchedView() { 157 await this.query(` 158 create view ${this.tableName('process_sched')} as 159 select ts, dur, cpu, utid 160 from experimental_sched_upid 161 where 162 utid != 0 and 163 upid = ${this.config.upid} 164 `); 165 } 166} 167 168trackControllerRegistry.register(ProcessSchedulingTrackController); 169