Created
September 8, 2014 20:27
-
-
Save jeffreytgilbert/5b3a7454e21eb5f3a9a7 to your computer and use it in GitHub Desktop.
Framed documents which try and force redraws in the browser and then measure the rates at which they redraw to discover discrepancies in browser behavior.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<head> | |
<link href="test-styles.css" rel="stylesheet" type="text/css"> | |
<style> | |
body { | |
margin:0; | |
padding:0; | |
background-color:#ff99ff; | |
} | |
progress[value]::-webkit-progress-value { | |
background-image: | |
-webkit-linear-gradient(-45deg, | |
transparent 33%, rgba(0, 0, 0, .1) 33%, | |
rgba(0,0, 0, .1) 66%, transparent 66%), | |
-webkit-linear-gradient(top, | |
rgba(255, 255, 255, .25), | |
rgba(0, 0, 0, .25)), | |
-webkit-linear-gradient(left, #09c, #f44); | |
border-radius: 2px; | |
background-size: 35px 20px, 100% 100%, 100% 100%; | |
-webkit-transition: width 5s; | |
transition: width 5s; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="force-change"> </div> | |
<div id="test">0.0</div> | |
<progress id="progressBar" max="100" value="0"></progress> | |
<table width="400"> | |
<tr valign="top"> | |
<td> | |
raf | |
</td> | |
<td> | |
setInterval | |
</td> | |
<td> | |
setTimeout | |
</td> | |
<td> | |
frames drawn | |
</td> | |
<td> | |
frames available | |
</td> | |
</tr> | |
<tr valign="top"> | |
<td id="rafReport"> | |
</td> | |
<td id="intervalReport"> | |
</td> | |
<td id="timeoutReport"> | |
</td> | |
<td id="framesDrawn"> | |
</td> | |
<td id="framesAvailable"> | |
</td> | |
</tr> | |
</table> | |
<script> | |
//try { | |
var FRAME_COUNT = 0; | |
function now () { | |
// return (!!performance && performance.now) ? performance.now() : new Date.time(); | |
return new Date().getTime(); | |
} | |
var xElement = null, | |
startTime = now(), | |
w = window, | |
d = document, | |
frames = 0, | |
_framesDrawnReport = d.getElementById('framesDrawn'), | |
_framesAvailableReport = d.getElementById('framesAvailable'), | |
// raf fallback is a timeout at 60 fps | |
raf = w.requestAnimationFrame || | |
w.webkitRequestAnimationFrame || | |
w.mozRequestAnimationFrame || function( callback ){ | |
w.setTimeout(callback, 1000 / 60); | |
}; | |
MeasureIntervalSpeed = function (resultsDOM, intervalTime) { | |
var self = this; | |
self.measure = function () { | |
var timeBetweenFrames = now() - lastTime; | |
lastTime = now(); | |
if(timeBetweenFrames < 1500){ | |
countOfIntervals[timeBetweenFrames] = !!countOfIntervals[timeBetweenFrames] ? countOfIntervals[timeBetweenFrames] + 1 : 1; | |
} | |
}; | |
var | |
lastTime = 0, | |
countOfIntervals = {}; | |
w.setInterval(function () { | |
var arrayOfIntervalCounts = []; | |
for (var index in countOfIntervals) { | |
arrayOfIntervalCounts.push({ time: index, count: countOfIntervals[index] }); | |
} | |
arrayOfIntervalCounts.sort(function (a, b) { | |
return b.count - a.count; | |
}); | |
resultsDOM.innerHTML = ''; | |
var x = 0; | |
while (x < arrayOfIntervalCounts.length) { | |
var intervalCount = arrayOfIntervalCounts[x]; | |
var div = d.createElement('div'); | |
div.innerHTML = intervalCount.count + ': ' + intervalCount.time; | |
resultsDOM.appendChild(div); | |
x++; | |
} | |
// arrayOfIntervalCounts.forEach(function(intervalCount){ | |
// var div = d.createElement('div'); | |
// div.innerHTML = intervalCount.count + ': ' + intervalCount.time; | |
// resultsDOM.appendChild(div); | |
// }); | |
}, intervalTime); | |
}; | |
var rafMeasure = new MeasureIntervalSpeed(d.getElementById('rafReport'), 1000); | |
var intervalMeasure = new MeasureIntervalSpeed(d.getElementById('intervalReport'), 1000); | |
var timeoutMeasure = new MeasureIntervalSpeed(d.getElementById('timeoutReport'), 1000); | |
rafMeasure.measure(); | |
timeoutMeasure.measure(); | |
xElement = d.getElementById('test'); | |
var rafWrapper = function () { | |
FRAME_COUNT += 1; | |
_framesDrawnReport.innerHTML = FRAME_COUNT; | |
_framesAvailableReport.innerHTML = (((new Date().getTime() - startTime) / 1000)*60).toFixed(0); | |
rafMeasure.measure(); | |
if (done) { return; } | |
raf(rafWrapper); | |
}; | |
var timeoutWrapper = function () { | |
timeoutMeasure.measure(); | |
if (done) { return; } | |
setTimeout(function(){ | |
timeoutWrapper(); | |
}, 1); | |
}; | |
var progressBar = d.getElementById('progressBar'); | |
var done = false; | |
var millisecondTimer = function(){ | |
var pct = ((now() - startTime)/60000)*100; | |
progressBar.setAttribute('value',pct); | |
if(pct > 100 && pct < 120) { done = true; } | |
xElement.innerHTML = ((now() - startTime)/1000).toFixed(3); | |
if (done) { return; } | |
w.setTimeout(function () { | |
millisecondTimer(); | |
},16); | |
// raf(millisecondTimer); | |
}; | |
// raf(millisecondTimer); | |
raf(rafWrapper); | |
setTimeout(timeoutWrapper, 1); | |
millisecondTimer(); | |
var invervalId = w.setInterval(function(){ | |
intervalMeasure.measure(); | |
if (done) { return w.clearInterval(invervalId); } | |
}, 1); | |
// var Interval = w.setInterval(startWatch, 1); | |
// function startWatch(){ | |
// frames = frames + 1; | |
// } | |
// function setDomain() { | |
// var dd = document.domain, dn = 'dotomi.com'; | |
// if (dd.indexOf(dn) == -1) { | |
// dn = ''; | |
// var dtm_match = new RegExp("[^.]+\.[^.]+$").exec(dd); | |
// if (dtm_match != null) { | |
// for (var i = 0; i < dtm_match.length; i++) { | |
// dn = dn + dtm_match[i]; | |
// } | |
// } | |
// } | |
// if (dn !== '') | |
// document.domain = dn; | |
// } | |
// try { | |
// setDomain(); | |
// } catch (e) { | |
// console.log(e); | |
// } | |
var MeasureFPS = function (stageFPS, measureEveryXFrames, _updateFpsCb, _updateMaxFpsCb) { | |
var _self = this, | |
_framesThisInterval = 0, | |
_prevTimer = 0, | |
_nextTimer = 0, | |
_curTimer = 0, | |
_fps = 0, | |
_running = false, | |
_stageFPS = stageFPS > 15 ? parseInt(stageFPS, 10) : 60, | |
_frameTime = 10, | |
_measurementInterval = 100, | |
_measureEveryXFrames = measureEveryXFrames > 3 ? parseInt(measureEveryXFrames, 10) : 5, | |
_intervalCheck, | |
_redrawClock, | |
_maxFPS = 0, | |
_lastFrame = 0, | |
_FPSSamples = [0,0], // max fps as measured by this script while measuring | |
_pageObject = d.getElementById('force-change'); | |
_self.updateFpsCb = _updateFpsCb || function (fps) { console.log('FPS: ' + fps); }; | |
_self.updateMaxFpsCb = _updateMaxFpsCb || function (maxFps) { console.log('Max FPS: ' + maxFps); }; | |
// console.log(_self.updateFpsCb, _self.updateMaxFpsCb); | |
_self.init = function() { | |
_frameTime = 1000 / _stageFPS; // 1 second in milliseconds / max frame rate = frameTime | |
_measurementInterval = _measureEveryXFrames * _frameTime; // number of frames in average frame time before an fps measurement is taken | |
}; | |
_self.stop = function() { | |
if (_running) { | |
clearInterval(_intervalCheck); | |
_running = false; | |
} | |
}; | |
_self.start = function() { | |
// console.log('JS FPS timer starting'); | |
if (!_running) { | |
// console.log('JS FPS timer started'); | |
_framesThisInterval = 0; | |
_prevTimer = 0; | |
_nextTimer = 0; | |
_curTimer = 0; | |
_fps = 0; | |
_lastFrame = w.mozPaintCount; | |
// force this method to fire when a frame is drawn | |
w.requestAnimationFrame(_self.addAnotherFrame); | |
// check the number of redraws around this time | |
_intervalCheck = setInterval(function () { | |
_self.performanceCheck(); | |
}, _measurementInterval); | |
// force the redraw of a frame at least this often | |
_redrawClock = setInterval(function () { | |
_self.forceRedraw(); | |
}, _frameTime); | |
_running = true; | |
} | |
}; | |
_self.currentFPS = function() { | |
return _fps; | |
}; | |
_self.maxFPS = function() { | |
return _maxFPS; | |
}; | |
_self.addAnotherFrame = function() { | |
_framesThisInterval += w.mozPaintCount - _lastFrame; | |
_lastFrame = w.mozPaintCount; | |
// console.log(_framesThisInterval); | |
if (_running) { | |
w.requestAnimationFrame(_self.addAnotherFrame); | |
} | |
}; | |
_self.forceRedraw = function () { | |
_pageObject.setAttribute('style','width: ' + Math.floor(Math.random()*100) + '%; height: ' + Math.floor(Math.random()*100) + '%; line-height: ' + Math.floor(Math.random()*10) + 'px'); | |
_pageObject.innerHTML = Math.floor(Math.random()*100); | |
}; | |
_self.performanceCheck = function() { | |
_curTimer = Date.now(); | |
// if (_curTimer >= _nextTimer) { // throttle the fps event updates to only fire every x seconds, limited by framerate. | |
// (_framesThisInterval / _measureEveryXFrames) // percentage of completeness in comparison to the guestimated throttle | |
// (_curTimer - _prevTimer) // elapsed time | |
// 1000 // total time in a second | |
// frames over this amount of time | |
// | |
// Solution: | |
// frames (1000 / partial second) * frames | |
// ------ = ------------------------------------ | |
// second (1000 / partial second) * partial second | |
// | |
// Partial formula: | |
// 1 second | |
// ----------- = how many more intervals do we need to measure fps | |
// time passed | |
// | |
// Proof: | |
// frames drawn x intervals | |
_fps = _framesThisInterval * // frames elapsed | |
( | |
// 1 sec / elapsed time | |
(1000 / (_curTimer - _prevTimer)) // how many partials go into a full second (as a measure of completeness) | |
); | |
// console.log([((_curTimer - _prevTimer) / 1000), _framesThisInterval, (_curTimer - _prevTimer)]); | |
// console.log('FPS: ' + _fps); | |
_self.updateFpsCb(_fps); | |
if(_fps > _maxFPS) { | |
var hi = _fps + 5, | |
lo = _fps - 5; | |
if( | |
_FPSSamples[0] > lo && _FPSSamples[0] < hi && | |
_FPSSamples[1] > lo && _FPSSamples[1] < hi | |
){ | |
_maxFPS = _fps; | |
// console.log('New max FPS: ' + _maxFPS); | |
_self.updateMaxFpsCb(_maxFPS); | |
} | |
} | |
// Why avg fps can't work as a outlier buster | |
//var avgFPS:uint = Math.round((_FPSSamples[0] + _FPSSamples[1] + _fps) / 3); | |
// 50 + 50 + 140 / 3 = 80 | |
// 50 + 50 + 60 / 3 = 53.3 | |
// 45 + 55 + 65 / 3 = 55 | |
// 0 + 55 + 140 / 3 = 65 | |
_FPSSamples[0] = _FPSSamples[1]; | |
_FPSSamples[1] = _fps; | |
_prevTimer = _curTimer; | |
// _nextTimer = _curTimer + _measurementInterval; // measure the fps every x frames | |
_framesThisInterval = 0; | |
}; | |
}; | |
var fpsUpdateHandler = function (fps) { | |
if (fps > 5) { | |
if (inViewStatus === 'out' || inViewStatus === 'na') { | |
inViewStatus = 'in'; | |
// console.log('reporter is saying it is in view'); | |
report(instanceName, 'in'); | |
} | |
} else { | |
if (inViewStatus === 'in' || inViewStatus === 'na') { | |
inViewStatus = 'out'; | |
// console.log('reporter is saying it is out of view'); | |
report(instanceName, 'out'); | |
} | |
} | |
}; | |
var maxFpsUpdateHandler = function (maxFps) { | |
}; | |
var startInViewReporter = function () { | |
if (instanceName === '') { return ; } | |
FPSMeter.start(); | |
}; | |
var stopInViewReporter = function () { | |
FPSMeter.stop(); | |
}; | |
var | |
w = window, | |
d = w.document, | |
isRunning = false, | |
inViewStatus = 'na', | |
setIntervalInstance, | |
instanceName = location.href.split('?')[1], | |
report = function () { | |
// console.warn('Coult not find vision reporter.'); | |
}; | |
var FPSMeter = new MeasureFPS(60, 5, fpsUpdateHandler, maxFpsUpdateHandler); | |
FPSMeter.init(); | |
report(instanceName, 'ready'); | |
//} catch (e) { | |
// console.error(e); | |
//} | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment