blob: ef8ce7307eecabd61aad97a6a4c122f41a8b7c00 [file] [log] [blame]
<!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/base/category_util.html">
<link rel="import" href="/tracing/base/math/statistics.html">
<link rel="import" href="/tracing/extras/chrome/cc/display_item_list.html">
<link rel="import" href="/tracing/extras/chrome/event_finder_utils.html">
<link rel="import" href="/tracing/extras/chrome/largest_contentful_paint.html">
<link rel="import" href="/tracing/extras/chrome/time_to_interactive.html">
<link rel="import" href="/tracing/metrics/metric_registry.html">
<link rel="import" href="/tracing/metrics/system_health/breakdown_tree_helpers.html">
<link rel="import" href="/tracing/metrics/system_health/rects_based_speed_index_metric.html">
<link rel="import" href="/tracing/metrics/system_health/utils.html">
<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
<link rel="import" href="/tracing/model/helpers/chrome_thread_helper.html">
<link rel="import" href="/tracing/model/timed_event.html">
<link rel="import" href="/tracing/value/diagnostics/alert_groups.html">
<link rel="import" href="/tracing/value/diagnostics/diagnostic_map.html">
<link rel="import" href="/tracing/value/histogram.html">
<script>
'use strict';
tr.exportTo('tr.metrics.sh', function() {
const LONG_TASK_THRESHOLD_MS = 50;
const timeDurationInMs_smallerIsBetter =
tr.b.Unit.byName.timeDurationInMs_smallerIsBetter;
const unitlessNumber_smallerIsBetter =
tr.b.Unit.byName.unitlessNumber_smallerIsBetter;
const RelatedEventSet = tr.v.d.RelatedEventSet;
const hasCategoryAndName = tr.metrics.sh.hasCategoryAndName;
const EventFinderUtils = tr.e.chrome.EventFinderUtils;
/**
* @param {!Object.<string, Object>} breakdownTree
* @return {tr.v.d.Breakdown} A breakdown with categories and the total time
* (ms) spent under each category.
*/
function createBreakdownDiagnostic(breakdownTree) {
const breakdownDiagnostic = new tr.v.d.Breakdown();
breakdownDiagnostic.colorScheme =
tr.v.d.COLOR_SCHEME_CHROME_USER_FRIENDLY_CATEGORY_DRIVER;
for (const label in breakdownTree) {
breakdownDiagnostic.set(label, breakdownTree[label].total);
}
return breakdownDiagnostic;
}
const LOADING_METRIC_BOUNDARIES = tr.v.HistogramBinBoundaries
.createLinear(0, 1e3, 20) // 50ms step to 1s
.addLinearBins(3e3, 20) // 100ms step to 3s
.addExponentialBins(20e3, 20);
const TIME_TO_INTERACTIVE_BOUNDARIES = tr.v.HistogramBinBoundaries
// 90-th percentiile of TTI is around 40 seconds, across warm and cold
// loads. Data obtained through Cluster Telemetry analysis.
.createExponential(1, 40e3, 35)
.addExponentialBins(80e3, 15);
const LAYOUT_SHIFT_SCORE_BOUNDARIES = tr.v.HistogramBinBoundaries
.createLinear(0, 50, 25);
const SUMMARY_OPTIONS = {
avg: true,
count: false,
max: true,
min: true,
std: true,
sum: false,
};
function findFrameLoaderSnapshotAt(rendererHelper, frameIdRef, ts) {
const objects = rendererHelper.process.objects;
const frameLoaderInstances = objects.instancesByTypeName_.FrameLoader;
if (frameLoaderInstances === undefined) return undefined;
let snapshot;
for (const instance of frameLoaderInstances) {
if (!instance.isAliveAt(ts)) continue;
const maybeSnapshot = instance.getSnapshotAt(ts);
if (frameIdRef !== maybeSnapshot.args.frame.id_ref) continue;
snapshot = maybeSnapshot;
}
return snapshot;
}
function findAllEvents(rendererHelper, category, title) {
const targetEvents = [];
for (const ev of rendererHelper.process.getDescendantEvents()) {
if (!hasCategoryAndName(ev, category, title)) continue;
targetEvents.push(ev);
}
return targetEvents;
}
function getMostRecentValidEvent(rendererHelper, category, title) {
const targetEvents = findAllEvents(rendererHelper, category, title);
// Want to keep the event with the most recent timestamp
let validEvent;
for (const targetEvent of targetEvents) {
if (rendererHelper.isTelemetryInternalEvent(targetEvent)) continue;
if (validEvent === undefined) {
validEvent = targetEvent;
} else {
// Want to keep the event with the most recent timestamp
if (validEvent.start < targetEvent.start) {
validEvent = targetEvent;
}
}
}
return validEvent;
}
function getFirstViewportReadySamples(rendererHelper,
navIdToNavStartEvents) {
const samples = [];
// Want to measure the time from when navigation starts to when the load
// event fired for all non-ad resources. This done with the associated
// navigation start event to the pc mark in the amp code, correlated by
// navigation id.
const pcEvent = getMostRecentValidEvent(
rendererHelper, 'blink.user_timing', 'pc');
if (pcEvent === undefined) return samples;
if (rendererHelper.isTelemetryInternalEvent(pcEvent)) return samples;
const navigationStartEvent = navIdToNavStartEvents.get(
pcEvent.args.data.navigationId);
if (navigationStartEvent === undefined) return samples;
const navStartToEventRange = tr.b.math.Range.fromExplicitRange(
navigationStartEvent.start, pcEvent.start);
const networkEvents = EventFinderUtils.getNetworkEventsInRange(
rendererHelper.process, navStartToEventRange);
// Main thread may be missing. See crbug.com/1059726 for an example.
if (rendererHelper.mainThread === undefined) return samples;
const breakdownTree = tr.metrics.sh.generateWallClockTimeBreakdownTree(
rendererHelper.mainThread, networkEvents, navStartToEventRange);
samples.push({
value: navStartToEventRange.duration,
breakdownTree,
diagnostics: {
breakdown: createBreakdownDiagnostic(breakdownTree),
Start: new RelatedEventSet(navigationStartEvent),
End: new RelatedEventSet(pcEvent)
}
});
return samples;
}
function getAboveTheFoldLoadedToVisibleSamples(rendererHelper) {
const samples = [];
// Want to measure the time from when the document is visible to the time
// when the load event fired for all non-ad resources. This is done with
// two marks in the amp code: pc and visible.
const pcEvent = getMostRecentValidEvent(
rendererHelper, 'blink.user_timing', 'pc');
const visibleEvent = getMostRecentValidEvent(
rendererHelper, 'blink.user_timing', 'visible');
if (pcEvent !== undefined && visibleEvent !== undefined) {
samples.push({
value: Math.max(0.0, pcEvent.start - visibleEvent.start),
diagnostics: {
Start: new RelatedEventSet(visibleEvent),
End: new RelatedEventSet(pcEvent)
}
});
}
return samples;
}
// Assume that this event has |event.arg.frame|.
function findTimeToXEntries(
category, eventName, rendererHelper, frameToNavStartEvents,
navIdToNavStartEvents) {
const targetEvents = findAllEvents(rendererHelper, category, eventName);
const entries = [];
for (const targetEvent of targetEvents) {
if (rendererHelper.isTelemetryInternalEvent(targetEvent)) continue;
const frameIdRef = targetEvent.args.frame;
const snapshot = findFrameLoaderSnapshotAt(
rendererHelper, frameIdRef, targetEvent.start);
if (snapshot === undefined || !snapshot.args.isLoadingMainFrame) continue;
const url = snapshot.args.documentLoaderURL;
if (tr.e.chrome.CHROME_INTERNAL_URLS.includes(url)) continue;
let navigationStartEvent;
if (targetEvent.args.data === undefined ||
targetEvent.args.data.navigationId === undefined) {
navigationStartEvent =
EventFinderUtils.findLastEventStartingOnOrBeforeTimestamp(
frameToNavStartEvents.get(frameIdRef) || [], targetEvent.start);
} else {
navigationStartEvent = navIdToNavStartEvents.get(
targetEvent.args.data.navigationId);
}
// Ignore layout w/o preceding navigationStart, as they are not
// attributed to any time-to-X metric.
if (navigationStartEvent === undefined) continue;
entries.push({
navigationStartEvent,
targetEvent,
url,
});
}
return entries;
}
function collectTimeToEvent(rendererHelper, timeToXEntries) {
const samples = [];
for (const { targetEvent, navigationStartEvent, url } of timeToXEntries) {
const navStartToEventRange = tr.b.math.Range.fromExplicitRange(
navigationStartEvent.start, targetEvent.start);
const networkEvents = EventFinderUtils.getNetworkEventsInRange(
rendererHelper.process, navStartToEventRange);
const breakdownTree = tr.metrics.sh.generateWallClockTimeBreakdownTree(
rendererHelper.mainThread, networkEvents, navStartToEventRange);
samples.push({
value: navStartToEventRange.duration,
breakdownTree,
diagnostics: {
breakdown: createBreakdownDiagnostic(breakdownTree),
url: new tr.v.d.GenericSet([url]),
Start: new RelatedEventSet(navigationStartEvent),
End: new RelatedEventSet(targetEvent)
}
});
}
return samples;
}
function collectTimeToEventInCpuTime(rendererHelper, timeToXEntries) {
const samples = [];
for (const { targetEvent, navigationStartEvent, url } of timeToXEntries) {
const navStartToEventRange = tr.b.math.Range.fromExplicitRange(
navigationStartEvent.start, targetEvent.start);
const mainThreadCpuTime =
rendererHelper.mainThread.getCpuTimeForRange(navStartToEventRange);
const breakdownTree = tr.metrics.sh.generateCpuTimeBreakdownTree(
rendererHelper.mainThread, navStartToEventRange);
samples.push({
value: mainThreadCpuTime,
breakdownTree,
diagnostics: {
breakdown: createBreakdownDiagnostic(breakdownTree),
start: new RelatedEventSet(navigationStartEvent),
end: new RelatedEventSet(targetEvent),
infos: new tr.v.d.GenericSet([{
pid: rendererHelper.pid,
start: navigationStartEvent.start,
event: targetEvent.start,
}]),
}
});
}
return samples;
}
function findMainFrameLayoutShiftSamples(rendererHelper) {
let sample;
EventFinderUtils.getSortedMainThreadEventsByFrame(rendererHelper,
'LayoutShift', 'loading').forEach((events) => {
// We want the final cumulative score, so look at the last event.
const evData = events.pop().args.data;
if (evData.is_main_frame) {
sample = {value: evData.cumulative_score};
}
});
return sample ? [sample] : [];
}
function findAllLayoutShiftSamples(chromeHelper) {
let total = 0;
let foundMainFrame = false;
for (const pid in chromeHelper.rendererHelpers) {
const rendererHelper = chromeHelper.rendererHelpers[pid];
if (rendererHelper.isChromeTracingUI) continue;
tr.e.chrome.EventFinderUtils.getSortedMainThreadEventsByFrame(
rendererHelper, 'LayoutShift', 'loading').forEach((events) => {
// Add up the scores for main frames and weighted scores for subframes.
for (const event of events) {
const evData = event.args.data;
if (evData.is_main_frame) {
total += evData.score;
foundMainFrame = true;
} else {
total += evData.weighted_score_delta;
}
}
});
}
return foundMainFrame ? [{value: total}] : [];
}
function addFirstMeaningfulPaintSample(samples, rendererHelper,
navigationStart, fmpMarkerEvent, url) {
const navStartToFMPRange = tr.b.math.Range.fromExplicitRange(
navigationStart.start, fmpMarkerEvent.start);
const networkEvents = EventFinderUtils.getNetworkEventsInRange(
rendererHelper.process, navStartToFMPRange);
const timeToFirstMeaningfulPaint = navStartToFMPRange.duration;
const breakdownTree = tr.metrics.sh.generateWallClockTimeBreakdownTree(
rendererHelper.mainThread, networkEvents, navStartToFMPRange);
samples.push({
value: timeToFirstMeaningfulPaint,
breakdownTree,
diagnostics: {
breakdown: createBreakdownDiagnostic(breakdownTree),
start: new RelatedEventSet(navigationStart),
end: new RelatedEventSet(fmpMarkerEvent),
infos: new tr.v.d.GenericSet([{
url,
pid: rendererHelper.pid,
start: navigationStart.start,
fmp: fmpMarkerEvent.start,
}]),
}
});
}
function addFirstMeaningfulPaintCpuTimeSample(samples, rendererHelper,
navigationStart, fmpMarkerEvent, url) {
const navStartToFMPRange = tr.b.math.Range.fromExplicitRange(
navigationStart.start, fmpMarkerEvent.start);
const mainThreadCpuTime =
rendererHelper.mainThread.getCpuTimeForRange(navStartToFMPRange);
const breakdownTree = tr.metrics.sh.generateCpuTimeBreakdownTree(
rendererHelper.mainThread, navStartToFMPRange);
samples.push({
value: mainThreadCpuTime,
breakdownTree,
diagnostics: {
breakdown: createBreakdownDiagnostic(breakdownTree),
start: new RelatedEventSet(navigationStart),
end: new RelatedEventSet(fmpMarkerEvent),
infos: new tr.v.d.GenericSet([{
url,
pid: rendererHelper.pid,
start: navigationStart.start,
fmp: fmpMarkerEvent.start,
}]),
}
});
}
/**
* Object containing one value and associated diagnostics info for that value
* for a metric.
* @typedef {{value: number, diagnostics: !tr.v.d.DiagnosticMap}} MetricSample
*/
/**
* Returns a MetricSample for interactivity metrics - First CPU Idle and Time
* to Interactive.
*
* @param {tr.model.helpers.ChromeRendererHelper} rendererHelper
* @param {?number} eventTimestamp - Timestamp of the event for which the
* sample is being generated.
* @param {tr.model.ThreadSlice} navigationStartEvent
* @param {number} firstContentfulPaintTime
* @param {number} domContentLoadedEndTime
* @param {string} url - URL of the current main frame document.
* @returns {MetricSample|undefined}
*/
function decorateInteractivitySampleWithDiagnostics_(rendererHelper,
eventTimestamp, navigationStartEvent, firstContentfulPaintTime,
domContentLoadedEndTime, url) {
if (eventTimestamp === undefined) return undefined;
const navigationStartTime = navigationStartEvent.start;
const navStartToEventTimeRange =
tr.b.math.Range.fromExplicitRange(
navigationStartTime, eventTimestamp);
const networkEvents = EventFinderUtils.getNetworkEventsInRange(
rendererHelper.process, navStartToEventTimeRange);
const breakdownTree = tr.metrics.sh.generateWallClockTimeBreakdownTree(
rendererHelper.mainThread, networkEvents,
navStartToEventTimeRange);
const breakdownDiagnostic = createBreakdownDiagnostic(breakdownTree);
return {
value: navStartToEventTimeRange.duration,
diagnostics: tr.v.d.DiagnosticMap.fromObject({
'Start': new RelatedEventSet(navigationStartEvent),
'Navigation infos': new tr.v.d.GenericSet([{
url,
pid: rendererHelper.pid,
navigationStartTime,
firstContentfulPaintTime,
domContentLoadedEndTime,
// eventTimestamp can be derived from value and navigationStartEvent,
// but it's useful to directly see the value in the UI.
eventTimestamp,
}]),
'Breakdown of [navStart, eventTimestamp]': breakdownDiagnostic,
}),
};
}
function getCandidateIndex(entry) {
return entry.targetEvent.args.data.candidateIndex;
}
function findLastCandidateForEachNavigation(timeToXEntries) {
const entryMap = new Map();
for (const e of timeToXEntries) {
const navStartEvent = e.navigationStartEvent;
if (!entryMap.has(navStartEvent)) {
entryMap.set(navStartEvent, []);
}
entryMap.get(navStartEvent).push(e);
}
const lastCandidates = [];
for (const timeToXEntriesByNavigation of entryMap.values()) {
let lastCandidate = timeToXEntriesByNavigation.shift();
for (const entry of timeToXEntriesByNavigation) {
if (getCandidateIndex(entry) > getCandidateIndex(lastCandidate)) {
lastCandidate = entry;
}
}
lastCandidates.push(lastCandidate);
}
return lastCandidates;
}
function findLargestTextPaintSamples(rendererHelper, frameToNavStartEvents,
navIdToNavStartEvents) {
const timeToPaintEntries = findTimeToXEntries('loading',
'LargestTextPaint::Candidate', rendererHelper, frameToNavStartEvents,
navIdToNavStartEvents);
const timeToPaintBlockingEntries = findTimeToXEntries('loading',
'LargestTextPaint::NoCandidate', rendererHelper, frameToNavStartEvents,
navIdToNavStartEvents);
const lastCandidateEvents =
findLastCandidateForEachNavigation(
timeToPaintEntries.concat(timeToPaintBlockingEntries))
.filter(event =>
event.targetEvent.title !== 'LargestTextPaint::NoCandidate');
return collectTimeToEvent(rendererHelper, lastCandidateEvents);
}
function findLargestImagePaintSamples(rendererHelper, frameToNavStartEvents,
navIdToNavStartEvents) {
const timeToPaintEntries = findTimeToXEntries('loading',
'LargestImagePaint::Candidate', rendererHelper, frameToNavStartEvents,
navIdToNavStartEvents);
const timeToPaintBlockingEntries = findTimeToXEntries('loading',
'LargestImagePaint::NoCandidate', rendererHelper, frameToNavStartEvents,
navIdToNavStartEvents);
const lastCandidateEvents =
findLastCandidateForEachNavigation(
timeToPaintEntries.concat(timeToPaintBlockingEntries))
.filter(event =>
event.targetEvent.title !== 'LargestImagePaint::NoCandidate');
return collectTimeToEvent(rendererHelper, lastCandidateEvents);
}
function findLargestContentfulPaintHistogramSamples(allBrowserEvents) {
const lcp = new tr.e.chrome.LargestContentfulPaint(allBrowserEvents);
const lcpSamples = lcp.findCandidates().map(candidate => {
const { durationInMilliseconds, size, type, inMainFrame,
mainFrameTreeNodeId } = candidate;
return {
value: durationInMilliseconds,
diagnostics: {
size: new tr.v.d.GenericSet([size]),
type: new tr.v.d.GenericSet([type]),
inMainFrame: new tr.v.d.GenericSet([inMainFrame]),
mainFrameTreeNodeId: new tr.v.d.GenericSet([mainFrameTreeNodeId]),
},
};
});
return lcpSamples;
}
function collectLoadingMetricsForRenderer(rendererHelper) {
const frameToNavStartEvents =
EventFinderUtils.getSortedMainThreadEventsByFrame(
rendererHelper, 'navigationStart', 'blink.user_timing');
const navIdToNavStartEvents =
EventFinderUtils.getSortedMainThreadEventsByNavId(
rendererHelper, 'navigationStart', 'blink.user_timing');
const firstPaintSamples = collectTimeToEvent(rendererHelper,
findTimeToXEntries('loading', 'firstPaint', rendererHelper,
frameToNavStartEvents, navIdToNavStartEvents));
const timeToFCPEntries = findTimeToXEntries('loading',
'firstContentfulPaint', rendererHelper, frameToNavStartEvents,
navIdToNavStartEvents);
const firstContentfulPaintSamples = collectTimeToEvent(rendererHelper,
timeToFCPEntries);
const firstContentfulPaintCpuTimeSamples = collectTimeToEventInCpuTime(
rendererHelper, timeToFCPEntries);
const onLoadSamples = collectTimeToEvent(rendererHelper, findTimeToXEntries(
'blink.user_timing', 'loadEventStart', rendererHelper,
frameToNavStartEvents, navIdToNavStartEvents));
const aboveTheFoldLoadedToVisibleSamples =
getAboveTheFoldLoadedToVisibleSamples(rendererHelper);
const firstViewportReadySamples = getFirstViewportReadySamples(
rendererHelper, navIdToNavStartEvents);
const largestImagePaintSamples = findLargestImagePaintSamples(
rendererHelper, frameToNavStartEvents, navIdToNavStartEvents);
const largestTextPaintSamples = findLargestTextPaintSamples(
rendererHelper, frameToNavStartEvents,
navIdToNavStartEvents);
const mainFrameLayoutShiftSamples = findMainFrameLayoutShiftSamples(
rendererHelper);
const navigationStartSamples = timeToFCPEntries.map(entry => {
return { value: entry.navigationStartEvent.start};
});
return {
frameToNavStartEvents,
firstPaintSamples,
firstContentfulPaintSamples,
firstContentfulPaintCpuTimeSamples,
onLoadSamples,
aboveTheFoldLoadedToVisibleSamples,
firstViewportReadySamples,
largestImagePaintSamples,
largestTextPaintSamples,
mainFrameLayoutShiftSamples,
navigationStartSamples,
};
}
function collectMetricsFromLoadExpectations(model, chromeHelper) {
// Add FMP, firstCpuIdle and interactive samples from load UE
const interactiveSamples = [];
const firstCpuIdleSamples = [];
const firstMeaningfulPaintSamples = [];
const firstMeaningfulPaintCpuTimeSamples = [];
const totalBlockingTimeSamples = [];
for (const expectation of model.userModel.expectations) {
if (!(expectation instanceof tr.model.um.LoadExpectation)) continue;
if (tr.e.chrome.CHROME_INTERNAL_URLS.includes(expectation.url)) {
continue;
}
const rendererHelper = chromeHelper.rendererHelpers[
expectation.renderProcess.pid];
if (expectation.fmpEvent !== undefined) {
addFirstMeaningfulPaintSample(firstMeaningfulPaintSamples,
rendererHelper, expectation.navigationStart, expectation.fmpEvent,
expectation.url);
addFirstMeaningfulPaintCpuTimeSample(firstMeaningfulPaintCpuTimeSamples,
rendererHelper, expectation.navigationStart, expectation.fmpEvent,
expectation.url);
}
if (expectation.firstCpuIdleTime !== undefined) {
firstCpuIdleSamples.push(decorateInteractivitySampleWithDiagnostics_(
rendererHelper, expectation.firstCpuIdleTime,
expectation.navigationStart,
expectation.fcpEvent.start,
expectation.domContentLoadedEndEvent.start, expectation.url));
}
if (expectation.timeToInteractive !== undefined) {
interactiveSamples.push(decorateInteractivitySampleWithDiagnostics_(
rendererHelper, expectation.timeToInteractive,
expectation.navigationStart,
expectation.fcpEvent.start,
expectation.domContentLoadedEndEvent.start, expectation.url));
}
if (expectation.totalBlockingTime !== undefined) {
totalBlockingTimeSamples.push({
value: expectation.totalBlockingTime,
diagnostics: {
url: new tr.v.d.GenericSet([expectation.url]),
navigationStart: new RelatedEventSet(expectation.navigationStart),
firstContentfulPaint: new RelatedEventSet(expectation.fcpEvent),
interactiveTime: new tr.v.d.GenericSet(
[expectation.timeToInteractive]),
}});
}
}
return {
firstMeaningfulPaintSamples,
firstMeaningfulPaintCpuTimeSamples,
firstCpuIdleSamples,
interactiveSamples,
totalBlockingTimeSamples,
};
}
function addSamplesToHistogram(samples, histogram, histograms) {
for (const sample of samples) {
histogram.addSample(sample.value, sample.diagnostics);
// Only add breakdown histograms for FCP.
// http://crbug.com/771610
if (histogram.name !== 'timeToFirstContentfulPaint') continue;
if (!sample.breakdownTree) continue;
for (const [category, breakdown] of Object.entries(
sample.breakdownTree)) {
const relatedName = `${histogram.name}:${category}`;
let relatedHist = histograms.getHistogramsNamed(relatedName)[0];
if (!relatedHist) {
relatedHist = histograms.createHistogram(
relatedName, histogram.unit, [], {
binBoundaries: LOADING_METRIC_BOUNDARIES,
summaryOptions: {
count: false,
max: false,
min: false,
sum: false,
},
});
let relatedNames = histogram.diagnostics.get('breakdown');
if (!relatedNames) {
relatedNames = new tr.v.d.RelatedNameMap();
histogram.diagnostics.set('breakdown', relatedNames);
}
relatedNames.set(category, relatedName);
}
relatedHist.addSample(breakdown.total, {
breakdown: tr.v.d.Breakdown.fromEntries(
Object.entries(breakdown.events)),
});
}
}
}
function loadingMetric(histograms, model) {
const firstPaintHistogram = histograms.createHistogram(
'timeToFirstPaint', timeDurationInMs_smallerIsBetter, [], {
binBoundaries: LOADING_METRIC_BOUNDARIES,
description: 'time to first paint',
summaryOptions: SUMMARY_OPTIONS,
alertGrouping: [tr.v.d.ALERT_GROUPS.LOADING_PAINT],
});
const firstContentfulPaintHistogram = histograms.createHistogram(
'timeToFirstContentfulPaint', timeDurationInMs_smallerIsBetter, [], {
binBoundaries: LOADING_METRIC_BOUNDARIES,
description: 'time to first contentful paint',
summaryOptions: SUMMARY_OPTIONS,
alertGrouping: [tr.v.d.ALERT_GROUPS.LOADING_PAINT],
});
const firstContentfulPaintCpuTimeHistogram = histograms.createHistogram(
'cpuTimeToFirstContentfulPaint', timeDurationInMs_smallerIsBetter, [], {
binBoundaries: LOADING_METRIC_BOUNDARIES,
description: 'CPU time to first contentful paint',
summaryOptions: SUMMARY_OPTIONS,
alertGrouping: [tr.v.d.ALERT_GROUPS.LOADING_PAINT],
});
const onLoadHistogram = histograms.createHistogram(
'timeToOnload', timeDurationInMs_smallerIsBetter, [], {
binBoundaries: LOADING_METRIC_BOUNDARIES,
description: 'time to onload. ' +
'This is temporary metric used for PCv1/v2 sanity checking',
summaryOptions: SUMMARY_OPTIONS,
});
const firstMeaningfulPaintHistogram = histograms.createHistogram(
'timeToFirstMeaningfulPaint', timeDurationInMs_smallerIsBetter, [], {
binBoundaries: LOADING_METRIC_BOUNDARIES,
description: 'time to first meaningful paint',
summaryOptions: SUMMARY_OPTIONS,
alertGrouping: [tr.v.d.ALERT_GROUPS.LOADING_PAINT],
});
const firstMeaningfulPaintCpuTimeHistogram = histograms.createHistogram(
'cpuTimeToFirstMeaningfulPaint', timeDurationInMs_smallerIsBetter, [], {
binBoundaries: LOADING_METRIC_BOUNDARIES,
description: 'CPU time to first meaningful paint',
summaryOptions: SUMMARY_OPTIONS,
alertGrouping: [tr.v.d.ALERT_GROUPS.LOADING_PAINT],
});
const timeToInteractiveHistogram = histograms.createHistogram(
'timeToInteractive', timeDurationInMs_smallerIsBetter, [], {
binBoundaries: TIME_TO_INTERACTIVE_BOUNDARIES,
description: 'Time to Interactive',
summaryOptions: SUMMARY_OPTIONS,
alertGrouping: [tr.v.d.ALERT_GROUPS.LOADING_PAINT],
alertGrouping: [tr.v.d.ALERT_GROUPS.LOADING_INTERACTIVITY],
});
const totalBlockingTimeHistogram = histograms.createHistogram(
'totalBlockingTime', timeDurationInMs_smallerIsBetter, [], {
binBoundaries: TIME_TO_INTERACTIVE_BOUNDARIES,
description: 'Total Blocking Time',
summaryOptions: SUMMARY_OPTIONS,
alertGrouping: [tr.v.d.ALERT_GROUPS.LOADING_INTERACTIVITY],
});
const timeToFirstCpuIdleHistogram = histograms.createHistogram(
'timeToFirstCpuIdle', timeDurationInMs_smallerIsBetter, [], {
binBoundaries: TIME_TO_INTERACTIVE_BOUNDARIES,
description: 'Time to First CPU Idle',
summaryOptions: SUMMARY_OPTIONS,
alertGrouping: [tr.v.d.ALERT_GROUPS.LOADING_INTERACTIVITY],
});
const aboveTheFoldLoadedToVisibleHistogram = histograms.createHistogram(
'aboveTheFoldLoadedToVisible', timeDurationInMs_smallerIsBetter, [], {
binBoundaries: TIME_TO_INTERACTIVE_BOUNDARIES,
description: 'Time from first visible to load for AMP pages only.',
summaryOptions: SUMMARY_OPTIONS,
});
const firstViewportReadyHistogram = histograms.createHistogram(
'timeToFirstViewportReady', timeDurationInMs_smallerIsBetter, [], {
binBoundaries: TIME_TO_INTERACTIVE_BOUNDARIES,
description: 'Time from navigation to load for AMP pages only. ',
summaryOptions: SUMMARY_OPTIONS,
});
const largestImagePaintHistogram = histograms.createHistogram(
'largestImagePaint', timeDurationInMs_smallerIsBetter, [], {
binBoundaries: LOADING_METRIC_BOUNDARIES,
description: 'Time to Largest Image Paint',
summaryOptions: SUMMARY_OPTIONS,
});
const largestTextPaintHistogram = histograms.createHistogram(
'largestTextPaint', timeDurationInMs_smallerIsBetter, [], {
binBoundaries: LOADING_METRIC_BOUNDARIES,
description: 'Time to Largest Text Paint',
summaryOptions: SUMMARY_OPTIONS,
});
const largestContentfulPaintHistogram = histograms.createHistogram(
'largestContentfulPaint', timeDurationInMs_smallerIsBetter, [], {
binBoundaries: LOADING_METRIC_BOUNDARIES,
description: 'Time to Largest Contentful Paint',
summaryOptions: SUMMARY_OPTIONS,
alertGrouping: [tr.v.d.ALERT_GROUPS.LOADING_PAINT],
});
const mainFrameLayoutShiftHistogram = histograms.createHistogram(
'mainFrameCumulativeLayoutShift', unitlessNumber_smallerIsBetter, [], {
binBoundaries: LAYOUT_SHIFT_SCORE_BOUNDARIES,
description: 'Main Frame Document Cumulative Layout Shift Score',
summaryOptions: SUMMARY_OPTIONS,
alertGrouping: [tr.v.d.ALERT_GROUPS.LOADING_LAYOUT],
});
const allLayoutShiftHistogram = histograms.createHistogram(
'overallCumulativeLayoutShift', unitlessNumber_smallerIsBetter, [], {
binBoundaries: LAYOUT_SHIFT_SCORE_BOUNDARIES,
description: 'Document Cumulative Layout Shift Score with iframes',
summaryOptions: SUMMARY_OPTIONS,
alertGrouping: [tr.v.d.ALERT_GROUPS.LOADING_LAYOUT],
});
const navigationStartHistogram = histograms.createHistogram(
'navigationStart', timeDurationInMs_smallerIsBetter, [], {
binBoundaries: LOADING_METRIC_BOUNDARIES,
description: 'navigationStart',
summaryOptions: SUMMARY_OPTIONS,
});
tr.metrics.sh.rectsBasedSpeedIndexMetric(histograms, model);
const chromeHelper = model.getOrCreateHelper(
tr.model.helpers.ChromeModelHelper);
const allLayoutShiftSamples = findAllLayoutShiftSamples(chromeHelper);
addSamplesToHistogram(
allLayoutShiftSamples,
allLayoutShiftHistogram,
histograms);
for (const pid in chromeHelper.rendererHelpers) {
const rendererHelper = chromeHelper.rendererHelpers[pid];
if (rendererHelper.isChromeTracingUI) continue;
const samplesSet =
collectLoadingMetricsForRenderer(rendererHelper);
const lcpSamples = findLargestContentfulPaintHistogramSamples(
chromeHelper.browserHelper.mainThread.sliceGroup.slices);
addSamplesToHistogram(
lcpSamples, largestContentfulPaintHistogram, histograms);
addSamplesToHistogram(
samplesSet.firstPaintSamples, firstPaintHistogram, histograms);
addSamplesToHistogram(
samplesSet.firstContentfulPaintSamples,
firstContentfulPaintHistogram,
histograms);
addSamplesToHistogram(
samplesSet.firstContentfulPaintCpuTimeSamples,
firstContentfulPaintCpuTimeHistogram,
histograms);
addSamplesToHistogram(
samplesSet.onLoadSamples, onLoadHistogram, histograms);
addSamplesToHistogram(
samplesSet.aboveTheFoldLoadedToVisibleSamples,
aboveTheFoldLoadedToVisibleHistogram,
histograms);
addSamplesToHistogram(
samplesSet.firstViewportReadySamples,
firstViewportReadyHistogram,
histograms);
addSamplesToHistogram(
samplesSet.largestImagePaintSamples,
largestImagePaintHistogram,
histograms);
addSamplesToHistogram(
samplesSet.largestTextPaintSamples,
largestTextPaintHistogram,
histograms);
addSamplesToHistogram(
samplesSet.mainFrameLayoutShiftSamples,
mainFrameLayoutShiftHistogram,
histograms);
addSamplesToHistogram(
samplesSet.navigationStartSamples,
navigationStartHistogram,
histograms);
}
const samplesSet = collectMetricsFromLoadExpectations(model, chromeHelper);
addSamplesToHistogram(
samplesSet.firstMeaningfulPaintSamples,
firstMeaningfulPaintHistogram,
histograms);
addSamplesToHistogram(
samplesSet.firstMeaningfulPaintCpuTimeSamples,
firstMeaningfulPaintCpuTimeHistogram,
histograms);
addSamplesToHistogram(
samplesSet.interactiveSamples,
timeToInteractiveHistogram,
histograms);
addSamplesToHistogram(
samplesSet.firstCpuIdleSamples,
timeToFirstCpuIdleHistogram,
histograms);
addSamplesToHistogram(
samplesSet.totalBlockingTimeSamples,
totalBlockingTimeHistogram,
histograms);
}
tr.metrics.MetricRegistry.register(loadingMetric);
return {
loadingMetric,
createBreakdownDiagnostic
};
});
</script>