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 {RawQueryResult} from '../../common/protos'; 17import {iter, NUM, slowlyCountRows} from '../../common/query_iterator'; 18import {fromNs, toNs} from '../../common/time'; 19import { 20 TrackController, 21 trackControllerRegistry 22} from '../../controller/track_controller'; 23 24import { 25 Config, 26 CPU_FREQ_TRACK_KIND, 27 Data, 28} from './common'; 29 30class CpuFreqTrackController extends TrackController<Config, Data> { 31 static readonly kind = CPU_FREQ_TRACK_KIND; 32 33 private maxDurNs = 0; 34 private maxTsEndNs = 0; 35 private maximumValueSeen = 0; 36 private cachedBucketNs = Number.MAX_SAFE_INTEGER; 37 38 async onSetup() { 39 await this.createFreqIdleViews(); 40 41 this.maximumValueSeen = await this.queryMaxFrequency(); 42 this.maxDurNs = await this.queryMaxSourceDur(); 43 44 const result = await this.query(` 45 select max(ts), dur, count(1) 46 from ${this.tableName('freq_idle')} 47 `); 48 this.maxTsEndNs = 49 result.columns[0].longValues![0] + result.columns[1].longValues![0]; 50 51 const rowCount = result.columns[2].longValues![0]; 52 const bucketNs = this.cachedBucketSizeNs(rowCount); 53 if (bucketNs === undefined) { 54 return; 55 } 56 await this.query(` 57 create table ${this.tableName('freq_idle_cached')} as 58 select 59 (ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs} as cached_tsq, 60 min(freq_value) as min_freq, 61 max(freq_value) as max_freq, 62 value_at_max_ts(ts, freq_value) as last_freq, 63 value_at_max_ts(ts, idle_value) as last_idle_value 64 from ${this.tableName('freq_idle')} 65 group by cached_tsq 66 order by cached_tsq 67 `); 68 this.cachedBucketNs = bucketNs; 69 } 70 71 async onBoundsChange(start: number, end: number, resolution: number): 72 Promise<Data> { 73 // The resolution should always be a power of two for the logic of this 74 // function to make sense. 75 const resolutionNs = toNs(resolution); 76 assertTrue(Math.log2(resolutionNs) % 1 === 0); 77 78 const startNs = toNs(start); 79 const endNs = toNs(end); 80 81 // ns per quantization bucket (i.e. ns per pixel). /2 * 2 is to force it to 82 // be an even number, so we can snap in the middle. 83 const bucketNs = 84 Math.max(Math.round(resolutionNs * this.pxSize() / 2) * 2, 1); 85 86 const freqResult = await this.queryData(startNs, endNs, bucketNs); 87 88 const numRows = slowlyCountRows(freqResult); 89 const data: Data = { 90 start, 91 end, 92 resolution, 93 length: numRows, 94 maximumValue: this.maximumValue(), 95 maxTsEnd: this.maxTsEndNs, 96 timestamps: new Float64Array(numRows), 97 minFreqKHz: new Uint32Array(numRows), 98 maxFreqKHz: new Uint32Array(numRows), 99 lastFreqKHz: new Uint32Array(numRows), 100 lastIdleValues: new Int8Array(numRows), 101 }; 102 103 const it = iter( 104 { 105 'tsq': NUM, 106 'minFreq': NUM, 107 'maxFreq': NUM, 108 'lastFreq': NUM, 109 'lastIdleValue': NUM, 110 }, 111 freqResult); 112 for (let i = 0; it.valid(); ++i, it.next()) { 113 data.timestamps[i] = fromNs(it.row.tsq); 114 data.minFreqKHz[i] = it.row.minFreq; 115 data.maxFreqKHz[i] = it.row.maxFreq; 116 data.lastFreqKHz[i] = it.row.lastFreq; 117 data.lastIdleValues[i] = it.row.lastIdleValue; 118 } 119 return data; 120 } 121 122 private async queryData(startNs: number, endNs: number, bucketNs: number): 123 Promise<RawQueryResult> { 124 const isCached = this.cachedBucketNs <= bucketNs; 125 126 if (isCached) { 127 return this.query(` 128 select 129 cached_tsq / ${bucketNs} * ${bucketNs} as tsq, 130 min(min_freq) as minFreq, 131 max(max_freq) as maxFreq, 132 value_at_max_ts(cached_tsq, last_freq) as lastFreq, 133 value_at_max_ts(cached_tsq, last_idle_value) as lastIdleValue 134 from ${this.tableName('freq_idle_cached')} 135 where 136 cached_tsq >= ${startNs - this.maxDurNs} and 137 cached_tsq <= ${endNs} 138 group by tsq 139 order by tsq 140 `); 141 } 142 143 const minTsFreq = await this.query(` 144 select ifnull(max(ts), 0) from ${this.tableName('freq')} 145 where ts < ${startNs} 146 `); 147 let minTs = minTsFreq.columns[0].longValues![0]; 148 if (this.config.idleTrackId !== undefined) { 149 const minTsIdle = await this.query(` 150 select ifnull(max(ts), 0) from ${this.tableName('idle')} 151 where ts < ${startNs} 152 `); 153 minTs = Math.min(minTsIdle.columns[0].longValues![0], minTs); 154 } 155 const geqConstraint = this.config.idleTrackId === undefined ? 156 `ts >= ${minTs}` : 157 `source_geq(ts, ${minTs})`; 158 return this.query(` 159 select 160 (ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs} as tsq, 161 min(freq_value) as minFreq, 162 max(freq_value) as maxFreq, 163 value_at_max_ts(ts, freq_value) as lastFreq, 164 value_at_max_ts(ts, idle_value) as lastIdleValue 165 from ${this.tableName('freq_idle')} 166 where 167 ${geqConstraint} and 168 ts <= ${endNs} 169 group by tsq 170 order by tsq 171 `); 172 } 173 174 private async queryMaxFrequency(): Promise<number> { 175 const result = await this.query(` 176 select max(freq_value) 177 from ${this.tableName('freq')} 178 `); 179 return result.columns[0].doubleValues![0]; 180 } 181 182 private async queryMaxSourceDur(): Promise<number> { 183 const maxDurFreqResult = 184 await this.query(`select max(dur) from ${this.tableName('freq')}`); 185 const maxFreqDurNs = maxDurFreqResult.columns[0].longValues![0]; 186 if (this.config.idleTrackId === undefined) { 187 return maxFreqDurNs; 188 } 189 190 const maxDurIdleResult = 191 await this.query(`select max(dur) from ${this.tableName('idle')}`); 192 return Math.max(maxFreqDurNs, maxDurIdleResult.columns[0].longValues![0]); 193 } 194 195 private async createFreqIdleViews() { 196 await this.query(`create view ${this.tableName('freq')} as 197 select 198 ts, 199 dur, 200 value as freq_value 201 from experimental_counter_dur c 202 where track_id = ${this.config.freqTrackId}; 203 `); 204 205 if (this.config.idleTrackId === undefined) { 206 await this.query(`create view ${this.tableName('freq_idle')} as 207 select 208 ts, 209 dur, 210 -1 as idle_value, 211 freq_value 212 from ${this.tableName('freq')}; 213 `); 214 return; 215 } 216 217 await this.query(` 218 create view ${this.tableName('idle')} as 219 select 220 ts, 221 dur, 222 iif(value = 4294967295, -1, cast(value as int)) as idle_value 223 from experimental_counter_dur c 224 where track_id = ${this.config.idleTrackId}; 225 `); 226 227 await this.query(` 228 create virtual table ${this.tableName('freq_idle')} 229 using span_join(${this.tableName('freq')}, ${this.tableName('idle')}); 230 `); 231 } 232 233 private maximumValue() { 234 return Math.max(this.config.maximumValue || 0, this.maximumValueSeen); 235 } 236} 237 238 239trackControllerRegistry.register(CpuFreqTrackController); 240