blob: 6e9afaf2cd230168da493858bd7ba66f92c482f1 [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';
const hooks = {
isDebug: () => false,
toggleDebug: () => {},
};
export function setPerfHooks(isDebug: () => boolean, toggleDebug: () => void) {
hooks.isDebug = isDebug;
hooks.toggleDebug = toggleDebug;
}
// Shorthand for if globals perf debug mode is on.
export const perfDebug = () => hooks.isDebug();
// 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`
);
}
export interface PerfStatsSource {
renderPerfStats(): m.Children;
}
// Globals singleton class that renders performance stats for the whole app.
class PerfDisplay {
private containers: PerfStatsSource[] = [];
addContainer(container: PerfStatsSource) {
this.containers.push(container);
}
removeContainer(container: PerfStatsSource) {
const i = this.containers.indexOf(container);
this.containers.splice(i, 1);
}
renderPerfStats(src: PerfStatsSource) {
if (!perfDebug()) return;
const perfDisplayEl = document.querySelector('.perf-stats');
if (!perfDisplayEl) return;
m.render(perfDisplayEl, [
m('section', src.renderPerfStats()),
m(
'button.close-button',
{
onclick: hooks.toggleDebug,
},
m('i.material-icons', 'close'),
),
this.containers.map((c, i) =>
m('section', m('div', `Panel Container ${i + 1}`), c.renderPerfStats()),
),
]);
}
}
export const perfDisplay = new PerfDisplay();