blob: 72e47deb240ae79a015a27f14665d55f084b092a [file] [log] [blame]
<!DOCTYPE html>
<!--
Copyright 2019 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/event_finder_utils.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/loading_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/diagnostic_map.html">
<link rel="import" href="/tracing/value/histogram.html">
<script>
'use strict';
/**
* Per-second metric is a set of page load metrics to show the different
* activities happening in each second since navigation start. It categorizes
* trace events into different categories (e.g., layout, style, gc) of
* activities. For each kind of activities, this metric outputs a
* single-value histogram to show how long each kind of activities run for
* during each second.
*
* It also has the following characteristics:
* - The unit is ms.
* - Produces wall-clock-time and CPU-time outcome.
* - The last incomplete second is curtailed
*/
tr.exportTo('tr.metrics.sh', function() {
const timeDurationInMs_smallerIsBetter =
tr.b.Unit.byName.timeDurationInMs_smallerIsBetter;
const EventFinderUtils = tr.e.chrome.EventFinderUtils;
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 SUMMARY_OPTIONS = {
avg: true,
count: false,
max: false,
min: false,
std: false,
sum: false,
};
function addSamplesToHistogram(pairInfo, breakdownTree, histogram, histograms,
diagnostics) {
histogram.addSample(pairInfo.end - pairInfo.start, diagnostics);
if (!breakdownTree) {
return;
}
for (const [category, breakdown] of Object.entries(breakdownTree)) {
const relatedName = `${histogram.name}:${category}`;
if (!histograms.getHistogramNamed(relatedName)) {
const relatedHist = histograms.createHistogram(
relatedName, histogram.unit, [], {
binBoundaries: LOADING_METRIC_BOUNDARIES,
summaryOptions: {
count: false,
max: false,
min: false,
sum: false,
},
});
}
const relatedHist = histograms.getHistogramNamed(relatedName);
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 splitOneRangeIntoPerSecondRanges(startTime, endTime) {
const results = [];
for (let i = 0; startTime + (i + 1) * 1000 <= endTime; i += 1) {
const start = i * 1000;
const end = (i + 1) * 1000;
results.push({
start,
end,
});
}
return results;
}
function getNavigationInfos(model) {
const navigationInfos = [];
const chromeHelper = model.getOrCreateHelper(
tr.model.helpers.ChromeModelHelper);
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];
// Main thread may be missing. See crbug.com/1059726 for an example.
if (rendererHelper.mainThread === undefined) continue;
navigationInfos.push({
navigationStart: expectation.navigationStart,
rendererHelper,
url: expectation.url
});
}
navigationInfos.forEach((navInfo, i) => {
if (i === navigationInfos.length - 1) {
navInfo.navigationEndTime = model.bounds.max; // end of trace
} else {
navInfo.navigationEndTime =
navigationInfos[i + 1].navigationStart.start;
}
});
return navigationInfos;
}
function getWallTimeBreakdownTree(rendererHelper, start, end) {
const startEndRange = tr.b.math.Range.fromExplicitRange(start, end);
const networkEvents = EventFinderUtils.getNetworkEventsInRange(
rendererHelper.process, startEndRange);
const breakdownTree = tr.metrics.sh.generateWallClockTimeBreakdownTree(
rendererHelper.mainThread, networkEvents, startEndRange);
return breakdownTree;
}
function getCpuTimeBreakdownTree(rendererHelper, start, end) {
const startEndRange = tr.b.math.Range.fromExplicitRange(start, end);
const breakdownTree = tr.metrics.sh.generateCpuTimeBreakdownTree(
rendererHelper.mainThread, startEndRange);
return breakdownTree;
}
function persecondMetric(histograms, model) {
const navigationInfos = getNavigationInfos(model);
if (navigationInfos.length === 0) {
return;
}
navigationInfos.forEach(navInfo => {
const navigationStart = navInfo.navigationStart.start;
const navigationEnd = navInfo.navigationEndTime;
// Produce a list of { start, end }, relative to navigation-start.
const startEndPairs =
splitOneRangeIntoPerSecondRanges(navigationStart, navigationEnd);
const breakdownList = startEndPairs.map(p => {
const wallHistogramName = `wall_${p.start}_to_${p.end}`;
const wallHistogramDescription =
`Wall-clock time ${p.start} to ${p.end} breakdown`;
const cpuHistogramName = `cpu_${p.start}_to_${p.end}`;
const cpuHistogramDescription =
`CPU time ${p.start} to ${p.end} breakdown`;
const pid = navInfo.rendererHelper.pid;
const breakdownTree = getWallTimeBreakdownTree(
navInfo.rendererHelper, navigationStart + p.start,
navigationStart + p.end);
const cpuBreakdownTree = getCpuTimeBreakdownTree(
navInfo.rendererHelper, navigationStart + p.start,
navigationStart + p.end);
const diagnostics = {
'Navigation infos': new tr.v.d.GenericSet([{
url: navInfo.url,
pid: navInfo.rendererHelper.pid,
navStart: navigationStart,
frameIdRef: navInfo.navigationStart.args.frame
}]),
'breakdown': tr.metrics.sh.createBreakdownDiagnostic(breakdownTree),
};
return Object.assign(p,
{
breakdownTree,
cpuBreakdownTree,
wallHistogramName,
wallHistogramDescription,
cpuHistogramName,
cpuHistogramDescription,
diagnostics,
});
});
breakdownList.forEach(p => {
// entry === {start:0, end:1, breakdownTree, cpuBreakdownTree}
if (!histograms.getHistogramNamed(p.wallHistogramName)) {
histograms.createHistogram(
p.wallHistogramName, timeDurationInMs_smallerIsBetter,
[], {
binBoundaries: LOADING_METRIC_BOUNDARIES,
description: p.wallHistogramDescription,
summaryOptions: SUMMARY_OPTIONS,
});
}
const wallHistogram = histograms.getHistogramNamed(p.wallHistogramName);
addSamplesToHistogram(p, p.breakdownTree, wallHistogram, histograms,
p.diagnostics);
if (!histograms.getHistogramNamed(p.cpuHistogramName)) {
histograms.createHistogram(
p.cpuHistogramName, timeDurationInMs_smallerIsBetter, [],
{ binBoundaries: LOADING_METRIC_BOUNDARIES,
description: p.cpuHistogramDescription,
summaryOptions: SUMMARY_OPTIONS,
});
}
const cpuHistogram = histograms.getHistogramNamed(p.cpuHistogramName);
addSamplesToHistogram(p, p.cpuBreakdownTree, cpuHistogram, histograms,
p.diagnostics);
});
});
}
tr.metrics.MetricRegistry.register(persecondMetric);
return {
persecondMetric,
splitOneRangeIntoPerSecondRanges
};
});
</script>