1// Copyright (C) 2018 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import m from 'mithril'; 16 17const hooks = { 18 isDebug: () => false, 19 toggleDebug: () => {}, 20}; 21 22export function setPerfHooks(isDebug: () => boolean, toggleDebug: () => void) { 23 hooks.isDebug = isDebug; 24 hooks.toggleDebug = toggleDebug; 25} 26 27// Shorthand for if globals perf debug mode is on. 28export const perfDebug = () => hooks.isDebug(); 29 30// Returns performance.now() if perfDebug is enabled, otherwise 0. 31// This is needed because calling performance.now is generally expensive 32// and should not be done for every frame. 33export const debugNow = () => (perfDebug() ? performance.now() : 0); 34 35// Returns execution time of |fn| if perf debug mode is on. Returns 0 otherwise. 36export function measure(fn: () => void): number { 37 const start = debugNow(); 38 fn(); 39 return debugNow() - start; 40} 41 42// Stores statistics about samples, and keeps a fixed size buffer of most recent 43// samples. 44export class RunningStatistics { 45 private _count = 0; 46 private _mean = 0; 47 private _lastValue = 0; 48 private _ptr = 0; 49 50 private buffer: number[] = []; 51 52 constructor(private _maxBufferSize = 10) {} 53 54 addValue(value: number) { 55 this._lastValue = value; 56 if (this.buffer.length >= this._maxBufferSize) { 57 this.buffer[this._ptr++] = value; 58 if (this._ptr >= this.buffer.length) { 59 this._ptr -= this.buffer.length; 60 } 61 } else { 62 this.buffer.push(value); 63 } 64 65 this._mean = (this._mean * this._count + value) / (this._count + 1); 66 this._count++; 67 } 68 69 get mean() { 70 return this._mean; 71 } 72 get count() { 73 return this._count; 74 } 75 get bufferMean() { 76 return this.buffer.reduce((sum, v) => sum + v, 0) / this.buffer.length; 77 } 78 get bufferSize() { 79 return this.buffer.length; 80 } 81 get maxBufferSize() { 82 return this._maxBufferSize; 83 } 84 get last() { 85 return this._lastValue; 86 } 87} 88 89// Returns a summary string representation of a RunningStatistics object. 90export function runningStatStr(stat: RunningStatistics) { 91 return ( 92 `Last: ${stat.last.toFixed(2)}ms | ` + 93 `Avg: ${stat.mean.toFixed(2)}ms | ` + 94 `Avg${stat.maxBufferSize}: ${stat.bufferMean.toFixed(2)}ms` 95 ); 96} 97 98export interface PerfStatsSource { 99 renderPerfStats(): m.Children; 100} 101 102// Globals singleton class that renders performance stats for the whole app. 103class PerfDisplay { 104 private containers: PerfStatsSource[] = []; 105 106 addContainer(container: PerfStatsSource) { 107 this.containers.push(container); 108 } 109 110 removeContainer(container: PerfStatsSource) { 111 const i = this.containers.indexOf(container); 112 this.containers.splice(i, 1); 113 } 114 115 renderPerfStats(src: PerfStatsSource) { 116 if (!perfDebug()) return; 117 const perfDisplayEl = document.querySelector('.perf-stats'); 118 if (!perfDisplayEl) return; 119 m.render(perfDisplayEl, [ 120 m('section', src.renderPerfStats()), 121 m( 122 'button.close-button', 123 { 124 onclick: hooks.toggleDebug, 125 }, 126 m('i.material-icons', 'close'), 127 ), 128 this.containers.map((c, i) => 129 m('section', m('div', `Panel Container ${i + 1}`), c.renderPerfStats()), 130 ), 131 ]); 132 } 133} 134 135export const perfDisplay = new PerfDisplay(); 136