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