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, 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_FREQ_TRACK_KIND, 26 Data, 27} from './common'; 28 29class CpuFreqTrackController extends TrackController<Config, Data> { 30 static readonly kind = CPU_FREQ_TRACK_KIND; 31 private setup = false; 32 private maximumValueSeen = 0; 33 34 async onBoundsChange(start: number, end: number, resolution: number): 35 Promise<Data> { 36 const startNs = toNs(start); 37 const endNs = toNs(end); 38 39 if (!this.setup) { 40 const result = await this.query(` 41 select max(value) 42 from counter 43 where track_id = ${this.config.freqTrackId}`); 44 this.maximumValueSeen = +result.columns[0].doubleValues![0]; 45 46 await this.query( 47 `create virtual table ${this.tableName('window')} using window;`); 48 49 await this.query(`create view ${this.tableName('freq')} 50 as select 51 ts, 52 dur, 53 value as freq_value 54 from experimental_counter_dur c 55 where track_id = ${this.config.freqTrackId}; 56 `); 57 58 // If there is no idle track, just make the idle track a single row 59 // which spans the entire time range. 60 if (this.config.idleTrackId === undefined) { 61 await this.query(`create view ${this.tableName('idle')} as 62 select 63 0 as ts, 64 ${Number.MAX_SAFE_INTEGER} as dur, 65 -1 as idle_value; 66 `); 67 } else { 68 await this.query(`create view ${this.tableName('idle')} 69 as select 70 ts, 71 dur, 72 value as idle_value 73 from experimental_counter_dur c 74 where track_id = ${this.config.idleTrackId}; 75 `); 76 } 77 78 await this.query(`create virtual table ${this.tableName('freq_idle')} 79 using span_join(${this.tableName('freq')}, 80 ${this.tableName('idle')});`); 81 82 await this.query(`create virtual table ${this.tableName('span_activity')} 83 using span_join(${this.tableName('freq_idle')}, 84 ${this.tableName('window')});`); 85 86 // TODO(taylori): Move the idle value processing to the TP. 87 await this.query(`create view ${this.tableName('activity')} 88 as select 89 ts, 90 dur, 91 quantum_ts, 92 case idle_value 93 when 4294967295 then -1 94 else idle_value 95 end as idle, 96 freq_value as freq 97 from ${this.tableName('span_activity')}; 98 `); 99 100 this.setup = true; 101 } 102 103 this.query(`update ${this.tableName('window')} set 104 window_start = ${startNs}, 105 window_dur = ${Math.max(1, endNs - startNs)}, 106 quantum = 0`); 107 108 const result = await this.engine.queryOneRow(`select count(*) 109 from ${this.tableName('activity')}`); 110 const isQuantized = result[0] > LIMIT; 111 112 // Cast as double to avoid problem where values are sometimes 113 // doubles, sometimes longs. 114 let query = `select ts, dur, cast(idle as DOUBLE), freq 115 from ${this.tableName('activity')} limit ${LIMIT}`; 116 117 if (isQuantized) { 118 // |resolution| is in s/px we want # ns for 10px window: 119 const bucketSizeNs = Math.round(resolution * 10 * 1e9); 120 const windowStartNs = Math.floor(startNs / bucketSizeNs) * bucketSizeNs; 121 const windowDurNs = Math.max(1, endNs - windowStartNs); 122 123 this.query(`update ${this.tableName('window')} set 124 window_start = ${startNs}, 125 window_dur = ${windowDurNs}, 126 quantum = ${isQuantized ? bucketSizeNs : 0}`); 127 128 query = `select 129 min(ts) as ts, 130 sum(dur) as dur, 131 case 132 when min(idle) = -1 then cast(-1 as DOUBLE) 133 else cast(0 as DOUBLE) 134 end as idle, 135 sum(weighted_freq)/sum(dur) as freq_avg, 136 quantum_ts 137 from ( 138 select 139 ts, 140 dur, 141 quantum_ts, 142 freq*dur as weighted_freq, 143 idle 144 from ${this.tableName('activity')}) 145 group by quantum_ts limit ${LIMIT}`; 146 } 147 148 const freqResult = await this.query(query); 149 150 const numRows = +freqResult.numRecords; 151 const data: Data = { 152 start, 153 end, 154 resolution, 155 length: numRows, 156 maximumValue: this.maximumValue(), 157 isQuantized, 158 tsStarts: new Float64Array(numRows), 159 tsEnds: new Float64Array(numRows), 160 idles: new Int8Array(numRows), 161 freqKHz: new Uint32Array(numRows), 162 }; 163 164 const cols = freqResult.columns; 165 for (let row = 0; row < numRows; row++) { 166 const startSec = fromNs(+cols[0].longValues![row]); 167 data.tsStarts[row] = startSec; 168 data.tsEnds[row] = startSec + fromNs(+cols[1].longValues![row]); 169 data.idles[row] = +cols[2].doubleValues![row]; 170 data.freqKHz[row] = +cols[3].doubleValues![row]; 171 } 172 173 return data; 174 } 175 176 private maximumValue() { 177 return Math.max(this.config.maximumValue || 0, this.maximumValueSeen); 178 } 179 180} 181 182 183trackControllerRegistry.register(CpuFreqTrackController); 184