Index: tools/perf/page_sets/mse_cases/startup_test.js |
diff --git a/tools/perf/page_sets/mse_cases/startup_test.js b/tools/perf/page_sets/mse_cases/startup_test.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..de9923d392c5722096d2afb4ed8821bb6c97ba2e |
--- /dev/null |
+++ b/tools/perf/page_sets/mse_cases/startup_test.js |
@@ -0,0 +1,487 @@ |
+(function() { |
+ function getPerfTimestamp() { |
+ return performance.now(); |
+ } |
+ |
+ var pageStartTime = getPerfTimestamp(); |
+ var bodyLoadTime; |
+ var pageEndTime; |
+ |
+ function parseQueryParameters() { |
+ var params = {}; |
+ var r = /([^&=]+)=?([^&]*)/g; |
+ |
+ function d(s) { return decodeURIComponent(s.replace(/\+/g, ' ')); } |
+ |
+ var match; |
+ while (match = r.exec(window.location.search.substring(1))) |
+ params[d(match[1])] = d(match[2]); |
+ |
+ return params; |
+ } |
+ |
+ var testParams; |
+ function loadTestParams() { |
+ var queryParameters = parseQueryParameters(); |
+ testParams = {}; |
+ testParams.testType = queryParameters["testType"] || "AV"; |
+ testParams.useAppendStream = (queryParameters["useAppendStream"] == "true"); |
+ testParams.doNotWaitForBodyOnLoad = (queryParameters["doNotWaitForBodyOnLoad"] == "true"); |
+ testParams.startOffset = 0; |
+ testParams.appendSize = parseInt(queryParameters["appendSize"] || "65536"); |
+ testParams.graphDuration = parseInt(queryParameters["graphDuration"] || "1000"); |
+ } |
+ |
+ function plotTimestamps(timestamps, graphDuration, element) { |
+ var c = document.getElementById('c'); |
+ var ctx = c.getContext('2d'); |
+ |
+ var bars = [ |
+ { label: 'Page Load Total', |
+ start: pageStartTime, |
+ end: pageEndTime, |
+ color: '#404040' }, |
+ { label: 'body.onload Delay', |
+ start: pageStartTime, |
+ end: bodyLoadTime, |
+ color: '#808080' }, |
+ { label: 'Test Total', |
+ start: timestamps.testStartTime, |
+ end: timestamps.testEndTime, |
+ color: '#00FF00' }, |
+ { label: 'MediaSource opening', |
+ start: timestamps.mediaSourceOpenStartTime, |
+ end: timestamps.mediaSourceOpenEndTime, |
+ color: '#008800' } |
+ ]; |
+ |
+ var maxAppendEndTime = 0; |
+ for (var i = 0; i < timestamps.appenders.length; ++i) { |
+ var appender = timestamps.appenders[i]; |
+ bars.push({ label: 'XHR', |
+ start: appender.xhrStartTime, |
+ end: appender.xhrEndTime, |
+ color: '#0088FF' }); |
+ bars.push({ label: 'Append', |
+ start: appender.appendStartTime, |
+ end: appender.appendEndTime, |
+ color: '#00FFFF' }); |
+ if (appender.appendEndTime > maxAppendEndTime) { |
+ maxAppendEndTime = appender.appendEndTime; |
+ } |
+ } |
+ |
+ bars.push({label: 'Post Append Delay', start: maxAppendEndTime, end: timestamps.testEndTime, color: '#B0B0B0' }); |
+ |
+ var minTimestamp = Number.MAX_VALUE; |
+ for (var i = 0; i < bars.length; ++i) { |
+ minTimestamp = Math.min(minTimestamp, bars[i].start); |
+ } |
+ |
+ var graphWidth = c.width - 100; |
+ function convertTimestampToX(t) { |
+ return graphWidth * (t - minTimestamp) / graphDuration; |
+ } |
+ var y = 0; |
+ var barThickness = 20; |
+ c.height = bars.length * barThickness; |
+ ctx.font = (0.75 * barThickness) + 'px arial'; |
+ for (var i = 0; i < bars.length; ++i) { |
+ var bar = bars[i]; |
+ var xStart = convertTimestampToX(bar.start); |
+ var xEnd = convertTimestampToX(bar.end); |
+ ctx.fillStyle = bar.color; |
+ ctx.fillRect(xStart, y, xEnd - xStart, barThickness); |
+ |
+ ctx.fillStyle = 'black'; |
+ var text = bar.label + ' (' + (bar.end - bar.start).toFixed(3) + ' ms)'; |
+ ctx.fillText(text, xEnd + 10, y + (0.75 * barThickness)); |
+ y += barThickness; |
+ } |
+ reportTelemetryMediaMetrics(bars, element); |
+ } |
+ |
+ function displayResults(stats) { |
+ var statsDiv = document.getElementById('stats'); |
+ |
+ if (!stats) { |
+ statsDiv.innerHTML = "Test failed"; |
+ return; |
+ } |
+ |
+ var statsMarkup = "Test passed<br><table>"; |
+ for (var i in stats) { |
+ statsMarkup += "<tr><td style=\"text-align:right\">" + i + ":</td><td>" + stats[i].toFixed(3) + " ms</td>"; |
+ } |
+ statsMarkup += "</table>"; |
+ statsDiv.innerHTML = statsMarkup; |
+ } |
+ |
+ function reportTelemetryMediaMetrics(stats, element) { |
+ if (!stats || !window.__getMediaMetric) { |
+ console.error("Stats not collected or could not find getMediaMetric()."); |
+ return; |
+ } |
+ var metric = window.__getMediaMetric(element); |
+ if (!metric) { |
+ console.error("Can not report Telemetry media metrics."); |
+ return |
+ } |
+ for (var i = 0; i < stats.length; ++i) { |
+ var bar = stats[i]; |
+ var label = bar.label.toLowerCase().replace(/\s+|\./g, '_'); |
+ var value = (bar.end - bar.start).toFixed(3); |
+ console.log("appending to telemetry " + label + " : " + value); |
+ metric.appendMetric("mse_" + label, value); |
+ } |
+ } |
+ |
+ function updateControls(testParams) { |
+ var testTypeElement = document.getElementById("testType"); |
+ for (var i in testTypeElement.options) { |
+ var option = testTypeElement.options[i]; |
+ if (option.value == testParams.testType) { |
+ testTypeElement.selectedIndex = option.index; |
+ } |
+ } |
+ |
+ document.getElementById("useAppendStream").checked = testParams.useAppendStream; |
+ document.getElementById("doNotWaitForBodyOnLoad").checked = testParams.doNotWaitForBodyOnLoad; |
+ document.getElementById("appendSize").value = testParams.appendSize; |
+ document.getElementById("graphDuration").value = testParams.graphDuration; |
+ } |
+ |
+ function BufferAppender(mimetype, url, id, startOffset, appendSize) { |
+ this.mimetype = mimetype; |
+ this.url = url; |
+ this.id = id; |
+ this.startOffset = startOffset; |
+ this.appendSize = appendSize; |
+ this.xhr = new XMLHttpRequest(); |
+ this.sourceBuffer = null; |
+ } |
+ |
+ BufferAppender.prototype.start = function() { |
+ this.xhr.addEventListener('loadend', this.onLoadEnd.bind(this)); |
+ this.xhr.open('GET', this.url); |
+ this.xhr.setRequestHeader('Range', 'bytes=' + this.startOffset + '-' + |
+ (this.startOffset + this.appendSize - 1)); |
+ this.xhr.responseType = 'arraybuffer'; |
+ this.xhr.send(); |
+ |
+ this.xhrStartTime = getPerfTimestamp(); |
+ }; |
+ |
+ BufferAppender.prototype.onLoadEnd = function() { |
+ this.xhrEndTime = getPerfTimestamp(); |
+ this.attemptAppend(); |
+ }; |
+ |
+ BufferAppender.prototype.onSourceOpen = function(mediaSource) { |
+ if (this.sourceBuffer) |
+ return; |
+ this.sourceBuffer = mediaSource.addSourceBuffer(this.mimetype); |
+ }; |
+ |
+ BufferAppender.prototype.attemptAppend = function() { |
+ if (!this.xhr.response || !this.sourceBuffer) |
+ return; |
+ |
+ this.appendStartTime = getPerfTimestamp(); |
+ |
+ if (this.sourceBuffer.appendBuffer) { |
+ this.sourceBuffer.addEventListener('updateend', |
+ this.onUpdateEnd.bind(this)); |
+ this.sourceBuffer.appendBuffer(this.xhr.response); |
+ } else { |
+ this.sourceBuffer.append(new Uint8Array(this.xhr.response)); |
+ this.appendEndTime = getPerfTimestamp(); |
+ } |
+ |
+ this.xhr = null; |
+ }; |
+ |
+ BufferAppender.prototype.onUpdateEnd = function() { |
+ this.appendEndTime = getPerfTimestamp(); |
+ }; |
+ |
+ BufferAppender.prototype.onPlaybackStarted = function() { |
+ var now = getPerfTimestamp(); |
+ this.playbackStartTime = now; |
+ if (this.sourceBuffer.updating) { |
+ // Still appending but playback has already started so just abort the XHR |
+ // and append. |
+ this.sourceBuffer.abort(); |
+ this.xhr.abort(); |
+ } |
+ }; |
+ |
+ BufferAppender.prototype.getXHRLoadDuration = function() { |
+ return this.xhrEndTime - this.xhrStartTime; |
+ }; |
+ |
+ BufferAppender.prototype.getAppendDuration = function() { |
+ return this.appendEndTime - this.appendStartTime; |
+ }; |
+ |
+ function StreamAppender(mimetype, url, id, startOffset, appendSize) { |
+ this.mimetype = mimetype; |
+ this.url = url; |
+ this.id = id; |
+ this.startOffset = startOffset; |
+ this.appendSize = appendSize; |
+ this.xhr = new XMLHttpRequest(); |
+ this.sourceBuffer = null; |
+ this.appendStarted = false; |
+ } |
+ |
+ StreamAppender.prototype.start = function() { |
+ this.xhr.addEventListener('readystatechange', |
+ this.attemptAppend.bind(this)); |
+ this.xhr.addEventListener('loadend', this.onLoadEnd.bind(this)); |
+ this.xhr.open('GET', this.url); |
+ this.xhr.setRequestHeader('Range', 'bytes=' + this.startOffset + '-' + |
+ (this.startOffset + this.appendSize - 1)); |
+ this.xhr.responseType = 'stream'; |
+ if (this.xhr.responseType != 'stream') { |
+ throw "XHR does not support 'stream' responses."; |
+ } |
+ this.xhr.send(); |
+ |
+ this.xhrStartTime = getPerfTimestamp(); |
+ }; |
+ |
+ StreamAppender.prototype.onLoadEnd = function() { |
+ this.xhrEndTime = getPerfTimestamp(); |
+ this.attemptAppend(); |
+ }; |
+ |
+ StreamAppender.prototype.onSourceOpen = function(mediaSource) { |
+ if (this.sourceBuffer) |
+ return; |
+ this.sourceBuffer = mediaSource.addSourceBuffer(this.mimetype); |
+ }; |
+ |
+ StreamAppender.prototype.attemptAppend = function() { |
+ if (this.xhr.readyState < this.xhr.LOADING) { |
+ return; |
+ } |
+ |
+ if (!this.xhr.response || !this.sourceBuffer || this.appendStarted) |
+ return; |
+ |
+ this.appendStartTime = getPerfTimestamp(); |
+ this.appendStarted = true; |
+ this.sourceBuffer.addEventListener('updateend', |
+ this.onUpdateEnd.bind(this)); |
+ this.sourceBuffer.appendStream(this.xhr.response); |
+ }; |
+ |
+ StreamAppender.prototype.onUpdateEnd = function() { |
+ this.appendEndTime = getPerfTimestamp(); |
+ }; |
+ |
+ StreamAppender.prototype.onPlaybackStarted = function() { |
+ var now = getPerfTimestamp(); |
+ this.playbackStartTime = now; |
+ if (this.sourceBuffer.updating) { |
+ // Still appending but playback has already started so just abort the XHR |
+ // and append. |
+ this.sourceBuffer.abort(); |
+ this.xhr.abort(); |
+ if (!this.appendEndTime) |
+ this.appendEndTime = now; |
+ |
+ if (!this.xhrEndTime) |
+ this.xhrEndTime = now; |
+ } |
+ }; |
+ |
+ StreamAppender.prototype.getXHRLoadDuration = function() { |
+ return this.xhrEndTime - this.xhrStartTime; |
+ }; |
+ |
+ StreamAppender.prototype.getAppendDuration = function() { |
+ return this.appendEndTime - this.appendStartTime; |
+ }; |
+ |
+ // runAppendTest() sets testDone to true once all appends finish. |
+ var testDone = false; |
+ function runAppendTest(mediaElement, appenders, doneCallback) { |
+ var testStartTime = getPerfTimestamp(); |
+ var mediaSourceOpenStartTime; |
+ var mediaSourceOpenEndTime; |
+ |
+ for (var i = 0; i < appenders.length; ++i) { |
+ appenders[i].start(); |
+ } |
+ |
+ function onSourceOpen(event) { |
+ var mediaSource = event.target; |
+ |
+ mediaSourceOpenEndTime = getPerfTimestamp(); |
+ |
+ for (var i = 0; i < appenders.length; ++i) { |
+ appenders[i].onSourceOpen(mediaSource); |
+ } |
+ |
+ for (var i = 0; i < appenders.length; ++i) { |
+ appenders[i].attemptAppend(mediaSource); |
+ } |
+ |
+ mediaElement.play(); |
+ } |
+ |
+ var mediaSource; |
+ if (window['MediaSource']) { |
+ mediaSource = new window.MediaSource(); |
+ mediaSource.addEventListener('sourceopen', onSourceOpen); |
+ } else { |
+ mediaSource = new window.WebKitMediaSource(); |
+ mediaSource.addEventListener('webkitsourceopen', onSourceOpen); |
+ } |
+ |
+ var listener; |
+ var timeout; |
+ function checkForCurrentTimeChange() { |
+ if (testDone) |
+ return; |
+ |
+ if (mediaElement.readyState < mediaElement.HAVE_METADATA || |
+ mediaElement.currentTime <= 0) |
+ return; |
+ |
+ for (var i = 0; i < appenders.length; ++i) { |
+ appenders[i].onPlaybackStarted(mediaSource); |
+ } |
+ |
+ var testEndTime = getPerfTimestamp(); |
+ |
+ testDone = true; |
+ window.clearInterval(listener); |
+ window.clearTimeout(timeout); |
+ |
+ var stats = {}; |
+ stats.total = testEndTime - testStartTime; |
+ stats.sourceOpen = mediaSourceOpenEndTime - mediaSourceOpenStartTime; |
+ stats.maxXHRLoadDuration = appenders[0].getXHRLoadDuration(); |
+ stats.maxAppendDuration = appenders[0].getAppendDuration(); |
+ |
+ var timestamps = {}; |
+ timestamps.testStartTime = testStartTime; |
+ timestamps.testEndTime = testEndTime; |
+ timestamps.mediaSourceOpenStartTime = mediaSourceOpenStartTime; |
+ timestamps.mediaSourceOpenEndTime = mediaSourceOpenEndTime; |
+ timestamps.appenders = []; |
+ |
+ for (var i = 1; i < appenders.length; ++i) { |
+ var appender = appenders[i]; |
+ var xhrLoadDuration = appender.getXHRLoadDuration(); |
+ var appendDuration = appender.getAppendDuration(); |
+ |
+ if (xhrLoadDuration > stats.maxXHRLoadDuration) |
+ stats.maxXHRLoadDuration = xhrLoadDuration; |
+ |
+ if (appendDuration > stats.maxAppendDuration) |
+ stats.maxAppendDuration = appendDuration; |
+ } |
+ |
+ for (var i = 0; i < appenders.length; ++i) { |
+ var appender = appenders[i]; |
+ var appenderTimestamps = {}; |
+ appenderTimestamps.xhrStartTime = appender.xhrStartTime; |
+ appenderTimestamps.xhrEndTime = appender.xhrEndTime; |
+ appenderTimestamps.appendStartTime = appender.appendStartTime; |
+ appenderTimestamps.appendEndTime = appender.appendEndTime; |
+ appenderTimestamps.playbackStartTime = appender.playbackStartTime; |
+ timestamps.appenders.push(appenderTimestamps); |
+ } |
+ |
+ mediaElement.pause(); |
+ |
+ pageEndTime = getPerfTimestamp(); |
+ doneCallback(stats, timestamps); |
+ }; |
+ |
+ mediaElement.addEventListener('timeupdate', checkForCurrentTimeChange); |
+ |
+ listener = setInterval(checkForCurrentTimeChange, 15); |
+ timeout = setTimeout(function() { |
+ if (testDone) |
+ return; |
+ |
+ console.log('Test timed out.'); |
+ testDone = true; |
+ window.clearInterval(listener); |
+ |
+ mediaElement.pause(); |
+ doneCallback(null); |
+ }, 10000); |
+ |
+ mediaSourceOpenStartTime = getPerfTimestamp(); |
+ mediaElement.src = URL.createObjectURL(mediaSource); |
+ }; |
+ |
+ function onBodyLoad() { |
+ bodyLoadTime = getPerfTimestamp(); |
+ |
+ if (!testParams.doNotWaitForBodyOnLoad) { |
+ startTest(); |
+ } |
+ } |
+ |
+ function startTest() { |
+ updateControls(testParams); |
+ |
+ var appenders = []; |
+ |
+ if (useAppendStream && !window.MediaSource) |
+ throw "Can't use appendStream() because the unprefixed MediaSource object is not present."; |
+ |
+ var Appender = testParams.useAppendStream ? StreamAppender : BufferAppender; |
+ |
+ if (testParams.testType.indexOf("A") != -1) { |
+ appenders.push(new Appender("audio/mp4; codecs=\"mp4a.40.2\"", "audio.mp4", "a", testParams.startOffset, testParams.appendSize)); |
+ } |
+ |
+ if (testParams.testType.indexOf("V") != -1) { |
+ appenders.push(new Appender("video/mp4; codecs=\"avc1.640028\"", "video.mp4", "v", testParams.startOffset, testParams.appendSize)); |
+ } |
+ |
+ var video = document.getElementById('v'); |
+ video.id = getTestID(); |
+ runAppendTest(video, appenders, function(stats, timestamps) { |
+ displayResults(stats); |
+ plotTimestamps(timestamps, testParams.graphDuration, video); |
+ }); |
+ } |
+ |
+ function getTestID() { |
+ console.log("setting test ID") |
+ console.log(testParams.doNotWaitForBodyOnLoad) |
+ var id = testParams.testType; |
+ if (testParams.useAppendStream) |
+ id += "_stream" |
+ else |
+ id += "_buffer" |
+ if (testParams.doNotWaitForBodyOnLoad) |
+ id += "_pre_load" |
+ else |
+ id += "_post_load" |
+ return id; |
+ } |
+ |
+ function setupTest() { |
+ loadTestParams(); |
+ document.body.onload = onBodyLoad; |
+ |
+ if (testParams.doNotWaitForBodyOnLoad) { |
+ startTest(); |
+ } |
+ } |
+ |
+ window["setupTest"] = setupTest; |
+ window.__testDone = function() { |
+ return testDone; |
+ }; |
+})(); |