282bde7ce5fca1c43506e5faca991c9963491ce7
[platform/framework/web/crosswalk.git] / src / tools / perf / page_sets / mse_cases / startup_test.js
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5
6 // The file runs a series of Media Source Entensions (MSE) operations on a
7 // video tag.  The test takes several URL parameters described in
8 //loadTestParams() function.
9
10 (function() {
11   function getPerfTimestamp() {
12     return performance.now();
13   }
14
15   var pageStartTime = getPerfTimestamp();
16   var bodyLoadTime;
17   var pageEndTime;
18
19   function parseQueryParameters() {
20     var params = {};
21     var r = /([^&=]+)=?([^&]*)/g;
22
23     function d(s) { return decodeURIComponent(s.replace(/\+/g, ' ')); }
24
25     var match;
26     while (match = r.exec(window.location.search.substring(1)))
27       params[d(match[1])] = d(match[2]);
28
29     return params;
30   }
31
32   var testParams;
33   function loadTestParams() {
34     var queryParameters = parseQueryParameters();
35     testParams = {};
36     testParams.testType = queryParameters["testType"] || "AV";
37     testParams.useAppendStream = (queryParameters["useAppendStream"] == "true");
38     testParams.doNotWaitForBodyOnLoad =
39         (queryParameters["doNotWaitForBodyOnLoad"] == "true");
40     testParams.startOffset = 0;
41     testParams.appendSize = parseInt(queryParameters["appendSize"] || "65536");
42     testParams.graphDuration =
43         parseInt(queryParameters["graphDuration"] || "1000");
44   }
45
46   function plotTimestamps(timestamps, graphDuration, element) {
47     if (!timestamps)
48       return;
49     var c = document.getElementById('c');
50     var ctx = c.getContext('2d');
51
52     var bars = [
53       { label: 'Page Load Total',
54         start: pageStartTime,
55         end: pageEndTime,
56         color: '#404040' },
57       { label: 'body.onload Delay',
58         start: pageStartTime,
59         end: bodyLoadTime,
60         color: '#808080' },
61       { label: 'Test Total',
62         start: timestamps.testStartTime,
63         end: timestamps.testEndTime,
64         color: '#00FF00' },
65       { label: 'MediaSource opening',
66         start: timestamps.mediaSourceOpenStartTime,
67         end: timestamps.mediaSourceOpenEndTime,
68         color: '#008800' }
69     ];
70
71     var maxAppendEndTime = 0;
72     for (var i = 0; i < timestamps.appenders.length; ++i) {
73       var appender = timestamps.appenders[i];
74       bars.push({ label: 'XHR',
75                   start: appender.xhrStartTime,
76                   end: appender.xhrEndTime,
77                   color: '#0088FF' });
78       bars.push({ label: 'Append',
79                   start: appender.appendStartTime,
80                   end: appender.appendEndTime,
81                   color: '#00FFFF' });
82       if (appender.appendEndTime > maxAppendEndTime) {
83         maxAppendEndTime = appender.appendEndTime;
84       }
85     }
86
87     bars.push({
88         label: 'Post Append Delay',
89         start: maxAppendEndTime,
90         end: timestamps.testEndTime,
91         color: '#B0B0B0' });
92
93     var minTimestamp = Number.MAX_VALUE;
94     for (var i = 0; i < bars.length; ++i) {
95       minTimestamp = Math.min(minTimestamp, bars[i].start);
96     }
97
98     var graphWidth = c.width - 100;
99     function convertTimestampToX(t) {
100       return graphWidth * (t - minTimestamp) / graphDuration;
101     }
102     var y = 0;
103     var barThickness = 20;
104     c.height = bars.length * barThickness;
105     ctx.font = (0.75 * barThickness) + 'px arial';
106     for (var i = 0; i < bars.length; ++i) {
107       var bar = bars[i];
108       var xStart = convertTimestampToX(bar.start);
109       var xEnd = convertTimestampToX(bar.end);
110       ctx.fillStyle = bar.color;
111       ctx.fillRect(xStart, y, xEnd - xStart, barThickness);
112
113       ctx.fillStyle = 'black';
114       var text = bar.label + ' (' + (bar.end - bar.start).toFixed(3) + ' ms)';
115       ctx.fillText(text, xEnd + 10, y + (0.75 * barThickness));
116       y += barThickness;
117     }
118     reportTelemetryMediaMetrics(bars, element);
119   }
120
121   function displayResults(stats) {
122     var statsDiv = document.getElementById('stats');
123
124     if (!stats) {
125       statsDiv.innerHTML = "Test failed";
126       return;
127     }
128
129     var statsMarkup = "Test passed<br><table>";
130     for (var i in stats) {
131       statsMarkup += "<tr><td style=\"text-align:right\">" + i + ":</td><td>" +
132                      stats[i].toFixed(3) + " ms</td>";
133     }
134     statsMarkup += "</table>";
135     statsDiv.innerHTML = statsMarkup;
136   }
137
138   function reportTelemetryMediaMetrics(stats, element) {
139     var metrics = {};
140     for (var i = 0; i < stats.length; ++i) {
141       var bar = stats[i];
142       var label = bar.label.toLowerCase().replace(/\s+|\./g, '_');
143       var value =  (bar.end - bar.start).toFixed(3);
144       console.log("appending to telemetry " + label + " : "  + value);
145       _AppendMetric(metrics, label, value);
146     }
147     window.__testMetrics = {
148       "id": element.id,
149       "metrics": metrics
150     };
151   }
152
153   function _AppendMetric(metrics, metric, value) {
154     if (!metrics[metric])
155       metrics[metric] = [];
156     metrics[metric].push(value);
157   }
158
159   function updateControls(testParams) {
160     var testTypeElement = document.getElementById("testType");
161     for (var i in testTypeElement.options) {
162       var option = testTypeElement.options[i];
163       if (option.value == testParams.testType) {
164         testTypeElement.selectedIndex = option.index;
165       }
166     }
167
168     document.getElementById("useAppendStream").checked =
169         testParams.useAppendStream;
170     document.getElementById("doNotWaitForBodyOnLoad").checked =
171         testParams.doNotWaitForBodyOnLoad;
172     document.getElementById("appendSize").value = testParams.appendSize;
173     document.getElementById("graphDuration").value = testParams.graphDuration;
174   }
175
176   function BufferAppender(mimetype, url, id, startOffset, appendSize) {
177     this.mimetype = mimetype;
178     this.url = url;
179     this.id = id;
180     this.startOffset = startOffset;
181     this.appendSize = appendSize;
182     this.xhr = new XMLHttpRequest();
183     this.sourceBuffer = null;
184   }
185
186   BufferAppender.prototype.start = function() {
187     this.xhr.addEventListener('loadend', this.onLoadEnd.bind(this));
188     this.xhr.open('GET', this.url);
189     this.xhr.setRequestHeader('Range', 'bytes=' + this.startOffset + '-' +
190                               (this.startOffset + this.appendSize - 1));
191     this.xhr.responseType = 'arraybuffer';
192     this.xhr.send();
193
194     this.xhrStartTime = getPerfTimestamp();
195   };
196
197   BufferAppender.prototype.onLoadEnd = function() {
198     this.xhrEndTime = getPerfTimestamp();
199     this.attemptAppend();
200   };
201
202   BufferAppender.prototype.onSourceOpen = function(mediaSource) {
203     if (this.sourceBuffer)
204       return;
205     this.sourceBuffer = mediaSource.addSourceBuffer(this.mimetype);
206   };
207
208   BufferAppender.prototype.attemptAppend = function() {
209     if (!this.xhr.response || !this.sourceBuffer)
210       return;
211
212     this.appendStartTime = getPerfTimestamp();
213
214     if (this.sourceBuffer.appendBuffer) {
215       this.sourceBuffer.addEventListener('updateend',
216                                          this.onUpdateEnd.bind(this));
217       this.sourceBuffer.appendBuffer(this.xhr.response);
218     } else {
219       this.sourceBuffer.append(new Uint8Array(this.xhr.response));
220       this.appendEndTime = getPerfTimestamp();
221     }
222
223     this.xhr = null;
224   };
225
226   BufferAppender.prototype.onUpdateEnd = function() {
227     this.appendEndTime = getPerfTimestamp();
228   };
229
230   BufferAppender.prototype.onPlaybackStarted = function() {
231     var now = getPerfTimestamp();
232     this.playbackStartTime = now;
233     if (this.sourceBuffer.updating) {
234       // Still appending but playback has already started so just abort the XHR
235       // and append.
236       this.sourceBuffer.abort();
237       this.xhr.abort();
238     }
239   };
240
241   BufferAppender.prototype.getXHRLoadDuration = function() {
242     return this.xhrEndTime - this.xhrStartTime;
243   };
244
245   BufferAppender.prototype.getAppendDuration = function() {
246     return this.appendEndTime - this.appendStartTime;
247   };
248
249   function StreamAppender(mimetype, url, id, startOffset, appendSize) {
250     this.mimetype = mimetype;
251     this.url = url;
252     this.id = id;
253     this.startOffset = startOffset;
254     this.appendSize = appendSize;
255     this.xhr = new XMLHttpRequest();
256     this.sourceBuffer = null;
257     this.appendStarted = false;
258   }
259
260   StreamAppender.prototype.start = function() {
261     this.xhr.addEventListener('readystatechange',
262                               this.attemptAppend.bind(this));
263     this.xhr.addEventListener('loadend', this.onLoadEnd.bind(this));
264     this.xhr.open('GET', this.url);
265     this.xhr.setRequestHeader('Range', 'bytes=' + this.startOffset + '-' +
266                               (this.startOffset + this.appendSize - 1));
267     this.xhr.responseType = 'stream';
268     if (this.xhr.responseType != 'stream') {
269       EndTest("XHR does not support 'stream' responses.");
270     }
271     this.xhr.send();
272
273     this.xhrStartTime = getPerfTimestamp();
274   };
275
276   StreamAppender.prototype.onLoadEnd = function() {
277     this.xhrEndTime = getPerfTimestamp();
278     this.attemptAppend();
279   };
280
281   StreamAppender.prototype.onSourceOpen = function(mediaSource) {
282     if (this.sourceBuffer)
283       return;
284     this.sourceBuffer = mediaSource.addSourceBuffer(this.mimetype);
285   };
286
287   StreamAppender.prototype.attemptAppend = function() {
288     if (this.xhr.readyState < this.xhr.LOADING) {
289       return;
290     }
291
292     if (!this.xhr.response || !this.sourceBuffer || this.appendStarted)
293       return;
294
295     this.appendStartTime = getPerfTimestamp();
296     this.appendStarted = true;
297     this.sourceBuffer.addEventListener('updateend',
298                                        this.onUpdateEnd.bind(this));
299     this.sourceBuffer.appendStream(this.xhr.response);
300   };
301
302   StreamAppender.prototype.onUpdateEnd = function() {
303     this.appendEndTime = getPerfTimestamp();
304   };
305
306   StreamAppender.prototype.onPlaybackStarted = function() {
307     var now = getPerfTimestamp();
308     this.playbackStartTime = now;
309     if (this.sourceBuffer.updating) {
310       // Still appending but playback has already started so just abort the XHR
311       // and append.
312       this.sourceBuffer.abort();
313       this.xhr.abort();
314       if (!this.appendEndTime)
315         this.appendEndTime = now;
316
317       if (!this.xhrEndTime)
318         this.xhrEndTime = now;
319     }
320   };
321
322   StreamAppender.prototype.getXHRLoadDuration = function() {
323     return this.xhrEndTime - this.xhrStartTime;
324   };
325
326   StreamAppender.prototype.getAppendDuration = function() {
327     return this.appendEndTime - this.appendStartTime;
328   };
329
330   // runAppendTest() sets testDone to true once all appends finish.
331   var testDone = false;
332   function runAppendTest(mediaElement, appenders, doneCallback) {
333     var testStartTime = getPerfTimestamp();
334     var mediaSourceOpenStartTime;
335     var mediaSourceOpenEndTime;
336
337     for (var i = 0; i < appenders.length; ++i) {
338       appenders[i].start();
339     }
340
341     function onSourceOpen(event) {
342       var mediaSource = event.target;
343
344       mediaSourceOpenEndTime = getPerfTimestamp();
345
346       for (var i = 0; i < appenders.length; ++i) {
347         appenders[i].onSourceOpen(mediaSource);
348       }
349
350       for (var i = 0; i < appenders.length; ++i) {
351         appenders[i].attemptAppend(mediaSource);
352       }
353
354       mediaElement.play();
355     }
356
357     var mediaSource;
358     if (window['MediaSource']) {
359       mediaSource = new window.MediaSource();
360       mediaSource.addEventListener('sourceopen', onSourceOpen);
361     } else {
362       mediaSource = new window.WebKitMediaSource();
363       mediaSource.addEventListener('webkitsourceopen', onSourceOpen);
364     }
365
366     var listener;
367     var timeout;
368     function checkForCurrentTimeChange() {
369       if (testDone)
370         return;
371
372       if (mediaElement.readyState < mediaElement.HAVE_METADATA ||
373           mediaElement.currentTime <= 0) {
374         listener = window.requestAnimationFrame(checkForCurrentTimeChange);
375         return;
376       }
377
378       var testEndTime = getPerfTimestamp();
379       for (var i = 0; i < appenders.length; ++i) {
380         appenders[i].onPlaybackStarted(mediaSource);
381       }
382
383       testDone = true;
384       window.clearInterval(listener);
385       window.clearTimeout(timeout);
386
387       var stats = {};
388       stats.total = testEndTime - testStartTime;
389       stats.sourceOpen = mediaSourceOpenEndTime - mediaSourceOpenStartTime;
390       stats.maxXHRLoadDuration = appenders[0].getXHRLoadDuration();
391       stats.maxAppendDuration = appenders[0].getAppendDuration();
392
393       var timestamps = {};
394       timestamps.testStartTime = testStartTime;
395       timestamps.testEndTime = testEndTime;
396       timestamps.mediaSourceOpenStartTime = mediaSourceOpenStartTime;
397       timestamps.mediaSourceOpenEndTime = mediaSourceOpenEndTime;
398       timestamps.appenders = [];
399
400       for (var i = 1; i < appenders.length; ++i) {
401         var appender = appenders[i];
402         var xhrLoadDuration = appender.getXHRLoadDuration();
403         var appendDuration = appender.getAppendDuration();
404
405         if (xhrLoadDuration > stats.maxXHRLoadDuration)
406           stats.maxXHRLoadDuration = xhrLoadDuration;
407
408         if (appendDuration > stats.maxAppendDuration)
409           stats.maxAppendDuration = appendDuration;
410       }
411
412       for (var i = 0; i < appenders.length; ++i) {
413         var appender = appenders[i];
414         var appenderTimestamps = {};
415         appenderTimestamps.xhrStartTime = appender.xhrStartTime;
416         appenderTimestamps.xhrEndTime = appender.xhrEndTime;
417         appenderTimestamps.appendStartTime = appender.appendStartTime;
418         appenderTimestamps.appendEndTime = appender.appendEndTime;
419         appenderTimestamps.playbackStartTime = appender.playbackStartTime;
420         timestamps.appenders.push(appenderTimestamps);
421       }
422
423       mediaElement.pause();
424
425       pageEndTime = getPerfTimestamp();
426       doneCallback(stats, timestamps);
427     };
428
429     listener = window.requestAnimationFrame(checkForCurrentTimeChange);
430
431     timeout = setTimeout(function() {
432       if (testDone)
433         return;
434
435       testDone = true;
436       window.cancelAnimationFrame(listener);
437
438       mediaElement.pause();
439       doneCallback(null);
440       EndTest("Test timed out.");
441     }, 10000);
442
443     mediaSourceOpenStartTime = getPerfTimestamp();
444     mediaElement.src = URL.createObjectURL(mediaSource);
445   };
446
447   function onBodyLoad() {
448     bodyLoadTime = getPerfTimestamp();
449
450     if (!testParams.doNotWaitForBodyOnLoad) {
451       startTest();
452     }
453   }
454
455   function startTest() {
456     updateControls(testParams);
457
458     var appenders = [];
459
460     if (testParams.useAppendStream && !window.MediaSource)
461       EndTest("Can't use appendStream() because the unprefixed MediaSource " +
462               "object is not present.");
463
464     var Appender = testParams.useAppendStream ? StreamAppender : BufferAppender;
465
466     if (testParams.testType.indexOf("A") != -1) {
467       appenders.push(
468           new Appender("audio/mp4; codecs=\"mp4a.40.2\"",
469                        "audio.mp4",
470                        "a",
471                        testParams.startOffset,
472                        testParams.appendSize));
473     }
474
475     if (testParams.testType.indexOf("V") != -1) {
476       appenders.push(
477           new Appender("video/mp4; codecs=\"avc1.640028\"",
478                        "video.mp4",
479                        "v",
480                        testParams.startOffset,
481                        testParams.appendSize));
482     }
483
484     var video = document.getElementById("v");
485     video.addEventListener("error", function(e) {
486       console.log("video error!");
487       EndTest("Video error: " + video.error);
488     });
489
490     video.id = getTestID();
491     runAppendTest(video, appenders, function(stats, timestamps) {
492       displayResults(stats);
493       plotTimestamps(timestamps, testParams.graphDuration, video);
494       EndTest("Call back call done.");
495     });
496   }
497
498   function EndTest(msg) {
499     console.log("Ending test: " + msg);
500     window.__testDone = true;
501   }
502
503   function getTestID() {
504     var id = testParams.testType;
505     if (testParams.useAppendStream)
506       id += "_stream"
507     else
508       id += "_buffer"
509     if (testParams.doNotWaitForBodyOnLoad)
510       id += "_pre_load"
511     else
512       id += "_post_load"
513     return id;
514   }
515
516   function setupTest() {
517     loadTestParams();
518     document.body.onload = onBodyLoad;
519
520     if (testParams.doNotWaitForBodyOnLoad) {
521       startTest();
522     }
523   }
524
525   window["setupTest"] = setupTest;
526   window.__testDone = false;
527   window.__testMetrics = {};
528 })();