blob: 11cef16634041659daf53af75a81c6ce2e34b0ce [file] [log] [blame]
<!DOCTYPE html>
<!--
Copyright (c) 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/extras/chrome/chrome_test_utils.html">
<link rel="import"
href="/tracing/extras/chrome/chrome_user_friendly_category_driver.html">
<link rel="import" href="/tracing/extras/chrome/event_finder_utils.html">
<link rel="import" href="/tracing/metrics/system_health/loading_metric.html">
<link rel="import" href="/tracing/value/histogram_set.html">
<script>
'use strict';
tr.b.unittest.testSuite(function() {
test('timeToFirstPaint', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess = model.rendererProcess;
const mainThread = model.rendererMain;
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'blink.user_timing',
title: 'navigationStart',
start: 200,
duration: 0.0,
args: {frame: '0xdeadbeef'}
}));
rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300,
{
isLoadingMainFrame: true,
frame: {id_ref: '0xdeadbeef'},
documentLoaderURL: 'http://example.com'
});
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'rail,loading,devtools.timeline',
title: 'firstPaint',
start: 1001,
duration: 0.0,
args: {frame: '0xdeadbeef'}
}));
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const hist = histograms.getHistogramNamed('timeToFirstPaint');
assert.strictEqual(1, hist.running.count);
assert.strictEqual(801, hist.running.mean);
});
test('timeToFirstContentfulPaint', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess = model.rendererProcess;
const mainThread = model.rendererMain;
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'blink.user_timing',
title: 'navigationStart',
start: 200,
duration: 0.0,
args: {frame: '0xdeadbeef'}
}));
rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300,
{
isLoadingMainFrame: true,
frame: {id_ref: '0xdeadbeef'},
documentLoaderURL: 'http://example.com'
});
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading,rail,devtools.timeline',
title: 'firstContentfulPaint',
start: 1000,
duration: 0.0,
args: {frame: '0xdeadbeef'}
}));
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: 'ResourceDispatcher::OnRequestComplete',
start: 200,
duration: 100,
cpuStart: 210,
cpuDuration: 25,
}));
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const hist = histograms.getHistogramNamed('timeToFirstContentfulPaint');
assert.strictEqual(1, hist.running.count);
assert.strictEqual(800, hist.running.mean);
const fcpResourceLoading = histograms.getHistogramNamed(
'timeToFirstContentfulPaint:resource_loading');
assert.strictEqual(
hist.diagnostics.get('breakdown').get(
'resource_loading'),
'timeToFirstContentfulPaint:resource_loading');
assert.strictEqual(fcpResourceLoading.sum, 100);
assert.strictEqual(tr.b.getOnlyElement(hist.getBinForValue(
800).diagnosticMaps).get('breakdown').get('resource_loading'), 100);
});
test('timeToLargestImagePaint', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess = model.rendererProcess;
const mainThread = model.rendererMain;
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'blink.user_timing',
title: 'navigationStart',
start: 200,
duration: 0.0,
args: {frame: '0xdeadbeef'}
}));
rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300,
{
isLoadingMainFrame: true,
frame: {id_ref: '0xdeadbeef'},
documentLoaderURL: 'http://example.com'
});
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: 'LargestImagePaint::Candidate',
start: 1000,
duration: 0.0,
args: {
frame: '0xdeadbeef',
data: { candidateIndex: 1 }}
}));
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const hist = histograms.getHistogramNamed('largestImagePaint');
assert.strictEqual(1, hist.running.count);
assert.strictEqual(800, hist.running.mean);
});
// If the loading candidate is the last candidate, block reporting any value.
test('timeToLargestImagePaint_filterLoadingCandidate', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess = model.rendererProcess;
const mainThread = model.rendererMain;
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'blink.user_timing',
title: 'navigationStart',
start: 200,
duration: 0.0,
args: {frame: '0xdeadbeef'}
}));
rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300,
{
isLoadingMainFrame: true,
frame: {id_ref: '0xdeadbeef'},
documentLoaderURL: 'http://example.com'
});
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: 'LargestImagePaint::Candidate',
start: 1000,
duration: 0.0,
args: {
frame: '0xdeadbeef',
data: { candidateIndex: 1 }}
}));
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: 'LargestImagePaint::NoCandidate',
start: 1000,
duration: 0.0,
args: {
frame: '0xdeadbeef',
data: { candidateIndex: 2 }}
}));
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const hist = histograms.getHistogramNamed('largestImagePaint');
assert.strictEqual(0, hist.sampleValues.length);
});
// If the loading candidate is the last candidate, block reporting any value.
test('timeToLargestTextPaint_filterLoadingCandidate', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess = model.rendererProcess;
const mainThread = model.rendererMain;
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'blink.user_timing',
title: 'navigationStart',
start: 200,
duration: 0.0,
args: {frame: '0xdeadbeef'}
}));
rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300,
{
isLoadingMainFrame: true,
frame: {id_ref: '0xdeadbeef'},
documentLoaderURL: 'http://example.com'
});
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: 'LargestTextPaint::Candidate',
start: 1000,
duration: 0.0,
args: {
frame: '0xdeadbeef',
data: { candidateIndex: 1 }}
}));
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: 'LargestTextPaint::NoCandidate',
start: 1000,
duration: 0.0,
args: {
frame: '0xdeadbeef',
data: { candidateIndex: 2 }}
}));
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const hist = histograms.getHistogramNamed('largestTextPaint');
assert.strictEqual(0, hist.sampleValues.length);
});
test('timeToLargestTextPaint', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess = model.rendererProcess;
const mainThread = model.rendererMain;
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'blink.user_timing',
title: 'navigationStart',
start: 200,
duration: 0.0,
args: {frame: '0xdeadbeef'}
}));
rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300,
{
isLoadingMainFrame: true,
frame: {id_ref: '0xdeadbeef'},
documentLoaderURL: 'http://example.com'
});
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: 'LargestTextPaint::Candidate',
start: 1000,
duration: 0.0,
args: {
frame: '0xdeadbeef',
data: { candidateIndex: 1 }}
}));
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const hist = histograms.getHistogramNamed('largestTextPaint');
assert.strictEqual(1, hist.running.count);
assert.strictEqual(800, hist.running.mean);
});
test('timeToLargestContentfulPaint', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const browerMain = model.browserMain;
browerMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: tr.e.chrome.LCP_CANDIDATE_EVENT_TITLE,
start: 200,
duration: 0.0,
args: {
data: { durationInMilliseconds: 100, type: 'text', size: 1000,
inMainFrame: true },
main_frame_tree_node_id: 12345,
}
}));
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const hist = histograms.getHistogramNamed('largestContentfulPaint');
assert.strictEqual(1, hist.running.count);
assert.strictEqual(100, hist.running.mean);
});
test('timeToLargestContentfulPaint_LastCandidate', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const browerMain = model.browserMain;
browerMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: tr.e.chrome.LCP_CANDIDATE_EVENT_TITLE,
start: 200,
duration: 0.0,
args: {
data: { durationInMilliseconds: 100, type: 'text', size: 1000,
inMainFrame: true },
main_frame_tree_node_id: 12345,
}
}));
browerMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: tr.e.chrome.LCP_CANDIDATE_EVENT_TITLE,
start: 300,
duration: 0.0,
args: {
data: { durationInMilliseconds: 200, type: 'image', size: 2000,
inMainFrame: true },
main_frame_tree_node_id: 12345,
}
}));
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const hist = histograms.getHistogramNamed('largestContentfulPaint');
assert.strictEqual(1, hist.running.count);
assert.strictEqual(200, hist.running.mean);
});
test('timeToLargestContentfulPaint_CandidatesOutOfOrder', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const browerMain = model.browserMain;
browerMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: tr.e.chrome.LCP_CANDIDATE_EVENT_TITLE,
start: 300,
duration: 0.0,
args: {
data: { durationInMilliseconds: 100, type: 'text', size: 2000,
inMainFrame: true },
main_frame_tree_node_id: 12345,
}
}));
browerMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: tr.e.chrome.LCP_CANDIDATE_EVENT_TITLE,
start: 200,
duration: 0.0,
args: {
data: { durationInMilliseconds: 200, type: 'image', size: 1000,
inMainFrame: true },
main_frame_tree_node_id: 12345,
}
}));
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const hist = histograms.getHistogramNamed('largestContentfulPaint');
assert.strictEqual(1, hist.running.count);
assert.strictEqual(100, hist.running.mean);
});
test('timeToLargestContentfulPaint_AggregateSubframe', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const browerMain = model.browserMain;
browerMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: tr.e.chrome.LCP_CANDIDATE_EVENT_TITLE,
start: 200,
duration: 0.0,
args: {
data: { durationInMilliseconds: 100, type: 'text', size: 1000,
inMainFrame: true },
main_frame_tree_node_id: 12345,
}
}));
browerMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: tr.e.chrome.LCP_CANDIDATE_EVENT_TITLE,
start: 300,
duration: 0.0,
args: {
data: { durationInMilliseconds: 200, type: 'image', size: 2000,
inMainFrame: false },
main_frame_tree_node_id: 12345,
}
}));
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const hist = histograms.getHistogramNamed('largestContentfulPaint');
assert.strictEqual(1, hist.running.count);
assert.strictEqual(200, hist.running.mean);
});
test('timeToLargestContentfulPaint_GroupByNavigation', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const browerMain = model.browserMain;
browerMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: tr.e.chrome.LCP_CANDIDATE_EVENT_TITLE,
start: 200,
duration: 0.0,
args: {
data: { durationInMilliseconds: 100, type: 'text', size: 1000,
inMainFrame: true },
main_frame_tree_node_id: 1,
}
}));
browerMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: tr.e.chrome.LCP_CANDIDATE_EVENT_TITLE,
start: 300,
duration: 0.0,
args: {
data: { durationInMilliseconds: 200, type: 'image', size: 2000,
inMainFrame: true },
main_frame_tree_node_id: 2,
}
}));
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const hist = histograms.getHistogramNamed('largestContentfulPaint');
assert.strictEqual(2, hist.running.count);
assert.strictEqual(150, hist.running.mean);
});
test('timeToLargestContentfulPaint_Invalidate', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const browerMain = model.browserMain;
browerMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: tr.e.chrome.LCP_CANDIDATE_EVENT_TITLE,
start: 200,
duration: 0.0,
args: {
data: { durationInMilliseconds: 100, type: 'text', size: 1000,
inMainFrame: true },
main_frame_tree_node_id: 12345,
}
}));
browerMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: tr.e.chrome.LCP_INVALIDATE_EVENT_TITLE,
start: 300,
duration: 0.0,
args: {
data: { durationInMilliseconds: 100, type: 'text', size: 1000,
inMainFrame: true },
main_frame_tree_node_id: 12345,
}
}));
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const hist = histograms.getHistogramNamed('largestContentfulPaint');
assert.strictEqual(0, hist.numValues);
});
test('FCPPlusPlus_lastCandidateIsFinal', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess = model.rendererProcess;
const mainThread = model.rendererMain;
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'blink.user_timing',
title: 'navigationStart',
start: 200,
duration: 0.0,
args: {frame: '0xdeadbeef'}
}));
rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300,
{
isLoadingMainFrame: true,
frame: {id_ref: '0xdeadbeef'},
documentLoaderURL: 'http://example.com'
});
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: 'LargestTextPaint::Candidate',
start: 1200,
duration: 0.0,
args: {
frame: '0xdeadbeef',
data: { candidateIndex: 1 }}
}));
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: 'LargestTextPaint::Candidate',
start: 1100,
duration: 0.0,
args: {
frame: '0xdeadbeef',
data: { candidateIndex: 3 }}
}));
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: 'LargestTextPaint::Candidate',
start: 1000,
duration: 0.0,
args: {
frame: '0xdeadbeef',
data: { candidateIndex: 2 }}
}));
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const hist = histograms.getHistogramNamed('largestTextPaint');
assert.strictEqual(1, hist.running.count);
assert.strictEqual(900, hist.running.mean);
});
test('FCPPlusPlus_eachCandidatePerNavigation', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess = model.rendererProcess;
const mainThread = model.rendererMain;
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'blink.user_timing',
title: 'navigationStart',
start: 200,
duration: 0.0,
args: {frame: 'firstframeid'}
}));
rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300,
{
isLoadingMainFrame: true,
frame: {id_ref: 'firstframeid'},
documentLoaderURL: 'http://example.com'
});
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: 'LargestTextPaint::Candidate',
start: 1200,
duration: 0.0,
args: {
frame: 'firstframeid',
data: { candidateIndex: 1 }}
}));
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'blink.user_timing',
title: 'navigationStart',
start: 2000,
duration: 0.0,
args: {frame: 'secondframeid'}
}));
rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 2300,
{
isLoadingMainFrame: true,
frame: {id_ref: 'secondframeid'},
documentLoaderURL: 'http://example.com'
});
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: 'LargestTextPaint::Candidate',
start: 3200,
duration: 0.0,
args: {
frame: 'secondframeid',
data: { candidateIndex: 1 }}
}));
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const hist = histograms.getHistogramNamed('largestTextPaint');
assert.strictEqual(2, hist.running.count);
// avg([1000, 1200])
assert.strictEqual(1100, hist.running.mean);
});
test('aboveTheFoldLoadedToVisibleHistogram', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess = model.rendererProcess;
const mainThread = model.rendererMain;
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'blink.user_timing',
title: 'pc',
start: 1200,
duration: 0.0,
args: {data: {navigationId: '0xsecondnav'}}
}));
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'blink.user_timing',
title: 'pc',
start: 1300,
duration: 0.0,
args: {data: {navigationId: '0xsecondnav'}}
}));
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'blink.user_timing',
title: 'visible',
start: 300,
duration: 0.0,
}));
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const hist = histograms.getHistogramNamed('aboveTheFoldLoadedToVisible');
assert.strictEqual(1, hist.running.count);
assert.strictEqual(1000, hist.running.mean);
});
test('timeToFirstViewportReady', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess = model.rendererProcess;
const mainThread = model.rendererMain;
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'blink.user_timing',
title: 'pc',
start: 1300,
duration: 0.0,
args: {data: {navigationId: '0xsecondnav'}}
}));
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'blink.user_timing',
title: 'navigationStart',
start: 300,
duration: 0.0,
args: {frame: '0xdeadbeef', data: {navigationId: '0xfirstnav'}}
}));
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'blink.user_timing',
title: 'navigationStart',
start: 400,
duration: 0.0,
args: {frame: '0xdeadbeef', data: {navigationId: '0xsecondnav'}}
}));
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const hist = histograms.getHistogramNamed('timeToFirstViewportReady');
assert.strictEqual(1, hist.running.count);
assert.strictEqual(900, hist.running.mean);
});
test('timeToFirstContentfulPaintIgnoringWarmCache', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess = model.rendererProcess;
const mainThread = model.rendererMain;
// warm cache navigation
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'blink.user_timing',
title: 'navigationStart',
start: 200,
duration: 0.0,
args: {frame: '0xdeadbeef'}
}));
mainThread.asyncSliceGroup.push(tr.c.TestUtils.newAsyncSliceEx({
cat: 'blink.console',
title: 'telemetry.internal.warm_cache.warm.start',
start: 250,
duration: 0.0
}));
rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300,
{
isLoadingMainFrame: true,
frame: {id_ref: '0xdeadbeef'},
documentLoaderURL: 'http://example.com'
});
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading,rail,devtools.timeline',
title: 'firstContentfulPaint',
start: 1000,
duration: 0.0,
args: {frame: '0xdeadbeef'}
}));
mainThread.asyncSliceGroup.push(tr.c.TestUtils.newAsyncSliceEx({
cat: 'blink.console',
title: 'telemetry.internal.warm_cache.warm.end',
start: 1250,
duration: 0.0
}));
// measurement navigation
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'blink.user_timing',
title: 'navigationStart',
start: 2000,
duration: 0.0,
args: {frame: '0xdeadbeef'}
}));
rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 2100,
{
isLoadingMainFrame: true,
frame: {id_ref: '0xdeadbeef'},
documentLoaderURL: 'http://example.com'
});
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading,rail,devtools.timeline',
title: 'firstContentfulPaint',
start: 2400,
duration: 0.0,
args: {frame: '0xdeadbeef'}
}));
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const hist = histograms.getHistogramNamed('timeToFirstContentfulPaint');
assert.strictEqual(1, hist.running.count);
assert.strictEqual(400, hist.running.mean);
});
test('timeToFirstContentfulPaintInCpuTime', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess = model.rendererProcess;
const mainThread = model.rendererMain;
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'blink.user_timing',
title: 'navigationStart',
start: 200,
duration: 0.0,
args: {frame: '0xdeadbeef'}
}));
rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300,
{
isLoadingMainFrame: true,
frame: {id_ref: '0xdeadbeef'},
documentLoaderURL: 'http://example.com'
});
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading,rail,devtools.timeline',
title: 'firstContentfulPaint',
start: 1000,
duration: 0.0,
args: {frame: '0xdeadbeef'}
}));
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: 'ResourceDispatcher::OnRequestComplete',
start: 200,
duration: 100,
cpuStart: 210,
cpuDuration: 25,
}));
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const hist = histograms.getHistogramNamed('cpuTimeToFirstContentfulPaint');
assert.strictEqual(1, hist.running.count);
assert.strictEqual(25, hist.running.mean);
assert.strictEqual(tr.b.getOnlyElement(hist.getBinForValue(
25).diagnosticMaps).get('breakdown').get('resource_loading'), 25);
});
test('timeToFirstContentfulPaintWithNavId', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess = model.rendererProcess;
const mainThread = model.rendererMain;
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'blink.user_timing',
title: 'navigationStart',
start: 200,
duration: 0.0,
args: {frame: '0xdeadbeef', data: {navigationId: '0xfirstnav'}}
}));
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'blink.user_timing',
title: 'navigationStart',
start: 400,
duration: 0.0,
args: {frame: '0xdeadbeef', data: {navigationId: '0xsecondnav'}}
}));
rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300,
{
isLoadingMainFrame: true,
frame: {id_ref: '0xdeadbeef'},
documentLoaderURL: 'http://example.com'
});
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading,rail,devtools.timeline',
title: 'firstContentfulPaint',
start: 1000,
duration: 0.0,
args: {frame: '0xdeadbeef', data: {navigationId: '0xfirstnav'}}
}));
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: 'ResourceDispatcher::OnRequestComplete',
start: 200,
duration: 100,
cpuStart: 210,
cpuDuration: 25,
}));
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const hist = histograms.getHistogramNamed('timeToFirstContentfulPaint');
assert.strictEqual(1, hist.running.count);
assert.strictEqual(800, hist.running.mean);
const fcpResourceLoading = histograms.getHistogramNamed(
'timeToFirstContentfulPaint:resource_loading');
assert.strictEqual(
hist.diagnostics.get('breakdown').get(
'resource_loading'),
'timeToFirstContentfulPaint:resource_loading');
assert.strictEqual(fcpResourceLoading.sum, 100);
assert.strictEqual(tr.b.getOnlyElement(hist.getBinForValue(
800).diagnosticMaps).get('breakdown').get('resource_loading'), 100);
});
test('timeToFirstMeaningfulPaint', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess = model.rendererProcess;
const mainThread = model.rendererMain;
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'blink.user_timing',
title: 'navigationStart',
start: 200,
duration: 0.0,
args: {frame: '0xdeadbeef'}
}));
rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300,
{
isLoadingMainFrame: true,
frame: {id_ref: '0xdeadbeef'},
documentLoaderURL: 'http://example.com'
});
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: 'firstMeaningfulPaintCandidate',
start: 600,
duration: 0.0,
args: {frame: '0xdeadbeef'}
}));
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: 'firstMeaningfulPaintCandidate',
start: 1000,
duration: 0.0,
args: {frame: '0xdeadbeef'}
}));
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const hist = histograms.getHistogramNamed('timeToFirstMeaningfulPaint');
assert.strictEqual(1, hist.running.count);
assert.strictEqual(800, hist.running.mean);
});
// [-------CPU: 300-----------]
// | [---CPU: 100---] |
// | | | |
// v v v v
// Ts: 100 200 Start 300 500 600
// | |
// Start FMP
test('cpuTimeToFirstMeaningfulPaint_withEmbeddedSlices', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess = model.rendererProcess;
const mainThread = model.rendererMain;
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'blink.user_timing',
title: 'navigationStart',
start: 200,
duration: 0.0,
cpuStart: 1160,
cpuDuration: 0,
args: {frame: '0xdeadbeef'}
}));
rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300,
{
isLoadingMainFrame: true,
frame: {id_ref: '0xdeadbeef'},
documentLoaderURL: 'http://example.com'
});
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: 'firstMeaningfulPaintCandidate',
start: 600,
duration: 0.0,
cpuStart: 1480,
cpuDuration: 0,
args: {frame: '0xdeadbeef'}
}));
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'toplevel',
title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
start: 100,
duration: 400,
cpuStart: 1000,
cpuDuration: 300,
}));
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: 'ResourceDispatcher::OnRequestComplete',
start: 200,
duration: 100,
cpuStart: 1150,
cpuDuration: 100,
}));
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const hist = histograms.getHistogramNamed('cpuTimeToFirstMeaningfulPaint');
assert.deepEqual(hist.sampleValues, [225]);
const histBin = hist.getBinForValue(225);
assert.closeTo(histBin.diagnosticMaps[0]
.get('breakdown').get('other'), 400 / 3, 0.1);
assert.strictEqual(histBin.diagnosticMaps[0]
.get('breakdown').get('resource_loading'), 100);
});
// [-------------] [------------------]
// | | | |
// v v v v
// Ts: 100 Start 300 500 FMP 700
// | |
// 200 600
test('cpuTimeToFirstMeaningfulPaint_withIntersectingBoundary', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess = model.rendererProcess;
const mainThread = model.rendererMain;
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'blink.user_timing',
title: 'navigationStart',
start: 200,
duration: 0.0,
cpuStart: 1160,
cpuDuration: 0,
args: {frame: '0xdeadbeef'}
}));
rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300,
{
isLoadingMainFrame: true,
frame: {id_ref: '0xdeadbeef'},
documentLoaderURL: 'http://example.com'
});
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: 'firstMeaningfulPaintCandidate',
start: 600,
duration: 0.0,
cpuStart: 1280,
cpuDuration: 0,
args: {frame: '0xdeadbeef'}
}));
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'toplevel',
title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
start: 100,
duration: 200,
cpuStart: 1060,
cpuDuration: 180,
}));
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'toplevel',
title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
start: 500,
duration: 200,
cpuStart: 1250,
cpuDuration: 100,
}));
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const hist = histograms.getHistogramNamed('cpuTimeToFirstMeaningfulPaint');
assert.strictEqual(1, hist.running.count);
assert.strictEqual(140, hist.running.mean);
});
// Render 1:
//
// [--------]
// | |
// v v
// CPU Time: Start 1180 1230 FMP
// | |
// 1160 1280
//
// Render 2:
//
// [-------------]
// | |
// v v
// CPU Time: Start 1170 1270 FMP
// | |
// 1160 1280
test('cpuTimeToFirstMeaningfulPaint_multipleRenderers', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess1 = model.rendererProcess;
let mainThread = model.rendererMain;
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'blink.user_timing',
title: 'navigationStart',
start: 200,
duration: 0.0,
cpuStart: 1160,
cpuDuration: 0,
args: {frame: '0xdeadbeef'}
}));
rendererProcess1.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300,
{
isLoadingMainFrame: true,
frame: {id_ref: '0xdeadbeef'},
documentLoaderURL: 'http://example.com'
});
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: 'firstMeaningfulPaintCandidate',
start: 600,
duration: 0.0,
cpuStart: 1280,
cpuDuration: 0,
args: {frame: '0xdeadbeef'}
}));
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loader',
title: 'ResourceDispatcher::OnRequestComplete',
start: 300,
duration: 200,
cpuStart: 1180,
cpuDuration: 50
}));
const rendererProcess2 = model.getOrCreateProcess(10);
mainThread = rendererProcess2.getOrCreateThread(20);
mainThread.name = 'CrRendererMain';
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'blink.user_timing',
title: 'navigationStart',
start: 200,
duration: 0.0,
cpuStart: 1160,
cpuDuration: 0,
args: {frame: '0xdeadbeef'}
}));
rendererProcess2.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300,
{
isLoadingMainFrame: true,
frame: {id_ref: '0xdeadbeef'},
documentLoaderURL: 'http://example.com'
});
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: 'firstMeaningfulPaintCandidate',
start: 600,
duration: 0.0,
cpuStart: 1280,
cpuDuration: 0,
args: {frame: '0xdeadbeef'}
}));
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'toplevel',
title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
start: 300,
duration: 200,
cpuStart: 1170,
cpuDuration: 100,
}));
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const hist = histograms.getHistogramNamed('cpuTimeToFirstMeaningfulPaint');
assert.deepEqual(hist.sampleValues, [50, 100]);
const histBin1 = hist.getBinForValue(50);
assert.strictEqual(histBin1.diagnosticMaps[0]
.get('breakdown').get('resource_loading'), 50);
const histBin2 = hist.getBinForValue(100);
assert.strictEqual(histBin2.diagnosticMaps[0]
.get('breakdown').get('other'), 100);
});
function addFrameLoaderObject_(rendererProcess, timestamp) {
rendererProcess.objects.addSnapshot(
'ptr', 'loading', 'FrameLoader', timestamp, {
isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'},
documentLoaderURL: 'http://example.com',
});
}
function addNavigationStart_(rendererMain, timestamp, opt_data) {
rendererMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'blink.user_timing',
title: 'navigationStart',
start: timestamp,
duration: 0.0,
args: {frame: '0xdeadbeef', data: opt_data}
}));
}
// Some utility functions to make tests easier to read.
function addFirstMeaningfulPaint_(rendererMain, timestamp) {
rendererMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: 'firstMeaningfulPaintCandidate',
start: timestamp,
duration: 0.0,
args: {frame: '0xdeadbeef'}
}));
}
function addFirstContentfulPaint_(rendererMain, timestamp) {
rendererMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: 'firstContentfulPaint',
start: timestamp,
duration: 0.0,
args: {frame: '0xdeadbeef'}
}));
}
function addDomContentLoadedEnd_(rendererMain, timestamp) {
rendererMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'blink.user_timing',
title: 'domContentLoadedEventEnd',
start: timestamp,
duration: 0.0,
args: {frame: '0xdeadbeef'}
}));
}
function addTopLevelTask_(rendererMain, start, duration) {
rendererMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'toplevel',
title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
start,
duration,
}));
}
function addNetworkRequest_(rendererMain, start, duration) {
const networkEvents = [];
rendererMain.asyncSliceGroup.push(tr.c.TestUtils.newSliceEx({
cat: 'disabled-by-default-network',
title: 'ResourceLoad',
start,
duration,
}));
}
test('loadExpectationMetrics_urlDirectlyFromNavStartEvent', function() {
// If the navigation start event contains the URL, we should not require a
// FrameLoader snapshot before the event. See https://crbug.com/824761.
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess = model.rendererProcess;
const mainThread = model.rendererMain;
addNavigationStart_(mainThread, 200,
{isLoadingMainFrame: true, documentLoaderURL: 'http://example.com'});
addNetworkRequest_(mainThread, 200, 250);
// FrameLoader creation time after navigation start.
rendererProcess.objects.idWasCreated(
'ptr', 'loading', 'FrameLoader', 250);
rendererProcess.objects.addSnapshot(
'ptr', 'loading', 'FrameLoader', 300, {
isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'},
documentLoaderURL: 'http://example.com',
});
addFirstContentfulPaint_(mainThread, 500);
addFirstMeaningfulPaint_(mainThread, 510);
addDomContentLoadedEnd_(mainThread, 600);
// New navigation to close the search window.
addNavigationStart_(mainThread, 7000,
{isLoadingMainFrame: true, documentLoaderURL: 'http://example.com'});
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const fmpHist = histograms.getHistogramNamed('timeToFirstMeaningfulPaint');
assert.strictEqual(1, fmpHist.running.count);
assert.strictEqual(310, fmpHist.running.mean);
// Both TTI and First CPU Idle firing at DCL.
const ttiHist = histograms.getHistogramNamed('timeToInteractive');
assert.strictEqual(1, ttiHist.running.count);
assert.strictEqual(400, ttiHist.running.mean);
const firstCpuIdleHist = histograms.getHistogramNamed('timeToFirstCpuIdle');
assert.strictEqual(1, firstCpuIdleHist.running.count);
assert.strictEqual(400, firstCpuIdleHist.running.mean);
});
test('interactivityMetrics_notAffectedByShortTasks', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess = model.rendererProcess;
const mainThread = model.rendererMain;
addNavigationStart_(mainThread, 200);
addNetworkRequest_(mainThread, 200, 250);
addFrameLoaderObject_(rendererProcess, 300);
addFirstContentfulPaint_(mainThread, 500);
addDomContentLoadedEnd_(mainThread, 600);
const mainThreadTopLevelTasks = [
{start: 800, duration: 100}, // Long task
{start: 1500, duration: 200}, // Last long task. TTI at 1700.
{start: 2000, duration: 49}, // Short task.
];
for (const task of mainThreadTopLevelTasks) {
addTopLevelTask_(mainThread, task.start, task.duration);
}
// New navigation to close the search window.
addNavigationStart_(mainThread, 7000);
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const ttiHist = histograms.getHistogramNamed('timeToInteractive');
assert.strictEqual(1, ttiHist.running.count);
// 1700 - 200 (navStart) = 1500.
assert.strictEqual(1500, ttiHist.running.mean);
const firstCpuIdleHist = histograms.getHistogramNamed('timeToFirstCpuIdle');
assert.strictEqual(1, firstCpuIdleHist.running.count);
// 1700 - 200 (navStart) = 1500.
assert.strictEqual(1500, firstCpuIdleHist.running.mean);
});
test('interactivityMetrics_longTaskBeforeFCP', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess = model.rendererProcess;
const mainThread = model.rendererMain;
addNavigationStart_(mainThread, 200);
addNetworkRequest_(mainThread, 200, 50);
addFrameLoaderObject_(rendererProcess, 300);
addDomContentLoadedEnd_(mainThread, 600);
addTopLevelTask_(mainThread, 600, 200);
addFirstContentfulPaint_(mainThread, 1000); // TTI at FMP.
// New navigation to close the search window.
addNavigationStart_(mainThread, 7000);
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const ttiHist = histograms.getHistogramNamed('timeToInteractive');
assert.strictEqual(1, ttiHist.running.count);
// 1000 - 200 (navStart) = 800.
assert.strictEqual(800, ttiHist.running.mean);
const firstCpuIdleHist = histograms.getHistogramNamed('timeToFirstCpuIdle');
assert.strictEqual(1, firstCpuIdleHist.running.count);
// 1000 - 200 (navStart) = 800.
assert.strictEqual(800, firstCpuIdleHist.running.mean);
});
test('interactivityMetrics_interactiveAtDCL', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess = model.rendererProcess;
const mainThread = model.rendererMain;
addNavigationStart_(mainThread, 200);
addNetworkRequest_(mainThread, 200, 50);
addFrameLoaderObject_(rendererProcess, 300);
addFirstContentfulPaint_(mainThread, 1000);
addDomContentLoadedEnd_(mainThread, 3000);
// New navigation to close the search window.
addNavigationStart_(mainThread, 7000);
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const ttiHist = histograms.getHistogramNamed('timeToInteractive');
assert.strictEqual(1, ttiHist.running.count);
// 3000 - 200 (navStart) = 2800.
assert.strictEqual(2800, ttiHist.running.mean);
const firstCpuIdleHist = histograms.getHistogramNamed('timeToFirstCpuIdle');
assert.strictEqual(1, firstCpuIdleHist.running.count);
// 3000 - 200 (navStart) = 2800.
assert.strictEqual(2800, firstCpuIdleHist.running.mean);
});
test('interactivityMetrics_networkBusyBlocksInteractivity', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess = model.rendererProcess;
const mainThread = model.rendererMain;
addNavigationStart_(mainThread, 200);
addFrameLoaderObject_(rendererProcess, 300);
addFirstContentfulPaint_(mainThread, 1000);
addDomContentLoadedEnd_(mainThread, 1100);
// Network busy requires at least three network requests.
addNetworkRequest_(mainThread, 1000, 7000);
addNetworkRequest_(mainThread, 1001, 7001);
addNetworkRequest_(mainThread, 1002, 7002);
// 400ms task makes a "heavy task cluster" for idle.
addTopLevelTask_(mainThread, 1200, 400);
// Next long task is more than five seconds away, but TTI is not reached
// yet since network is busy. TTI is at the at of this task.
addTopLevelTask_(mainThread, 6800, 200);
// New navigation to close the search window.
addNavigationStart_(mainThread, 13000);
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const ttiHist = histograms.getHistogramNamed('timeToInteractive');
assert.strictEqual(1, ttiHist.running.count);
// 7000 - 200 (navStart) = 6800.
assert.strictEqual(6800, ttiHist.running.mean);
const firstCpuIdleHist = histograms.getHistogramNamed('timeToFirstCpuIdle');
assert.strictEqual(1, firstCpuIdleHist.running.count);
// 1600 - 200 (navStart) = 1400. CPU Idle is not affected by network.
assert.strictEqual(1400, firstCpuIdleHist.running.mean);
});
test('interactivityMetrics_notReportedIfTracingEndsEarly', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess = model.rendererProcess;
const mainThread = model.rendererMain;
addNavigationStart_(mainThread, 200);
addNetworkRequest_(mainThread, 200, 50);
addFrameLoaderObject_(rendererProcess, 300);
addDomContentLoadedEnd_(mainThread, 600);
addFirstContentfulPaint_(mainThread, 1000);
addTopLevelTask_(mainThread, 2000, 400);
// Last task in the model. 2501 will be considered end of tracing.
addTopLevelTask_(mainThread, 2500, 1);
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const ttiHist = histograms.getHistogramNamed('timeToInteractive');
assert.strictEqual(0, ttiHist.numValues);
const firstCpuIdleHist = histograms.getHistogramNamed('timeToFirstCpuIdle');
assert.strictEqual(0, firstCpuIdleHist.numValues);
});
test('interactivityMetrics_notReportedIfNextNavigationIsEarly', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess = model.rendererProcess;
const mainThread = model.rendererMain;
addNavigationStart_(mainThread, 200);
addNetworkRequest_(mainThread, 200, 50);
addFrameLoaderObject_(rendererProcess, 300);
addDomContentLoadedEnd_(mainThread, 600);
addFirstContentfulPaint_(mainThread, 1000);
addTopLevelTask_(mainThread, 2000, 400);
// New navigation to close the search window. The window is not big enough
// to reach TTI.
addNavigationStart_(mainThread, 3000);
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const ttiHist = histograms.getHistogramNamed('timeToInteractive');
assert.strictEqual(0, ttiHist.numValues);
const firstCpuIdleHist = histograms.getHistogramNamed('timeToFirstCpuIdle');
assert.strictEqual(0, firstCpuIdleHist.numValues);
});
test('interactivityMetrics_reportsValueForLastNavigation', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess = model.rendererProcess;
const mainThread = model.rendererMain;
addNavigationStart_(mainThread, 200);
addNetworkRequest_(mainThread, 200, 50);
addFrameLoaderObject_(rendererProcess, 300);
addDomContentLoadedEnd_(mainThread, 600);
addFirstContentfulPaint_(mainThread, 1000);
addTopLevelTask_(mainThread, 2000, 400);
// Last task in the model. 8001 will be considered end of tracing, so
// there is sufficiently large window to detect TTI.
addTopLevelTask_(mainThread, 8000, 1);
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const ttiHist = histograms.getHistogramNamed('timeToInteractive');
assert.strictEqual(1, ttiHist.running.count);
// 2400 - 200 (navStart) = 2200.
assert.strictEqual(2200, ttiHist.running.mean);
const firstCpuIdleHist = histograms.getHistogramNamed('timeToFirstCpuIdle');
assert.strictEqual(1, firstCpuIdleHist.running.count);
// 2400 - 200 (navStart) = 2200.
assert.strictEqual(2200, firstCpuIdleHist.running.mean);
});
test('interactivityMetrics_multipleRenderers', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const renderers =
[model.getOrCreateProcess(1984), model.getOrCreateProcess(1985)];
for (const i of [0, 1]) {
const rendererProcess = renderers[i];
const mainThread = rendererProcess.getOrCreateThread(2);
mainThread.name = 'CrRendererMain';
addNavigationStart_(mainThread, 200);
addNetworkRequest_(mainThread, 200, 50);
addFrameLoaderObject_(rendererProcess, 300);
addDomContentLoadedEnd_(mainThread, 600);
addFirstContentfulPaint_(mainThread, 1000);
// Durations are 400 and 800 for i value of 0 an 1.
addTopLevelTask_(mainThread, 2000, (i + 1) * 400);
// New navigation to close the search window.
addNavigationStart_(mainThread, 10000);
}
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const ttiHist = histograms.getHistogramNamed('timeToInteractive');
assert.strictEqual(2, ttiHist.running.count);
// 2800 - 200 (navStart) = 2200, and 2400 - 200 = 2200.
assert.strictEqual(2600, ttiHist.running.max);
assert.strictEqual(2200, ttiHist.running.min);
const firstCpuIdleHist = histograms.getHistogramNamed('timeToFirstCpuIdle');
assert.strictEqual(2, firstCpuIdleHist.running.count);
// 2800 - 200 (navStart) = 2200, and 2400 - 200 = 2200.
assert.strictEqual(2600, firstCpuIdleHist.running.max);
assert.strictEqual(2200, firstCpuIdleHist.running.min);
});
test('interactivityMetrics_eventsFromMultipleFrame', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess = model.rendererProcess;
const mainThread = model.rendererMain;
addNavigationStart_(mainThread, 200);
addNetworkRequest_(mainThread, 200, 50);
addFrameLoaderObject_(rendererProcess, 300);
addFirstContentfulPaint_(mainThread, 1000);
// No long task. TTI is reached at 3000.
addDomContentLoadedEnd_(mainThread, 3000);
// DomContentLoadedEnd and NavigationStart for a different frame.
rendererProcess.objects.addSnapshot(
'ptr', 'loading', 'FrameLoader', 4000, {
isLoadingMainFrame: true, frame: {id_ref: '0xffffffff'},
documentLoaderURL: 'http://example.com'
});
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'blink.user_timing',
title: 'navigationStart',
start: 4000,
duration: 0.0,
args: {frame: '0xffffffff'},
}));
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'blink.user_timing',
title: 'domContentLoadedEventEnd',
start: 4500,
duration: 0.0,
args: {frame: '0xffffffff'}
}));
// Last task in the model. 8001 will be considered end of tracing, so
// there is sufficiently large window to detect TTI.
addTopLevelTask_(mainThread, 8000, 1);
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const ttiHist = histograms.getHistogramNamed('timeToInteractive');
assert.strictEqual(1, ttiHist.running.count);
// 3000 - 200 (navStart) = 2800.
assert.strictEqual(2800, ttiHist.running.mean);
const firstCpuIdleHist = histograms.getHistogramNamed('timeToFirstCpuIdle');
assert.strictEqual(1, firstCpuIdleHist.running.count);
// 3000 - 200 (navStart) = 2800.
assert.strictEqual(2800, firstCpuIdleHist.running.mean);
});
test('timeToInteractive_notReportedWithoutResourceLoadEvents', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess = model.rendererProcess;
const mainThread = model.rendererMain;
addNavigationStart_(mainThread, 200);
addFrameLoaderObject_(rendererProcess, 300);
addDomContentLoadedEnd_(mainThread, 600);
addFirstContentfulPaint_(mainThread, 1000);
addTopLevelTask_(mainThread, 2000, 400);
// Last task in the model. 8001 will be considered end of tracing, so
// there is sufficiently large window to detect TTI.
addTopLevelTask_(mainThread, 8000, 1);
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const ttiHist = histograms.getHistogramNamed('timeToInteractive');
assert.strictEqual(0, ttiHist.numValues);
});
test('webView', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const process = model.rendererProcess;
const rendererThread = model.rendererMain;
rendererThread.name = 'Chrome_InProcRendererThread';
rendererThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'blink.user_timing',
title: 'navigationStart',
start: 200,
duration: 0.0,
args: {frame: '0xdeadbeef'}
}));
process.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300,
{
isLoadingMainFrame: true,
frame: {id_ref: '0xdeadbeef'},
documentLoaderURL: 'http://example.com'
});
rendererThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading,rail,devtools.timeline',
title: 'firstContentfulPaint',
start: 600,
duration: 0.0,
args: {frame: '0xdeadbeef'}
}));
rendererThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: 'firstMeaningfulPaintCandidate',
start: 1000,
duration: 0.0,
args: {frame: '0xdeadbeef'}
}));
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const fcp = histograms.getHistogramNamed('timeToFirstContentfulPaint');
assert.strictEqual(1, fcp.running.count);
assert.strictEqual(400, fcp.running.mean);
const fmp = histograms.getHistogramNamed('timeToFirstMeaningfulPaint');
assert.strictEqual(1, fmp.running.count);
assert.strictEqual(800, fmp.running.mean);
});
test('testGetNetworkEvents', function() {
// Our renderer looks like:
// [ irrelevant syncEvent ]
// [ irrelevant asyncEvent ]
// | [ d..netlog]
// [ netlog ] [ d..network] [ net ]
// | | | | | |
// | | | | | |
// | | | | | |
// v v v v v v
// Ts: 100 200 400 450 510 520
const rendererPid = 245;
const netEvent1 = tr.c.TestUtils.newAsyncSliceEx({
cat: 'netlog',
title: 'Generic Network event',
start: 100,
duration: 100,
});
const netEvent2 = tr.c.TestUtils.newAsyncSliceEx({
cat: 'disabled-by-default-network',
title: 'ResourceLoad',
start: 400,
duration: 50,
});
const netEvent3 = tr.c.TestUtils.newAsyncSliceEx({
cat: 'net',
title: 'ResourceLoad',
start: 510,
duration: 10,
});
const netEvent4 = tr.c.TestUtils.newAsyncSliceEx({
cat: 'disabled-by-default-netlog',
title: 'ResourceLoad',
start: 510,
duration: 10,
});
const irrelevantAsyncEvent = tr.c.TestUtils.newAsyncSliceEx({
cat: 'irrelevant',
title: 'ResourceLoad',
start: 0,
duration: 510,
});
const irrelevantSyncEvent = tr.c.TestUtils.newSliceEx({
cat: 'blink.user_timing',
title: 'navigationStart',
start: 0,
duration: 510,
args: {frame: '0xdeadbeef'}
});
const model = tr.c.TestUtils.newModel(function(model) {
const rendererProcess = model.getOrCreateProcess(rendererPid);
const thread1 = rendererProcess.getOrCreateThread(1);
thread1.name = 'CrRendererMain';
thread1.asyncSliceGroup.push(netEvent1);
const thread2 = rendererProcess.getOrCreateThread(2);
thread2.name = 'thread2';
thread2.asyncSliceGroup.push(netEvent2);
const thread3 = rendererProcess.getOrCreateThread(3);
thread3.name = 'thread2';
thread3.asyncSliceGroup.push(netEvent3);
const thread4 = rendererProcess.getOrCreateThread(4);
thread4.name = 'thread2';
thread4.asyncSliceGroup.push(netEvent4);
const thread5 = rendererProcess.getOrCreateThread(5);
thread5.name = 'thread5';
thread5.asyncSliceGroup.push(irrelevantAsyncEvent);
const thread6 = rendererProcess.getOrCreateThread(6);
thread6.name = 'thread6';
thread6.sliceGroup.pushSlice(irrelevantSyncEvent);
});
const chromeHelper = model.getOrCreateHelper(
tr.model.helpers.ChromeModelHelper);
const rendererHelper = chromeHelper.rendererHelpers[rendererPid];
const allNetworkEvents =
tr.e.chrome.EventFinderUtils.getNetworkEventsInRange(
rendererHelper.process, tr.b.math.Range.fromExplicitRange(0, 550));
assert.sameDeepMembers(
[netEvent1, netEvent2, netEvent3, netEvent4],
allNetworkEvents);
const partialNetworkEvents =
tr.e.chrome.EventFinderUtils.getNetworkEventsInRange(
rendererHelper.process, tr.b.math.Range.fromExplicitRange(0, 460));
assert.strictEqual(2, partialNetworkEvents.length);
assert.sameDeepMembers(
[netEvent1, netEvent2],
partialNetworkEvents);
const networkEventsWithIntersecting =
tr.e.chrome.EventFinderUtils.getNetworkEventsInRange(
rendererHelper.process, tr.b.math.Range.fromExplicitRange(0, 410));
assert.sameDeepMembers(
[netEvent1, netEvent2],
partialNetworkEvents);
});
test('mainFrameCumulativeLayoutShift', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess = model.rendererProcess;
const mainThread = model.rendererMain;
const mainFrame = {id: '0xdeadbeef', is_main: true};
const subframe = {id: '0xdeadb33f', is_main: false};
const emitEvent = (time, cumulativeScore, frame) => {
const data = {
is_main_frame: frame.is_main,
cumulative_score: cumulativeScore
};
mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: 'LayoutShift',
start: time,
duration: 0.0,
args: {frame: frame.id, data}
}));
};
emitEvent(1000, 1.5, mainFrame);
emitEvent(3000, 3.5, mainFrame);
emitEvent(2000, 3.0, mainFrame);
emitEvent(4000, 4.0, subframe);
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const hist = histograms.getHistogramNamed('mainFrameCumulativeLayoutShift');
assert.strictEqual(1, hist.sampleValues.length);
assert.strictEqual(1, hist.running.count);
assert.strictEqual(3.5, hist.running.mean);
});
test('overallCumulativeLayoutShift', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess = model.rendererProcess;
const mainThread = model.rendererMain;
const mainFrame = {id: '0xdeadbeef', is_main: true};
const subframe = {id: '0xdeadb33f', is_main: false};
const emitEvent = (thread, time, score, weightedScore, frame) => {
const data = {
is_main_frame: frame.is_main,
score,
weighted_score_delta: weightedScore
};
thread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
cat: 'loading',
title: 'LayoutShift',
start: time,
duration: 0.0,
args: {frame: frame.id, data}
}));
};
emitEvent(mainThread, 1000, 1.5, 1000, mainFrame);
emitEvent(mainThread, 3000, 3.5, 1000, mainFrame);
emitEvent(mainThread, 2000, 3.0, 1000, mainFrame);
emitEvent(mainThread, 4000, 4000, 4.0, subframe);
const rendererProcess2 = model.getOrCreateProcess(10);
const mainThread2 = rendererProcess2.getOrCreateThread(20);
mainThread2.name = 'CrRendererMain';
const subframe2 = { id: '0xdeadb33f', is_main: false };
emitEvent(mainThread2, 2500, 1000, 2.0, subframe2);
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const hist = histograms.getHistogramNamed('overallCumulativeLayoutShift');
assert.strictEqual(1, hist.sampleValues.length);
assert.strictEqual(1, hist.running.count);
assert.strictEqual(14, hist.running.mean);
});
test('speedIndexIsAddedToHistograms', function() {
// The speed index code is fully tested in
// rects_based_speed_index_metric_test.
// Here, just verify a histogram is added in the loading metric.
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess = model.rendererProcess;
const mainThread = model.rendererMain;
addNavigationStart_(mainThread, 200);
addFrameLoaderObject_(rendererProcess, 300);
addDomContentLoadedEnd_(mainThread, 600);
addFirstMeaningfulPaint_(mainThread, 1000);
addTopLevelTask_(mainThread, 2000, 400);
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const hist = histograms.getHistogramNamed('rectsBasedSpeedIndex');
assert.isNotNull(hist);
});
test('totalBlockingTime_looksAtCorrectInterval', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess = model.rendererProcess;
const mainThread = model.rendererMain;
addNavigationStart_(mainThread, 200);
addFrameLoaderObject_(rendererProcess, 300);
addNetworkRequest_(mainThread, 200, 100);
addDomContentLoadedEnd_(mainThread, 600);
// Contributes 0 since it's before FCP.
addTopLevelTask_(mainThread, 800, 100);
addFirstContentfulPaint_(mainThread, 1000);
addTopLevelTask_(mainThread, 2000, 400); // Contributes 350.
addTopLevelTask_(mainThread, 2500, 300); // Contributes 250.
// Contributes 0 since it's after TTI. TTI at 2800.
addTopLevelTask_(mainThread, 10000, 300);
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const hist = histograms.getHistogramNamed('totalBlockingTime');
assert.strictEqual(1, hist.running.count);
assert.strictEqual(600, hist.running.mean);
});
test('totalBlockingTime_performsProperClipping', function() {
const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
const rendererProcess = model.rendererProcess;
const mainThread = model.rendererMain;
addNavigationStart_(mainThread, 200);
addFrameLoaderObject_(rendererProcess, 300);
addNetworkRequest_(mainThread, 200, 100);
// Contributes 150 since only 250ms is after FCP.
addTopLevelTask_(mainThread, 800, 400);
addFirstContentfulPaint_(mainThread, 1000);
// Task straddles DCL, but contributes 0 since duration < 50ms.
addTopLevelTask_(mainThread, 1980, 49);
addDomContentLoadedEnd_(mainThread, 2000); // TTI fires at DCL: 2000ms.
// Sufficiently far task to trigger TTI.
addTopLevelTask_(mainThread, 10000, 0);
});
const histograms = new tr.v.HistogramSet();
tr.metrics.sh.loadingMetric(histograms, model);
const hist = histograms.getHistogramNamed('totalBlockingTime');
assert.strictEqual(1, hist.running.count);
assert.strictEqual(150, hist.running.mean);
});
});
</script>