|  | // 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(); |