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 {Actions} from '../common/actions'; 19import {globals} from './globals'; 20import {PanelContainer} from './panel_container'; 21 22/** 23 * Shorthand for if globals perf debug mode is on. 24 */ 25export const perfDebug = () => globals.state.perfDebug; 26 27/** 28 * Returns performance.now() if perfDebug is enabled, otherwise 0. 29 * This is needed because calling performance.now is generally expensive 30 * and should not be done for every frame. 31 */ 32export const debugNow = () => perfDebug() ? performance.now() : 0; 33 34/** 35 * Returns execution time of |fn| if perf debug mode is on. Returns 0 otherwise. 36 */ 37export function measure(fn: () => void): number { 38 const start = debugNow(); 39 fn(); 40 return debugNow() - start; 41} 42 43/** 44 * Stores statistics about samples, and keeps a fixed size buffer of most recent 45 * samples. 46 */ 47export class RunningStatistics { 48 private _count = 0; 49 private _mean = 0; 50 private _lastValue = 0; 51 52 private buffer: number[] = []; 53 54 constructor(private _maxBufferSize = 10) {} 55 56 addValue(value: number) { 57 this._lastValue = value; 58 this.buffer.push(value); 59 if (this.buffer.length > this._maxBufferSize) { 60 this.buffer.shift(); 61 } 62 this._mean = (this._mean * this._count + value) / (this._count + 1); 63 this._count++; 64 } 65 66 get mean() { 67 return this._mean; 68 } 69 get count() { 70 return this._count; 71 } 72 get bufferMean() { 73 return this.buffer.reduce((sum, v) => sum + v, 0) / this.buffer.length; 74 } 75 get bufferSize() { 76 return this.buffer.length; 77 } 78 get maxBufferSize() { 79 return this._maxBufferSize; 80 } 81 get last() { 82 return this._lastValue; 83 } 84} 85 86/** 87 * Returns a summary string representation of a RunningStatistics object. 88 */ 89export function runningStatStr(stat: RunningStatistics) { 90 return `Last: ${stat.last.toFixed(2)}ms | ` + 91 `Avg: ${stat.mean.toFixed(2)}ms | ` + 92 `Avg${stat.maxBufferSize}: ${stat.bufferMean.toFixed(2)}ms`; 93} 94 95/** 96 * Globals singleton class that renders performance stats for the whole app. 97 */ 98class PerfDisplay { 99 private containers: PanelContainer[] = []; 100 addContainer(container: PanelContainer) { 101 this.containers.push(container); 102 } 103 104 removeContainer(container: PanelContainer) { 105 const i = this.containers.indexOf(container); 106 this.containers.splice(i, 1); 107 } 108 109 renderPerfStats() { 110 if (!perfDebug()) return; 111 const perfDisplayEl = document.querySelector('.perf-stats'); 112 if (!perfDisplayEl) return; 113 m.render(perfDisplayEl, [ 114 m('section', globals.rafScheduler.renderPerfStats()), 115 m('button.close-button', 116 { 117 onclick: () => globals.dispatch(Actions.togglePerfDebug({})), 118 }, 119 m('i.material-icons', 'close')), 120 this.containers.map((c, i) => m('section', c.renderPerfStats(i))) 121 ]); 122 } 123} 124 125export const perfDisplay = new PerfDisplay(); 126