blob: 504bafef50e10d033c504aa5ac2c2fce10e33e6b [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 m from 'mithril';
import {Actions} from '../common/actions';
import {globals} from './globals';
import {PanelContainer} from './panel_container';
// Shorthand for if globals perf debug mode is on.
export const perfDebug = () => globals.state.perfDebug;
// Returns performance.now() if perfDebug is enabled, otherwise 0.
// This is needed because calling performance.now is generally expensive
// and should not be done for every frame.
export const debugNow = () => perfDebug() ? performance.now() : 0;
// Returns execution time of |fn| if perf debug mode is on. Returns 0 otherwise.
export function measure(fn: () => void): number {
const start = debugNow();
fn();
return debugNow() - start;
}
// Stores statistics about samples, and keeps a fixed size buffer of most recent
// samples.
export class RunningStatistics {
private _count = 0;
private _mean = 0;
private _lastValue = 0;
private _ptr = 0;
private buffer: number[] = [];
constructor(private _maxBufferSize = 10) {}
addValue(value: number) {
this._lastValue = value;
if (this.buffer.length >= this._maxBufferSize) {
this.buffer[this._ptr++] = value;
if (this._ptr >= this.buffer.length) {
this._ptr -= this.buffer.length;
}
} else {
this.buffer.push(value);
}
this._mean = (this._mean * this._count + value) / (this._count + 1);
this._count++;
}
get mean() {
return this._mean;
}
get count() {
return this._count;
}
get bufferMean() {
return this.buffer.reduce((sum, v) => sum + v, 0) / this.buffer.length;
}
get bufferSize() {
return this.buffer.length;
}
get maxBufferSize() {
return this._maxBufferSize;
}
get last() {
return this._lastValue;
}
}
// Returns a summary string representation of a RunningStatistics object.
export function runningStatStr(stat: RunningStatistics) {
return `Last: ${stat.last.toFixed(2)}ms | ` +
`Avg: ${stat.mean.toFixed(2)}ms | ` +
`Avg${stat.maxBufferSize}: ${stat.bufferMean.toFixed(2)}ms`;
}
// Globals singleton class that renders performance stats for the whole app.
class PerfDisplay {
private containers: PanelContainer[] = [];
addContainer(container: PanelContainer) {
this.containers.push(container);
}
removeContainer(container: PanelContainer) {
const i = this.containers.indexOf(container);
this.containers.splice(i, 1);
}
renderPerfStats() {
if (!perfDebug()) return;
const perfDisplayEl = document.querySelector('.perf-stats');
if (!perfDisplayEl) return;
m.render(perfDisplayEl, [
m('section', globals.rafScheduler.renderPerfStats()),
m('button.close-button',
{
onclick: () => globals.dispatch(Actions.togglePerfDebug({})),
},
m('i.material-icons', 'close')),
this.containers.map((c, i) => m('section', c.renderPerfStats(i))),
]);
}
}
export const perfDisplay = new PerfDisplay();