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 CPU_FREQ_TRACK_KIND, 24 Data, 25} from './common'; 26 27class CpuFreqTrackController extends TrackController<Config, Data> { 28 static readonly kind = CPU_FREQ_TRACK_KIND; 29 private busy = false; 30 private setup = false; 31 private maximumValueSeen = 0; 32 33 onBoundsChange(start: number, end: number, resolution: number): void { 34 this.update(start, end, resolution); 35 } 36 37 private async update(start: number, end: number, resolution: number): 38 Promise<void> { 39 // TODO: we should really call TraceProcessor.Interrupt() at this point. 40 if (this.busy) return; 41 42 const startNs = Math.round(start * 1e9); 43 const endNs = Math.round(end * 1e9); 44 45 this.busy = true; 46 if (!this.setup) { 47 const result = await this.query(` 48 select max(value) from 49 counters where name = 'cpufreq' 50 and ref = ${this.config.cpu}`); 51 this.maximumValueSeen = +result.columns[0].doubleValues![0]; 52 53 await this.query( 54 `create virtual table ${this.tableName('window')} using window;`); 55 56 await this.query(`create view ${this.tableName('freq')} 57 as select 58 ts, 59 lead(ts) over (order by ts) - ts as dur, 60 ref as cpu, 61 name as freq_name, 62 value as freq_value 63 from counters 64 where name = 'cpufreq' 65 and ref = ${this.config.cpu} 66 and ref_type = 'cpu'; 67 `); 68 69 await this.query(`create view ${this.tableName('idle')} 70 as select 71 ts, 72 lead(ts) over (order by ts) - ts as dur, 73 ref as cpu, 74 name as idle_name, 75 value as idle_value 76 from counters 77 where name = 'cpuidle' 78 and ref = ${this.config.cpu} 79 and ref_type = 'cpu'; 80 `); 81 82 await this.query(`create virtual table ${this.tableName('freq_idle')} 83 using span_join(${this.tableName('freq')} PARTITIONED cpu, 84 ${this.tableName('idle')} PARTITIONED cpu);`); 85 86 await this.query(`create virtual table ${this.tableName('span_activity')} 87 using span_join(${this.tableName('freq_idle')} PARTITIONED cpu, 88 ${this.tableName('window')});`); 89 90 // TODO(taylori): Move the idle value processing to the TP. 91 await this.query(`create view ${this.tableName('activity')} 92 as select 93 ts, 94 dur, 95 quantum_ts, 96 cpu, 97 case idle_value 98 when 4294967295 then -1 99 else idle_value 100 end as idle, 101 freq_value as freq 102 from ${this.tableName('span_activity')}; 103 `); 104 105 this.setup = true; 106 } 107 108 const isQuantized = this.shouldSummarize(resolution); 109 // |resolution| is in s/px we want # ns for 10px window: 110 const bucketSizeNs = Math.round(resolution * 10 * 1e9); 111 let windowStartNs = startNs; 112 if (isQuantized) { 113 windowStartNs = Math.floor(windowStartNs / bucketSizeNs) * bucketSizeNs; 114 } 115 const windowDurNs = Math.max(1, endNs - windowStartNs); 116 117 this.query(`update ${this.tableName('window')} set 118 window_start = ${startNs}, 119 window_dur = ${windowDurNs}, 120 quantum = ${isQuantized ? bucketSizeNs : 0}`); 121 122 // Cast as double to avoid problem where values are sometimes 123 // doubles, sometimes longs. 124 let query = `select ts, dur, cast(idle as DOUBLE), freq 125 from ${this.tableName('activity')}`; 126 127 if (isQuantized) { 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`; 146 } 147 148 const freqResult = await this.query(query); 149 150 const numRows = +freqResult.numRecords; 151 const data: Data = { 152 start, 153 end, 154 maximumValue: this.maximumValue(), 155 resolution, 156 isQuantized, 157 tsStarts: new Float64Array(numRows), 158 tsEnds: new Float64Array(numRows), 159 idles: new Int8Array(numRows), 160 freqKHz: new Uint32Array(numRows), 161 }; 162 163 const cols = freqResult.columns; 164 for (let row = 0; row < numRows; row++) { 165 const startSec = fromNs(+cols[0].longValues![row]); 166 data.tsStarts[row] = startSec; 167 data.tsEnds[row] = startSec + fromNs(+cols[1].longValues![row]); 168 data.idles[row] = +cols[2].doubleValues![row]; 169 data.freqKHz[row] = +cols[3].doubleValues![row]; 170 } 171 172 this.publish(data); 173 this.busy = false; 174 } 175 176 private maximumValue() { 177 return Math.max(this.config.maximumValue || 0, this.maximumValueSeen); 178 } 179 180 private async query(query: string) { 181 const result = await this.engine.query(query); 182 if (result.error) { 183 console.error(`Query error "${query}": ${result.error}`); 184 throw new Error(`Query error "${query}": ${result.error}`); 185 } 186 return result; 187 } 188} 189 190 191trackControllerRegistry.register(CpuFreqTrackController); 192