blob: a404b8e1980d6dc390c13f59f90b92cd6cb91be6 [file] [log] [blame]
<!DOCTYPE html>
<!--
Copyright 2017 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<link rel="import" href="/tracing/core/test_utils.html">
<link rel="import" href="/tracing/extras/importer/trace_event_importer.html">
<link rel="import" href="/tracing/metrics/media_metric.html">
<link rel="import" href="/tracing/value/histogram_set.html">
<script>
'use strict';
tr.b.unittest.testSuite(function() {
// Arbitrarily selected process ID and thread IDs we'll use in test data
const procId = 52;
const tidMain = 1;
const tidCompositor = 53;
const tidAudio = 55;
function doLoadEvent(timestamp, opt_id) {
return {name: 'WebMediaPlayerImpl::DoLoad', args: {id: opt_id || 0},
pid: procId, ts: timestamp, cat: 'media', tid: tidMain, ph: 'X'};
}
function videoRenderEvent(timestamp, opt_id) {
return {name: 'VideoRendererImpl::Render', args: {id: opt_id || 0},
pid: procId, ts: timestamp, cat: 'media', tid: tidCompositor, ph: 'X'};
}
function audioRenderEvent(timestamp, opt_id) {
return {name: 'AudioRendererImpl::Render', args: {id: opt_id || 0},
pid: procId, ts: timestamp, cat: 'media', tid: tidAudio, ph: 'X'};
}
function videoFramesDroppedEvent(timestamp, frameCount, opt_id) {
return {name: 'VideoFramesDropped',
args: {count: frameCount, id: opt_id || 0},
pid: procId, ts: timestamp, cat: 'media', tid: tidCompositor, ph: 'X'};
}
function onEndedEvent(timestamp, mediaDuration, opt_id) {
return {name: 'WebMediaPlayerImpl::OnEnded',
args: {duration: mediaDuration, id: opt_id || 0},
pid: procId, ts: timestamp, cat: 'media', tid: tidMain, ph: 'X'};
}
function doSeekEvent(timestamp, targetTime, opt_id) {
return {name: 'WebMediaPlayerImpl::DoSeek',
args: {target: targetTime, id: opt_id || 0},
pid: procId, ts: timestamp, cat: 'media', tid: tidMain, ph: 'X'};
}
function seekedEvent(timestamp, targetTime, opt_id) {
return {name: 'WebMediaPlayerImpl::OnPipelineSeeked',
args: {target: targetTime, id: opt_id || 0},
pid: procId, ts: timestamp, cat: 'media', tid: tidMain, ph: 'X'};
}
function bufferEnoughEvent(timestamp, opt_id) {
return {name: 'WebMediaPlayerImpl::BufferingHaveEnough',
args: {id: opt_id || 0},
pid: procId, ts: timestamp, cat: 'media', tid: tidMain, ph: 'X'};
}
function threadMarker(threadName, threadId) {
return {name: 'thread_name', args: {name: threadName},
pid: procId, ts: 0, cat: '__metadata', tid: threadId, ph: 'M'};
}
const mainThreadMarker = threadMarker('CrRendererMain', tidMain);
const compositorThreadMarker = threadMarker('Compositor', tidCompositor);
const audioThreadMarker = threadMarker('AudioOutputDevice', tidAudio);
function makeModel(events) {
return tr.c.TestUtils.newModelWithEvents([events]);
}
function checkCloseTo(histograms, histogramName, expectedValue) {
assert.isDefined(histograms.getHistogramNamed(histogramName));
const value = histograms.getHistogramNamed(histogramName);
const statistics = value.running;
assert.strictEqual(statistics.count, 1);
assert.closeTo(statistics.mean, expectedValue, 1e-5);
}
function checkCloseToMultiple(histograms, histogramName, expectedCount,
expectedMin, expectedMean, expectedMax) {
assert.isDefined(histograms.getHistogramNamed(histogramName));
const value = histograms.getHistogramNamed(histogramName);
const statistics = value.running;
assert.strictEqual(statistics.count, expectedCount);
assert.closeTo(statistics.min, expectedMin, 1e-5);
assert.closeTo(statistics.mean, expectedMean, 1e-5);
assert.closeTo(statistics.max, expectedMax, 1e-5);
}
function checkEqual(histograms, histogramName, expectedValue) {
assert.isDefined(histograms.getHistogramNamed(histogramName));
const value = histograms.getHistogramNamed(histogramName);
const statistics = value.running;
assert.strictEqual(statistics.count, 1);
assert.strictEqual(statistics.mean, expectedValue);
}
test('mediaMetric_noData', function() {
const histograms = new tr.v.HistogramSet();
const events = [];
tr.metrics.mediaMetric(histograms, makeModel(events));
assert.lengthOf(histograms, 0);
});
test('mediaMetric_videoTimeToPlay', function() {
const histograms = new tr.v.HistogramSet();
const events = [
doLoadEvent(100),
videoRenderEvent(300),
// Video renderer always generate multiple render events,
// one for each frame. For calculation of time-to-play,
// only the first render event is relevant. Here we put in
// a second render event to make sure it's ignored by the
// metric computation code.
videoRenderEvent(400),
mainThreadMarker,
compositorThreadMarker,
];
tr.metrics.mediaMetric(histograms, makeModel(events));
checkCloseTo(histograms, 'time_to_video_play', 0.2);
});
test('mediaMetric_audioTimeToPlay', function() {
const histograms = new tr.v.HistogramSet();
const events = [
mainThreadMarker,
audioThreadMarker,
doLoadEvent(1000),
audioRenderEvent(1100),
];
tr.metrics.mediaMetric(histograms, makeModel(events));
checkCloseTo(histograms, 'time_to_audio_play', 0.1);
});
test('mediaMetric_bufferingTimeVideo', function() {
const histograms = new tr.v.HistogramSet();
const events = [
doLoadEvent(1000),
videoRenderEvent(1500),
videoRenderEvent(1600),
onEndedEvent(10051500, 10),
mainThreadMarker,
compositorThreadMarker,
];
tr.metrics.mediaMetric(histograms, makeModel(events));
checkCloseTo(histograms, 'buffering_time', 50);
});
test('mediaMetric_bufferingTimeAudio', function() {
const histograms = new tr.v.HistogramSet();
const events = [
mainThreadMarker,
audioThreadMarker,
doLoadEvent(1000),
audioRenderEvent(1500),
onEndedEvent(5002500, 5),
];
tr.metrics.mediaMetric(histograms, makeModel(events));
checkCloseTo(histograms, 'buffering_time', 1);
});
// With seek, no buffering time should be reported
test('mediaMetric_noBufferingTime', function() {
const histograms = new tr.v.HistogramSet();
const events = [
doLoadEvent(1000),
videoRenderEvent(1500),
videoRenderEvent(1600),
onEndedEvent(10066666, 10),
doSeekEvent(525, 1.2),
seekedEvent(719, 1.2),
bufferEnoughEvent(825),
mainThreadMarker,
compositorThreadMarker,
];
tr.metrics.mediaMetric(histograms, makeModel(events));
assert.isUndefined(histograms.getHistogramNamed('buffering_time'));
});
test('mediaMetric_droppedFrameCount', function() {
const histograms = new tr.v.HistogramSet();
const events = [
doLoadEvent(1000),
videoRenderEvent(1500),
videoFramesDroppedEvent(123456, 3),
videoFramesDroppedEvent(234567, 6),
videoFramesDroppedEvent(345678, 1),
mainThreadMarker,
compositorThreadMarker,
];
tr.metrics.mediaMetric(histograms, makeModel(events));
checkEqual(histograms, 'dropped_frame_count', 10);
});
test('mediaMetric_droppedFrameCountZero', function() {
const histograms = new tr.v.HistogramSet();
const events = [
doLoadEvent(1000),
videoRenderEvent(1500),
mainThreadMarker,
compositorThreadMarker,
];
tr.metrics.mediaMetric(histograms, makeModel(events));
checkEqual(histograms, 'dropped_frame_count', 0);
});
test('mediaMetric_droppedFrameCountNoVideo', function() {
const histograms = new tr.v.HistogramSet();
const events = [
doLoadEvent(1000),
audioRenderEvent(1500),
mainThreadMarker,
audioThreadMarker,
];
tr.metrics.mediaMetric(histograms, makeModel(events));
assert.isUndefined(histograms.getHistogramNamed('dropped_frame_count'));
});
test('mediaMetric_seekTimeVideo', function() {
const histograms = new tr.v.HistogramSet();
const events = [
doLoadEvent(1000),
videoRenderEvent(1500),
doSeekEvent(2000, 1.2),
seekedEvent(2500, 1.2),
bufferEnoughEvent(3000),
doSeekEvent(15000, 3.7),
seekedEvent(75000, 3.7),
bufferEnoughEvent(95000),
mainThreadMarker,
compositorThreadMarker,
];
tr.metrics.mediaMetric(histograms, makeModel(events));
checkCloseTo(histograms, 'pipeline_seek_time_1_2', 0.5);
checkCloseTo(histograms, 'seek_time_1_2', 1);
checkCloseTo(histograms, 'pipeline_seek_time_3_7', 60);
checkCloseTo(histograms, 'seek_time_3_7', 80);
});
test('mediaMetric_seekTimeAudio', function() {
const histograms = new tr.v.HistogramSet();
const events = [
doLoadEvent(1000),
audioRenderEvent(1500),
doSeekEvent(2000, 1.2),
seekedEvent(2500, 1.2),
bufferEnoughEvent(3000),
doSeekEvent(15000, 3.7),
seekedEvent(75000, 3.7),
bufferEnoughEvent(95000),
mainThreadMarker,
audioThreadMarker,
];
tr.metrics.mediaMetric(histograms, makeModel(events));
checkCloseTo(histograms, 'pipeline_seek_time_1_2', 0.5);
checkCloseTo(histograms, 'seek_time_1_2', 1);
checkCloseTo(histograms, 'pipeline_seek_time_3_7', 60);
checkCloseTo(histograms, 'seek_time_3_7', 80);
});
test('mediaMetric_seekOverlap', function() {
const histograms = new tr.v.HistogramSet();
const events = [
doLoadEvent(1000),
videoRenderEvent(1500),
doSeekEvent(2000, 1.2),
seekedEvent(2500, 1.2),
doSeekEvent(2800, 3.7), // Out of order
bufferEnoughEvent(3000),
seekedEvent(75000, 3.7),
bufferEnoughEvent(95000),
mainThreadMarker,
compositorThreadMarker,
];
tr.metrics.mediaMetric(histograms, makeModel(events));
assert.isDefined(histograms.getHistogramNamed('time_to_video_play'));
assert.isUndefined(histograms.getHistogramNamed('seek_time_1_2'));
});
test('mediaMetric_seekIncomplete', function() {
const histograms = new tr.v.HistogramSet();
const events = [
doLoadEvent(1000),
videoRenderEvent(1500),
doSeekEvent(2000, 1.2),
seekedEvent(2500, 1.2),
// Missing bufferEnoughEvent
mainThreadMarker,
compositorThreadMarker,
];
tr.metrics.mediaMetric(histograms, makeModel(events));
assert.isDefined(histograms.getHistogramNamed('time_to_video_play'));
assert.isUndefined(histograms.getHistogramNamed('seek_time_1_2'));
});
test('mediaMetric_seekWrongTarget', function() {
const histograms = new tr.v.HistogramSet();
const events = [
doLoadEvent(1000),
videoRenderEvent(1500),
doSeekEvent(2000, 1.2),
seekedEvent(2500, 2.7), // Wrong target, should be 1.2
mainThreadMarker,
compositorThreadMarker,
];
tr.metrics.mediaMetric(histograms, makeModel(events));
assert.isDefined(histograms.getHistogramNamed('time_to_video_play'));
assert.isUndefined(histograms.getHistogramNamed('seek_time_1_2'));
});
test('mediaMetric_multiplePlaybacks', function() {
const histograms = new tr.v.HistogramSet();
const events = [
doLoadEvent(1000, 0),
doLoadEvent(1100, 1),
videoRenderEvent(1400, 1),
videoRenderEvent(1500, 0),
doLoadEvent(2000, 2),
doSeekEvent(2000, 1.2, 0),
doSeekEvent(2100, 1.2, 1),
videoRenderEvent(2100, 2),
doSeekEvent(2200, 1.2, 2),
seekedEvent(2500, 1.2, 2),
seekedEvent(2600, 1.2, 1),
seekedEvent(2700, 1.2, 0),
bufferEnoughEvent(3000, 0),
bufferEnoughEvent(3100, 1),
bufferEnoughEvent(3800, 2),
mainThreadMarker,
compositorThreadMarker,
];
tr.metrics.mediaMetric(histograms, makeModel(events));
// Values are 0.5, 0.3, 0.1, with min 0.1, mean 0.3, max 0.5.
checkCloseToMultiple(histograms, 'time_to_video_play', 3, 0.1, 0.3, 0.5);
// Values are 0.7, 0.5, 0.3, with min 0.3, mean 0.5, max 0.7.
checkCloseToMultiple(histograms,
'pipeline_seek_time_1_2', 3, 0.3, 0.5, 0.7);
// Values are 1, 1, 1.6, with min 1, mean 1.2, max 1.6
checkCloseToMultiple(histograms, 'seek_time_1_2', 3, 1, 1.2, 1.6);
});
// Scenario: Play mixed audio/video from start to finish
test('mediaMetric_playVideoScenario', function() {
const histograms = new tr.v.HistogramSet();
const events = [
doLoadEvent(2000),
videoRenderEvent(3000),
audioRenderEvent(3200),
videoRenderEvent(3300),
videoFramesDroppedEvent(123456, 4),
videoFramesDroppedEvent(234567, 2),
onEndedEvent(10013000, 10),
mainThreadMarker,
compositorThreadMarker,
audioThreadMarker,
];
tr.metrics.mediaMetric(histograms, makeModel(events));
checkCloseTo(histograms, 'time_to_video_play', 1);
checkCloseTo(histograms, 'time_to_audio_play', 1.2);
checkCloseTo(histograms, 'buffering_time', 10);
checkEqual(histograms, 'dropped_frame_count', 6);
});
// Scenario: Play audio from start to finish
test('mediaMetric_playAudioScenario', function() {
const histograms = new tr.v.HistogramSet();
const events = [
doLoadEvent(1000),
audioRenderEvent(1500),
onEndedEvent(10002500, 10),
mainThreadMarker,
audioThreadMarker,
];
tr.metrics.mediaMetric(histograms, makeModel(events));
assert.isUndefined(histograms.getHistogramNamed('time_to_video_play'));
checkCloseTo(histograms, 'time_to_audio_play', 0.5);
checkCloseTo(histograms, 'buffering_time', 1);
assert.isUndefined(histograms.getHistogramNamed('dropped_frame_count'));
});
// Scenario: Play audio/video with two seeks
test('mediaMetric_seekScenario', function() {
const histograms = new tr.v.HistogramSet();
const events = [
doLoadEvent(1000),
videoRenderEvent(2000),
audioRenderEvent(2020),
videoRenderEvent(2040),
doSeekEvent(5000, 0.5),
seekedEvent(5200, 0.5),
bufferEnoughEvent(6000),
videoFramesDroppedEvent(123456, 4),
doSeekEvent(200000, 9),
seekedEvent(210000, 9),
bufferEnoughEvent(220000),
videoFramesDroppedEvent(234567, 2),
onEndedEvent(300000, 10),
mainThreadMarker,
compositorThreadMarker,
audioThreadMarker,
];
tr.metrics.mediaMetric(histograms, makeModel(events));
checkCloseTo(histograms, 'time_to_video_play', 1);
checkCloseTo(histograms, 'time_to_audio_play', 1.02);
assert.isUndefined(histograms.getHistogramNamed('buffering_time'));
checkEqual(histograms, 'dropped_frame_count', 6);
checkCloseTo(histograms, 'pipeline_seek_time_0_5', 0.2);
checkCloseTo(histograms, 'seek_time_0_5', 1);
checkCloseTo(histograms, 'pipeline_seek_time_9', 10);
checkCloseTo(histograms, 'seek_time_9', 20);
});
});
</script>