| 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;
|
| + };
|
| +})();
|
|
|