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