| Index: tracing/tracing/metrics/estimated_input_latency_metric_test.html
|
| diff --git a/tracing/tracing/metrics/estimated_input_latency_metric_test.html b/tracing/tracing/metrics/estimated_input_latency_metric_test.html
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..cb30b9dc9f46e50875f7625f47762e49a11f9b5b
|
| --- /dev/null
|
| +++ b/tracing/tracing/metrics/estimated_input_latency_metric_test.html
|
| @@ -0,0 +1,379 @@
|
| +<!DOCTYPE html>
|
| +<!--
|
| +Copyright 2016 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/metrics/estimated_input_latency_metric.html">
|
| +<link rel="import" href="/tracing/metrics/system_health/loading_metric.html">
|
| +<link rel="import" href="/tracing/value/value_set.html">
|
| +
|
| +<script>
|
| +'use strict';
|
| +
|
| +tr.b.unittest.testSuite(function() {
|
| + var MAIN_THREAD_ID = 2;
|
| + var INTERACTIVE_WINDOW_SIZE_MS = tr.metrics.sh.INTERACTIVE_WINDOW_SIZE_MS;
|
| + var calculateEILRiskPercentiles = tr.metrics.calculateEILRiskPercentiles;
|
| +
|
| + function newSchedulerTask(startTime, duration) {
|
| + return tr.c.TestUtils.newSliceEx({
|
| + cat: 'toplevel',
|
| + title: 'TaskQueueManager::ProcessTaskFromWorkQueue',
|
| + start: startTime,
|
| + duration: duration
|
| + });
|
| + }
|
| +
|
| + function assertTasksNotTooLong(tti, taskList) {
|
| + // We need to make sure the tasks in the interactive window are less than
|
| + // responsiveness threshold because otherwise it will push back TTI.
|
| + // If we change RESPONSIVENESS_THRESHOLD_MS or INTERACTIVE_WINDOW_SIZE_MS in
|
| + // TTI we may have to change the task durations for the tests.
|
| + for (var task of taskList) {
|
| + if (task.start < tti + INTERACTIVE_WINDOW_SIZE_MS) {
|
| + assert.isBelow(
|
| + task.duration, tr.metrics.sh.RESPONSIVENESS_THRESHOLD_MS);
|
| + }
|
| + }
|
| + }
|
| +
|
| + function addTasksToThread(mainThread, tti, taskList) {
|
| + assertTasksNotTooLong(tti, taskList);
|
| + for (var task of taskList) {
|
| + mainThread.sliceGroup.pushSlice(
|
| + newSchedulerTask(task.start, task.duration));
|
| + }
|
| + }
|
| +
|
| + function createTestModel(rendererCallback) {
|
| + var model = tr.c.TestUtils.newModel(function(model) {
|
| + var rendererProcess = model.getOrCreateProcess(1);
|
| + var mainThread = rendererProcess.getOrCreateThread(MAIN_THREAD_ID);
|
| + mainThread.name = 'CrRendererMain';
|
| + rendererCallback(rendererProcess);
|
| + });
|
| + return model;
|
| + }
|
| +
|
| + function addTestFrame(rendererProcess) {
|
| + // We need FMP for TTI, and FMP needs to reference a frame.
|
| + rendererProcess.objects.addSnapshot(
|
| + 'ptr', 'loading', 'FrameLoader', 300,
|
| + {isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'},
|
| + documentLoaderURL: 'http://example.com'});
|
| + }
|
| +
|
| + function addInteractiveWindow(
|
| + rendererProcess, startNavigationTime, ttiTime) {
|
| + // To get to TTI, we need a firstMeaningfulPaint and a sufficiently large
|
| + // window of no long tasks.
|
| + var mainThread = rendererProcess.getOrCreateThread(MAIN_THREAD_ID);
|
| + mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
|
| + cat: 'blink.user_timing',
|
| + title: 'navigationStart',
|
| + start: startNavigationTime,
|
| + duration: 0.0,
|
| + args: {frame: '0xdeadbeef'}
|
| + }));
|
| +
|
| + mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
|
| + cat: 'loading',
|
| + title: 'firstMeaningfulPaintCandidate',
|
| + start: ttiTime,
|
| + duration: 0.0,
|
| + args: {frame: '0xdeadbeef'}
|
| + }));
|
| +
|
| + // Add a 0 duration task at the end so we hit the required window size.
|
| + mainThread.sliceGroup.pushSlice(
|
| + newSchedulerTask(ttiTime + INTERACTIVE_WINDOW_SIZE_MS, 0));
|
| + }
|
| +
|
| + function prepareModelForSingleWindowTest(tti, postTTITasks) {
|
| + var model = createTestModel(rendererProcess => {
|
| + var startNavTime = 0;
|
| + addTestFrame(rendererProcess);
|
| + addInteractiveWindow(rendererProcess, startNavTime, tti);
|
| + var mainThread = rendererProcess.getOrCreateThread(MAIN_THREAD_ID);
|
| + addTasksToThread(mainThread, tti, postTTITasks);
|
| + });
|
| + return model;
|
| + }
|
| +
|
| + function computeEQT90Numeric(model) {
|
| + var values = new tr.v.ValueSet();
|
| + tr.metrics.estimatedInputLatencyMetric(values, model);
|
| + var numeric = tr.b.getOnlyElement(values.getValuesNamed(
|
| + 'Expected Queueing Time 90th Percentile'));
|
| + return numeric;
|
| + }
|
| +
|
| + test('eqt90thPercentileTest40msFull', function() {
|
| + var tti = 1000;
|
| + var postTTITasks = [];
|
| + var taskDur = 40;
|
| + var windowEnd = tti + INTERACTIVE_WINDOW_SIZE_MS;
|
| + for (var startTime = tti; startTime < windowEnd; startTime += taskDur) {
|
| + postTTITasks.push({start: startTime, duration: taskDur});
|
| + }
|
| +
|
| + var model = prepareModelForSingleWindowTest(tti, postTTITasks);
|
| + var numeric = computeEQT90Numeric(model);
|
| + assert.equal(numeric.running.count, 1);
|
| + assert.closeTo(numeric.running.mean, 36, 0.1);
|
| + });
|
| +
|
| + test('eqt90thPercentileTestZeroEQT', function() {
|
| + var tti = 1000;
|
| + var postTTITasks = []; // No tasks after TTI.
|
| + var model = prepareModelForSingleWindowTest(tti, postTTITasks);
|
| + var numeric = computeEQT90Numeric(model);
|
| + assert.equal(numeric.running.count, 1);
|
| + assert.equal(numeric.running.mean, 0);
|
| + });
|
| +
|
| + test('eqt90thPercentileTestLargePostTTITask', function() {
|
| + var tti = 1000;
|
| + var postTTITasks = [
|
| + {start: tti + INTERACTIVE_WINDOW_SIZE_MS, duration: 5000}
|
| + ];
|
| +
|
| + var model = prepareModelForSingleWindowTest(tti, postTTITasks);
|
| + var numeric = computeEQT90Numeric(model);
|
| + assert.equal(numeric.running.count, 1);
|
| + assert.closeTo(numeric.running.mean, 4000, 0.1);
|
| + });
|
| +
|
| + test('eqt90thPercentileTestAlternate40ms', function() {
|
| + var tti = 1000;
|
| + var postTTITasks = [];
|
| + var taskDur = 40;
|
| + var windowEnd = tti + INTERACTIVE_WINDOW_SIZE_MS;
|
| + for (var startTime = tti; startTime < windowEnd; startTime += 2 * taskDur) {
|
| + postTTITasks.push({start: startTime, duration: taskDur});
|
| + }
|
| + var model = prepareModelForSingleWindowTest(tti, postTTITasks);
|
| + var numeric = computeEQT90Numeric(model);
|
| + assert.equal(numeric.running.count, 1);
|
| + assert.closeTo(numeric.running.mean, 32, 0.1);
|
| + });
|
| +
|
| + test('eqt90thPercentileMultipleWindows', function() {
|
| + // There are five interactive windows:
|
| + // window 1 - 3 have zero tasks
|
| + // window 4 is full of back to back 40 ms tasks
|
| + // window 5 has alternating 40ms task and 40ms idleTime
|
| + var model = createTestModel(rendererProcess => {
|
| + addTestFrame(rendererProcess);
|
| + var mainThread = rendererProcess.getOrCreateThread(MAIN_THREAD_ID);
|
| +
|
| + for (var i = 0; i < 5; i++) {
|
| + var startNavigationTime = i * 10000;
|
| + var ttiTime = startNavigationTime + 1000;
|
| + var windowEnd = (i + 1) * 10000;
|
| + addInteractiveWindow(
|
| + rendererProcess, startNavigationTime, ttiTime, windowEnd);
|
| + }
|
| +
|
| + var taskDur = 40;
|
| + var win4Tasks = [];
|
| + var win4Interactive = 31000;
|
| + var win4End = 40000;
|
| + for (var startTime = win4Interactive;
|
| + startTime < win4End; startTime += taskDur) {
|
| + win4Tasks.push({start: startTime, duration: taskDur});
|
| + }
|
| + addTasksToThread(mainThread, win4Interactive, win4Tasks);
|
| +
|
| + var win5Tasks = [];
|
| + var win5Interactive = 41000;
|
| + var win5End = 50000;
|
| + for (var startTime = win5Interactive;
|
| + startTime < win5End; startTime += 2 * taskDur) {
|
| + win5Tasks.push({start: startTime, duration: taskDur});
|
| + }
|
| + addTasksToThread(mainThread, win5Interactive, win5Tasks);
|
| + });
|
| +
|
| + var numeric = computeEQT90Numeric(model);
|
| + assert.equal(numeric.running.count, 5);
|
| + numeric.sampleValues.slice(0, 3).forEach(v => assert.equal(v, 0));
|
| + assert.closeTo(numeric.sampleValues[3], 36, 0.1);
|
| + assert.closeTo(numeric.sampleValues[4], 32, 0.1);
|
| + });
|
| +
|
| + test('properlyIterateSchedulerTasks', function() {
|
| + // Test that we handle both top level message loop tasks and nested renderer
|
| + // scheduler tasks. We add 2000ms worth of back to back 40ms
|
| + // MessageLoop::RunTask slices with no nested scheduler task, and then one
|
| + // big 3000ms MessageLoop::RunTask with 2000ms worth of back to back 40ms
|
| + // nested scheduler tasks inside.
|
| + var messageLoopTaskTitle = 'MessageLoop::RunTask';
|
| + var schedulerTaskTitle = 'TaskQueueManager::ProcessTaskFromWorkQueue';
|
| + var model = createTestModel(rendererProcess => {
|
| + var startNavTime = 0;
|
| + var tti = 1000;
|
| + var taskDur = 40;
|
| + assert.isBelow(taskDur, tr.metrics.sh.RESPONSIVENESS_THRESHOLD_MS);
|
| +
|
| + addTestFrame(rendererProcess);
|
| + addInteractiveWindow(rendererProcess, startNavTime, tti);
|
| + var mainThread = rendererProcess.getOrCreateThread(MAIN_THREAD_ID);
|
| +
|
| + for (var start = tti; start < tti + 2000; start += taskDur) {
|
| + mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
|
| + cat: 'toplevel',
|
| + title: messageLoopTaskTitle,
|
| + start: start,
|
| + duration: taskDur
|
| + }));
|
| + }
|
| +
|
| + // A huge ML task with back to back 40ms tasks in scheduler
|
| + var giantMessageLoopSlice = tr.c.TestUtils.newSliceEx({
|
| + cat: 'toplevel',
|
| + title: messageLoopTaskTitle,
|
| + start: 3000,
|
| + duration: 3000
|
| + });
|
| +
|
| + // back to back for 3000
|
| + var schedulerDoWork = tr.c.TestUtils.newSliceEx({
|
| + cat: 'not-toplevel',
|
| + title: 'TaskQueueManager::DoWork',
|
| + start: 3500,
|
| + duration: 2000
|
| + });
|
| +
|
| + for (var start = 3500; start < 3500 + 2000; start += taskDur) {
|
| + schedulerDoWork.subSlices.push(newSchedulerTask(start, taskDur));
|
| + }
|
| +
|
| + giantMessageLoopSlice.subSlices.push(schedulerDoWork);
|
| + mainThread.sliceGroup.pushSlice(giantMessageLoopSlice);
|
| + })
|
| +
|
| + var numeric = computeEQT90Numeric(model);
|
| + assert.equal(numeric.running.count, 1);
|
| + assert.closeTo(numeric.running.mean, 30, 0.1);
|
| + });
|
| +
|
| + // The tests below are ported from the lighthouse implementation of
|
| + // EQT90thPercentile. They have been modified to match the new function
|
| + // signatures.
|
| + var defaultPercentiles = [0, .25, .50, .75, .90, .99, 1];
|
| +
|
| + function verifyAllResults(results, expected) {
|
| + for (var i = 0; i < defaultPercentiles.length; i++) {
|
| + var computedEqt = results.get(defaultPercentiles[i]);
|
| + var expectedEqt = expected[i];
|
| + assert.closeTo(computedEqt, expectedEqt, 0.1);
|
| + }
|
| + }
|
| +
|
| + test('riskPercentiles.PercentileOfNoTasks', () => {
|
| + var results = calculateEILRiskPercentiles([], 100, defaultPercentiles);
|
| + var expected = [0, 0, 0, 0, 0, 0, 0];
|
| + verifyAllResults(results, expected);
|
| + });
|
| +
|
| + test('riskPercentiles.singleTaskWithIdleTime', () => {
|
| + var results = calculateEILRiskPercentiles([50], 100, defaultPercentiles);
|
| + var expected = [0, 0, 0, 25, 40, 49, 50];
|
| + verifyAllResults(results, expected);
|
| + });
|
| +
|
| + test('riskPercentiles.singleTaskNoIdleTime', () => {
|
| + var results = calculateEILRiskPercentiles([50], 50, defaultPercentiles);
|
| + var expected = [0, 12.5, 25, 37.5, 45, 49.5, 50];
|
| + verifyAllResults(results, expected);
|
| + });
|
| +
|
| + test('riskPercentiles.severalEqualLengthTasks', () => {
|
| + var results = calculateEILRiskPercentiles(
|
| + [50, 50, 50, 50], 400, defaultPercentiles);
|
| + var expected = [0, 0, 0, 25, 40, 49, 50];
|
| + verifyAllResults(results, expected);
|
| + });
|
| +
|
| + test('riskPercentiles.zeroDurationTask', () => {
|
| + var results = calculateEILRiskPercentiles(
|
| + [0, 0, 0, 10, 20, 20, 30, 30, 120], 320, defaultPercentiles);
|
| + var expected = [0, 0, 12, 40, 88, 116.8, 120];
|
| + verifyAllResults(results, expected);
|
| + });
|
| +
|
| + test('riskPercentiles.threeOneSecondInFiveSecondWindow', () => {
|
| + // Three tasks of one second each, all within a five-second window.
|
| + // Mean Queueing Time of 300ms.
|
| + var results = calculateEILRiskPercentiles(
|
| + [1000, 1000, 1000], 5000, defaultPercentiles, 0);
|
| + var expected = [0, 0, 166.67, 583.33, 833.33, 983.33, 1000];
|
| + verifyAllResults(results, expected);
|
| + });
|
| +
|
| + test('riskPercentiles.clippedTask', () => {
|
| + var results = calculateEILRiskPercentiles(
|
| + [10, 20, 50, 60, 90, 100], 300, defaultPercentiles, 30);
|
| + var expected = [0, 16.25, 37.5, 58.33, 80, 97, 100];
|
| + verifyAllResults(results, expected);
|
| +
|
| + });
|
| +
|
| + test('riskPercentiles.singleTaskOverMultipleWindows', () => {
|
| + // One 20 second long task over three five-second windows.
|
| +
|
| + // Starts 3 seconds into the first window. Mean Queueing Time = 7600ms.
|
| + var TASK_LENGTH = 20000;
|
| + let results1 = calculateEILRiskPercentiles(
|
| + [TASK_LENGTH], 5000, defaultPercentiles, TASK_LENGTH - 2000);
|
| + var expected1 = [0, 0, 0, 18750, 19500, 19950, 20000];
|
| + verifyAllResults(results1, expected1);
|
| +
|
| + // Starts 2 seconds before and ends 13 seconds after.
|
| + // Mean Queueing Time = 15500ms.
|
| + var results2 = calculateEILRiskPercentiles(
|
| + [TASK_LENGTH - 2000], 5000, defaultPercentiles, TASK_LENGTH - 7000);
|
| + var expected2 = [0, 14250, 15500, 16750, 17500, 17950, 18000];
|
| + verifyAllResults(results2, expected2);
|
| +
|
| + // Starts 17 seconds before and ends 3 seconds into the window.
|
| + // Mean Queueing Time = 900ms.
|
| + var results3 = calculateEILRiskPercentiles(
|
| + [TASK_LENGTH - 17000], 5000, defaultPercentiles, 0);
|
| + var expected3 = [0, 0, 500, 1750, 2500, 2950, 3000];
|
| + verifyAllResults(results3, expected3);
|
| + });
|
| +
|
| + test('riskPercentiles.oneTaskShorterThanClippedLengthOfAnother', () => {
|
| + var results = calculateEILRiskPercentiles(
|
| + [40, 100], 100, defaultPercentiles, 50);
|
| + var expected = [0, 15, 40, 75, 90, 99, 100];
|
| + verifyAllResults(results, expected);
|
| + });
|
| +
|
| + test('riskPercentiles.completelyClippedTask', () => {
|
| + var results = calculateEILRiskPercentiles(
|
| + [40, 100], 100, defaultPercentiles, 100);
|
| + var expected = [0, 0, 0, 15, 30, 39, 40];
|
| + verifyAllResults(results, expected);
|
| + });
|
| +
|
| + test('riskPercentiles.doesNotDivideByZeroWhenSumLessThanWhole', () => {
|
| + // Durations chosen such that, due to floating point error:
|
| + // var idleTime = totalTime - (duration1 + duration2);
|
| + // (idleTime + duration1 + duration2) < totalTime
|
| + var duration1 = 67 / 107;
|
| + var duration2 = 67 / 53;
|
| + var totalTime = 10;
|
| + var results = calculateEILRiskPercentiles(
|
| + [duration1, duration2], totalTime, [1], 0);
|
| + var expected = duration2;
|
| + assert.closeTo(results.get(1), expected, 0.1);
|
| + });
|
| +
|
| +});
|
| +</script>
|
|
|