perf.js (5709B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 // Performance monitoring and calculation. 6 7 function round_up(val, interval) { 8 return val + (interval - (val % interval)); 9 } 10 11 // Class for inter-frame timing, which handles being paused and resumed. 12 var FrameTimer = class { 13 constructor() { 14 // Start time of the current active test, adjusted for any time spent 15 // stopped (so `now - this.start` is how long the current active test 16 // has run for.) 17 this.start = undefined; 18 19 // Timestamp of callback following the previous frame. 20 this.prev = undefined; 21 22 // Timestamp when drawing was paused, or zero if drawing is active. 23 this.stopped = 0; 24 } 25 26 is_stopped() { 27 return this.stopped != 0; 28 } 29 30 start_recording(now = gHost.now()) { 31 this.start = this.prev = now; 32 } 33 34 on_frame_finished(now = gHost.now()) { 35 const delay = now - this.prev; 36 this.prev = now; 37 return delay; 38 } 39 40 pause(now = gHost.now()) { 41 this.stopped = now; 42 // Abuse this.prev to store the time elapsed since the previous frame. 43 // This will be used to adjust this.prev when we resume. 44 this.prev = now - this.prev; 45 } 46 47 resume(now = gHost.now()) { 48 this.prev = now - this.prev; 49 const stop_duration = now - this.stopped; 50 this.start += stop_duration; 51 this.stopped = 0; 52 } 53 }; 54 55 // Per-frame time sampling infra. 56 var sampleTime = 16.666667; // ms 57 var sampleIndex = 0; 58 59 // Class for maintaining a rolling window of per-frame GC-related counters: 60 // inter-frame delay, minor/major/slice GC counts, cumulative bytes, etc. 61 var FrameHistory = class { 62 constructor(numSamples) { 63 // Private 64 this._frameTimer = new FrameTimer(); 65 this._numSamples = numSamples; 66 67 // Public API 68 this.delays = new Array(numSamples); 69 this.gcBytes = new Array(numSamples); 70 this.mallocBytes = new Array(numSamples); 71 this.gcs = new Array(numSamples); 72 this.minorGCs = new Array(numSamples); 73 this.majorGCs = new Array(numSamples); 74 this.slices = new Array(numSamples); 75 76 sampleIndex = 0; 77 this.reset(); 78 } 79 80 start(now = gHost.now()) { 81 this._frameTimer.start_recording(now); 82 } 83 84 reset() { 85 this.delays.fill(0); 86 this.gcBytes.fill(0); 87 this.mallocBytes.fill(0); 88 this.gcs.fill(this.gcs[sampleIndex]); 89 this.minorGCs.fill(this.minorGCs[sampleIndex]); 90 this.majorGCs.fill(this.majorGCs[sampleIndex]); 91 this.slices.fill(this.slices[sampleIndex]); 92 93 sampleIndex = 0; 94 } 95 96 get numSamples() { 97 return this._numSamples; 98 } 99 100 findMax(collection) { 101 // Depends on having at least one non-negative entry, and unfilled 102 // entries being <= max. 103 var maxIndex = 0; 104 for (let i = 0; i < this._numSamples; i++) { 105 if (collection[i] >= collection[maxIndex]) { 106 maxIndex = i; 107 } 108 } 109 return maxIndex; 110 } 111 112 findMaxDelay() { 113 return this.findMax(this.delays); 114 } 115 116 on_frame(now = gHost.now()) { 117 const delay = this._frameTimer.on_frame_finished(now); 118 119 // Total time elapsed while the active test has been running. 120 var t = now - this._frameTimer.start; 121 var newIndex = Math.round(t / sampleTime); 122 while (sampleIndex < newIndex) { 123 sampleIndex++; 124 var idx = sampleIndex % this._numSamples; 125 this.delays[idx] = delay; 126 if (gHost.features.haveMemorySizes) { 127 this.gcBytes[idx] = gHost.gcBytes; 128 this.mallocBytes[idx] = gHost.mallocBytes; 129 } 130 if (gHost.features.haveGCCounts) { 131 this.minorGCs[idx] = gHost.minorGCCount; 132 this.majorGCs[idx] = gHost.majorGCCount; 133 this.slices[idx] = gHost.GCSliceCount; 134 } 135 } 136 137 return delay; 138 } 139 140 pause() { 141 this._frameTimer.pause(); 142 } 143 144 resume() { 145 this._frameTimer.resume(); 146 } 147 148 is_stopped() { 149 return this._frameTimer.is_stopped(); 150 } 151 }; 152 153 var PerfTracker = class { 154 constructor() { 155 // Private 156 this._currentLoadStart = undefined; 157 this._frameCount = undefined; 158 this._mutating_ms = undefined; 159 this._suspend_sec = undefined; 160 this._minorGCs = undefined; 161 this._majorGCs = undefined; 162 163 // Public 164 this.results = []; 165 } 166 167 on_load_start(load, now = gHost.now()) { 168 this._currentLoadStart = now; 169 this._frameCount = 0; 170 this._mutating_ms = 0; 171 this._suspend_sec = 0; 172 this._majorGCs = gHost.majorGCCount; 173 this._minorGCs = gHost.minorGCCount; 174 } 175 176 on_load_end(load, now = gHost.now()) { 177 const elapsed_time = (now - this._currentLoadStart) / 1000; 178 const full_time = round_up(elapsed_time, 1 / 60); 179 const frame_60fps_limit = Math.round(full_time * 60); 180 const dropped_60fps_frames = frame_60fps_limit - this._frameCount; 181 const dropped_60fps_fraction = dropped_60fps_frames / frame_60fps_limit; 182 183 const mutating_and_gc_fraction = this._mutating_ms / (full_time * 1000); 184 185 const result = { 186 load, 187 elapsed_time, 188 mutating: this._mutating_ms / 1000, 189 mutating_and_gc_fraction, 190 suspended: this._suspend_sec, 191 full_time, 192 frames: this._frameCount, 193 dropped_60fps_frames, 194 dropped_60fps_fraction, 195 majorGCs: gHost.majorGCCount - this._majorGCs, 196 minorGCs: gHost.minorGCCount - this._minorGCs, 197 }; 198 this.results.push(result); 199 200 this._currentLoadStart = undefined; 201 this._frameCount = 0; 202 203 return result; 204 } 205 206 after_suspend(wait_sec) { 207 this._suspend_sec += wait_sec; 208 } 209 210 before_mutator(now = gHost.now()) { 211 this._frameCount++; 212 } 213 214 after_mutator(start_time, end_time = gHost.now()) { 215 this._mutating_ms += end_time - start_time; 216 } 217 };