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> |