blob: d036d62b09fe382d60def21fe0a48813c52696d3 [file] [log] [blame]
// Copyright (C) 2018 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 {assertExists} from '../base/logging';
import {Engine} from '../common/engine';
import {Registry} from '../common/registry';
import {TraceTime, TrackState} from '../common/state';
import {LIMIT, TrackData} from '../common/track_data';
import {Controller} from './controller';
import {ControllerFactory} from './controller';
import {globals} from './globals';
// TrackController is a base class overridden by track implementations (e.g.,
// sched slices, nestable slices, counters).
export abstract class TrackController<Config = {},
Data extends TrackData = TrackData>
extends Controller<'main'> {
readonly trackId: string;
readonly engine: Engine;
private data?: TrackData;
private requestingData = false;
private queuedRequest = false;
constructor(args: TrackControllerArgs) {
super('main');
this.trackId = args.trackId;
this.engine = args.engine;
}
// Must be overridden by the track implementation. Is invoked when the track
// frontend runs out of cached data. The derived track controller is expected
// to publish new track data in response to this call.
abstract async onBoundsChange(start: number, end: number, resolution: number):
Promise<Data>;
get trackState(): TrackState {
return assertExists(globals.state.tracks[this.trackId]);
}
get config(): Config {
return this.trackState.config as Config;
}
publish(data: Data): void {
this.data = data;
globals.publish('TrackData', {id: this.trackId, data});
}
/**
* Returns a valid SQL table name with the given prefix that should be unique
* for each track.
*/
tableName(prefix: string) {
// Derive table name from, since that is unique for each track.
// Track ID can be UUID but '-' is not valid for sql table name.
const idSuffix = this.trackId.split('-').join('_');
return `${prefix}_${idSuffix}`;
}
shouldSummarize(resolution: number): boolean {
// |resolution| is in s/px (to nearest power of 10) assuming a display
// of ~1000px 0.0008 is 0.8s.
return resolution >= 0.0008;
}
protected async query(query: string) {
const result = await this.engine.query(query);
if (result.error) {
console.error(`Query error "${query}": ${result.error}`);
throw new Error(`Query error "${query}": ${result.error}`);
}
return result;
}
shouldRequestData(traceTime: TraceTime): boolean {
if (this.data === undefined) return true;
// If at the limit only request more data if the view has moved.
const atLimit = this.data.length === LIMIT;
if (atLimit) {
// We request more data than the window, so add window duration to find
// the previous window.
const prevWindowStart =
this.data.start + (traceTime.startSec - traceTime.endSec);
return traceTime.startSec !== prevWindowStart;
}
// Otherwise request more data only when out of range of current data or
// resolution has changed.
const inRange = traceTime.startSec >= this.data.start &&
traceTime.endSec <= this.data.end;
return !inRange ||
this.data.resolution !==
globals.state.frontendLocalState.visibleState.resolution;
}
run() {
const visibleState = globals.state.frontendLocalState.visibleState;
if (visibleState === undefined) return;
const dur = visibleState.endSec - visibleState.startSec;
if (globals.state.visibleTracks.includes(this.trackId) &&
this.shouldRequestData(visibleState)) {
if (this.requestingData) {
this.queuedRequest = true;
} else {
this.requestingData = true;
this.onBoundsChange(
visibleState.startSec - dur,
visibleState.endSec + dur,
visibleState.resolution)
.then(data => {
this.publish(data);
})
.catch(error => {
console.error(error);
})
.finally(() => {
this.requestingData = false;
if (this.queuedRequest) {
this.queuedRequest = false;
this.run();
}
});
}
}
}
}
export interface TrackControllerArgs {
trackId: string;
engine: Engine;
}
export interface TrackControllerFactory extends
ControllerFactory<TrackControllerArgs> {
kind: string;
}
export const trackControllerRegistry = new Registry<TrackControllerFactory>();