| |
| // 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 * as m from 'mithril'; |
| |
| import {globals} from './globals'; |
| import {PanelContainer} from './panel_container'; |
| |
| /** |
| * Shorthand for if globals perf debug mode is on. |
| */ |
| export const perfDebug = () => globals.frontendLocalState.perfDebug; |
| |
| /** |
| * 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 buffer: number[] = []; |
| |
| constructor(private _maxBufferSize = 10) {} |
| |
| addValue(value: number) { |
| this._lastValue = value; |
| this.buffer.push(value); |
| if (this.buffer.length > this._maxBufferSize) { |
| this.buffer.shift(); |
| } |
| 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`; |
| } |
| |
| /** |
| * Globals singleton class that renders performance stats for the whole app. |
| */ |
| class PerfDisplay { |
| private containers: PanelContainer[] = []; |
| addContainer(container: PanelContainer) { |
| this.containers.push(container); |
| } |
| |
| removeContainer(container: PanelContainer) { |
| const i = this.containers.indexOf(container); |
| this.containers.splice(i, 1); |
| } |
| |
| renderPerfStats() { |
| if (!perfDebug()) return; |
| const perfDisplayEl = document.querySelector('.perf-stats'); |
| if (!perfDisplayEl) return; |
| m.render(perfDisplayEl, [ |
| m('section', globals.rafScheduler.renderPerfStats()), |
| m('button.close-button', |
| { |
| onclick: () => globals.frontendLocalState.togglePerfDebug(), |
| }, |
| m('i.material-icons', 'close')), |
| this.containers.map((c, i) => m('section', c.renderPerfStats(i))) |
| ]); |
| } |
| } |
| |
| export const perfDisplay = new PerfDisplay(); |