Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(152)

Unified Diff: tools/perf/page_sets/mse_cases/startup_test.js

Issue 71303007: Add MSE perf Telemetry tests. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Add perf metrics reporting Created 7 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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;
+ };
+})();
« tools/perf/page_sets/mse_cases.json ('K') | « tools/perf/page_sets/mse_cases/startup_test.html ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698