blob: 8ab8c4ab40daa9a0932fa588a292b94207f908fe [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 * as m from 'mithril';
import {globals} from './globals';
import {PanelContainer} from './panel_container';
/**
* Shorthand for if globals perf debug mode is on.
*/
export const perfDebug = () => globals.frontendLocalState.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 buffer: number[] = [];
constructor(private _maxBufferSize = 10) {}
addValue(value: number) {
this._lastValue = value;
this.buffer.push(value);
if (this.buffer.length > this._maxBufferSize) {
this.buffer.shift();
}
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.frontendLocalState.togglePerfDebug(),
},
m('i.material-icons', 'close')),
this.containers.map((c, i) => m('section', c.renderPerfStats(i)))
]);
}
}
export const perfDisplay = new PerfDisplay();