blob: 08ca0b563851f85c6c3260d2ee34609eee0f7f0f [file] [log] [blame]
// Copyright (C) 2019 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {assertTrue} from '../../base/logging';
import {RawQueryResult} from '../../common/protos';
import {iter, NUM, slowlyCountRows} from '../../common/query_iterator';
import {fromNs, toNs} from '../../common/time';
import {
TrackController,
trackControllerRegistry
} from '../../controller/track_controller';
import {
Config,
CPU_FREQ_TRACK_KIND,
Data,
} from './common';
class CpuFreqTrackController extends TrackController<Config, Data> {
static readonly kind = CPU_FREQ_TRACK_KIND;
private maxDurNs = 0;
private maxTsEndNs = 0;
private maximumValueSeen = 0;
private cachedBucketNs = Number.MAX_SAFE_INTEGER;
async onSetup() {
await this.createFreqIdleViews();
this.maximumValueSeen = await this.queryMaxFrequency();
this.maxDurNs = await this.queryMaxSourceDur();
const result = await this.query(`
select max(ts), dur, count(1)
from ${this.tableName('freq_idle')}
`);
this.maxTsEndNs =
result.columns[0].longValues![0] + result.columns[1].longValues![0];
const rowCount = result.columns[2].longValues![0];
const bucketNs = this.cachedBucketSizeNs(rowCount);
if (bucketNs === undefined) {
return;
}
await this.query(`
create table ${this.tableName('freq_idle_cached')} as
select
(ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs} as cached_tsq,
min(freq_value) as min_freq,
max(freq_value) as max_freq,
value_at_max_ts(ts, freq_value) as last_freq,
value_at_max_ts(ts, idle_value) as last_idle_value
from ${this.tableName('freq_idle')}
group by cached_tsq
order by cached_tsq
`);
this.cachedBucketNs = bucketNs;
}
async onBoundsChange(start: number, end: number, resolution: number):
Promise<Data> {
// The resolution should always be a power of two for the logic of this
// function to make sense.
const resolutionNs = toNs(resolution);
assertTrue(Math.log2(resolutionNs) % 1 === 0);
const startNs = toNs(start);
const endNs = toNs(end);
// ns per quantization bucket (i.e. ns per pixel). /2 * 2 is to force it to
// be an even number, so we can snap in the middle.
const bucketNs =
Math.max(Math.round(resolutionNs * this.pxSize() / 2) * 2, 1);
const freqResult = await this.queryData(startNs, endNs, bucketNs);
const numRows = slowlyCountRows(freqResult);
const data: Data = {
start,
end,
resolution,
length: numRows,
maximumValue: this.maximumValue(),
maxTsEnd: this.maxTsEndNs,
timestamps: new Float64Array(numRows),
minFreqKHz: new Uint32Array(numRows),
maxFreqKHz: new Uint32Array(numRows),
lastFreqKHz: new Uint32Array(numRows),
lastIdleValues: new Int8Array(numRows),
};
const it = iter(
{
'tsq': NUM,
'minFreq': NUM,
'maxFreq': NUM,
'lastFreq': NUM,
'lastIdleValue': NUM,
},
freqResult);
for (let i = 0; it.valid(); ++i, it.next()) {
data.timestamps[i] = fromNs(it.row.tsq);
data.minFreqKHz[i] = it.row.minFreq;
data.maxFreqKHz[i] = it.row.maxFreq;
data.lastFreqKHz[i] = it.row.lastFreq;
data.lastIdleValues[i] = it.row.lastIdleValue;
}
return data;
}
private async queryData(startNs: number, endNs: number, bucketNs: number):
Promise<RawQueryResult> {
const isCached = this.cachedBucketNs <= bucketNs;
if (isCached) {
return this.query(`
select
cached_tsq / ${bucketNs} * ${bucketNs} as tsq,
min(min_freq) as minFreq,
max(max_freq) as maxFreq,
value_at_max_ts(cached_tsq, last_freq) as lastFreq,
value_at_max_ts(cached_tsq, last_idle_value) as lastIdleValue
from ${this.tableName('freq_idle_cached')}
where
cached_tsq >= ${startNs - this.maxDurNs} and
cached_tsq <= ${endNs}
group by tsq
order by tsq
`);
}
const minTsFreq = await this.query(`
select ifnull(max(ts), 0) from ${this.tableName('freq')}
where ts < ${startNs}
`);
let minTs = minTsFreq.columns[0].longValues![0];
if (this.config.idleTrackId !== undefined) {
const minTsIdle = await this.query(`
select ifnull(max(ts), 0) from ${this.tableName('idle')}
where ts < ${startNs}
`);
minTs = Math.min(minTsIdle.columns[0].longValues![0], minTs);
}
const geqConstraint = this.config.idleTrackId === undefined ?
`ts >= ${minTs}` :
`source_geq(ts, ${minTs})`;
return this.query(`
select
(ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs} as tsq,
min(freq_value) as minFreq,
max(freq_value) as maxFreq,
value_at_max_ts(ts, freq_value) as lastFreq,
value_at_max_ts(ts, idle_value) as lastIdleValue
from ${this.tableName('freq_idle')}
where
${geqConstraint} and
ts <= ${endNs}
group by tsq
order by tsq
`);
}
private async queryMaxFrequency(): Promise<number> {
const result = await this.query(`
select max(freq_value)
from ${this.tableName('freq')}
`);
return result.columns[0].doubleValues![0];
}
private async queryMaxSourceDur(): Promise<number> {
const maxDurFreqResult =
await this.query(`select max(dur) from ${this.tableName('freq')}`);
const maxFreqDurNs = maxDurFreqResult.columns[0].longValues![0];
if (this.config.idleTrackId === undefined) {
return maxFreqDurNs;
}
const maxDurIdleResult =
await this.query(`select max(dur) from ${this.tableName('idle')}`);
return Math.max(maxFreqDurNs, maxDurIdleResult.columns[0].longValues![0]);
}
private async createFreqIdleViews() {
await this.query(`create view ${this.tableName('freq')} as
select
ts,
dur,
value as freq_value
from experimental_counter_dur c
where track_id = ${this.config.freqTrackId};
`);
if (this.config.idleTrackId === undefined) {
await this.query(`create view ${this.tableName('freq_idle')} as
select
ts,
dur,
-1 as idle_value,
freq_value
from ${this.tableName('freq')};
`);
return;
}
await this.query(`
create view ${this.tableName('idle')} as
select
ts,
dur,
iif(value = 4294967295, -1, cast(value as int)) as idle_value
from experimental_counter_dur c
where track_id = ${this.config.idleTrackId};
`);
await this.query(`
create virtual table ${this.tableName('freq_idle')}
using span_join(${this.tableName('freq')}, ${this.tableName('idle')});
`);
}
private maximumValue() {
return Math.max(this.config.maximumValue || 0, this.maximumValueSeen);
}
}
trackControllerRegistry.register(CpuFreqTrackController);