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