Skip to content

Instantly share code, notes, and snippets.

@jeffreytgilbert
Created September 8, 2014 20:27
Show Gist options
  • Save jeffreytgilbert/5b3a7454e21eb5f3a9a7 to your computer and use it in GitHub Desktop.
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.
<!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">&nbsp;</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