blob: 3973a4e9b5c4a0b936b94267dd019840d8f618d0 [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/math/range.html">
<link rel="import" href="/tracing/base/unit.html">
<link rel="import" href="/tracing/extras/v8/runtime_stats_entry.html">
<link rel="import" href="/tracing/metrics/metric_registry.html">
<link rel="import" href="/tracing/metrics/system_health/loading_metric.html">
<link rel="import" href="/tracing/value/histogram.html">
<script>
'use strict';
tr.exportTo('tr.metrics.v8', function() {
var COUNT_CUSTOM_BOUNDARIES = tr.v.HistogramBinBoundaries
.createExponential(1, 1000000, 50);
var DURATION_CUSTOM_BOUNDARIES = tr.v.HistogramBinBoundaries
.createExponential(0.1, 10000, 50);
function computeDomContentLoadedTime_(model) {
var chromeHelper = model.getOrCreateHelper(
tr.model.helpers.ChromeModelHelper);
var domContentLoadedTime = 0;
for (var rendererHelper of Object.values(chromeHelper.rendererHelpers)) {
for (var ev of rendererHelper.mainThread.sliceGroup.childEvents()) {
if (ev.title === 'domContentLoadedEventEnd' &&
ev.start > domContentLoadedTime) {
domContentLoadedTime = ev.start;
}
}
}
return domContentLoadedTime;
}
function computeInteractiveTime_(model) {
var chromeHelper = model.getOrCreateHelper(
tr.model.helpers.ChromeModelHelper);
var interactiveTime = 0;
for (var rendererHelper of Object.values(chromeHelper.rendererHelpers)) {
var samples = tr.metrics.sh.collectLoadingMetricsForRenderer(
rendererHelper).firstInteractiveSamples;
// TODO(fmeawad): Support multiple navigations.
if (samples.length === 0) continue;
if (interactiveTime !== 0) throw new Error('Too many navigations');
interactiveTime = tr.b.getOnlyElement(samples).diagnostics[
'Navigation infos'].value.interactive;
}
return interactiveTime;
}
function createDurationHistogram_(name) {
var histogram = new tr.v.Histogram(name + ':duration',
tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
DURATION_CUSTOM_BOUNDARIES);
histogram.customizeSummaryOptions({
std: false, count: false, sum: false, min: false, max: false
});
return histogram;
}
function createCountHistogram_(name) {
var histogram = new tr.v.Histogram(name + ':count',
tr.b.Unit.byName.count_smallerIsBetter,
COUNT_CUSTOM_BOUNDARIES);
histogram.customizeSummaryOptions({
std: false, count: false, sum: false, min: false, max: false
});
return histogram;
}
function convertMicroToMilli_(time) {
return tr.b.convertUnit(time,
tr.b.UnitPrefixScale.METRIC.MICRO, tr.b.UnitPrefixScale.METRIC.MILLI);
}
// TODO(crbug.com/688342): Remove this function when runtimeStatsMetric is
// removed.
function computeRuntimeStats(histograms, slices) {
var runtimeGroupCollection = new tr.e.v8.RuntimeStatsGroupCollection();
runtimeGroupCollection.addSlices(slices);
for (var runtimeGroup of runtimeGroupCollection.runtimeGroups) {
var durationSamples = new tr.v.d.RelatedHistogramBreakdown();
var countSamples = new tr.v.d.RelatedHistogramBreakdown();
for (var entry of runtimeGroup.values) {
var durationSampleHistogram = createDurationHistogram_(entry.name);
durationSampleHistogram.addSample(convertMicroToMilli_(entry.time));
durationSamples.set(entry.name + ':duration', durationSampleHistogram);
histograms.addHistogram(durationSampleHistogram);
var countSampleHistogram = createCountHistogram_(entry.name);
countSampleHistogram.addSample(entry.count);
countSamples.set(entry.name + ':count', countSampleHistogram);
histograms.addHistogram(countSampleHistogram);
}
var durationHistogram = createDurationHistogram_(runtimeGroup.name);
durationHistogram.addSample(convertMicroToMilli_(runtimeGroup.time), {
samples: durationSamples
});
var countHistogram = createCountHistogram_(runtimeGroup.name);
countHistogram.addSample(runtimeGroup.count, {
samples: countSamples
});
histograms.addHistogram(durationHistogram);
histograms.addHistogram(countHistogram);
}
}
// TODO(crbug.com/688342): Remove this metric and use runtimeStatsTotalMetric
// instead when the runtimeStatsTotalMetric is stable.
function runtimeStatsMetric(histograms, model) {
var interactiveTime = computeInteractiveTime_(model);
var domContentLoadedTime = computeDomContentLoadedTime_(model);
var endTime = Math.max(interactiveTime, domContentLoadedTime);
var slices = [...model.getDescendantEvents()].filter(event =>
event instanceof tr.e.v8.V8ThreadSlice && event.start <= endTime);
computeRuntimeStats(histograms, slices);
}
function computeRuntimeStatsBucketOnUE(histograms, slices,
v8SlicesBucketOnUEMap) {
let durationRelatedHistsByGroupName = new Map();
let countRelatedHistsByGroupName = new Map();
// Compute runtimeStats in each of the UE buckets. Also record the
// histograms in RelatedHistogramMap. These histograms are added to the
// corresponding histograms in the total bucket as a diagnostic. This keeps
// the data grouped.
for (var [name, slicesUE] of v8SlicesBucketOnUEMap) {
var runtimeGroupCollection = new tr.e.v8.RuntimeStatsGroupCollection();
runtimeGroupCollection.addSlices(slicesUE);
for (var runtimeGroup of runtimeGroupCollection.runtimeGroups) {
var histogramName = name + '_' + runtimeGroup.name;
var durationHistogram = createDurationHistogram_(histogramName);
durationHistogram.addSample(convertMicroToMilli_(runtimeGroup.time));
histograms.addHistogram(durationHistogram);
// Record this histogram in RelatedHistogramMap.
if (durationRelatedHistsByGroupName.get(runtimeGroup.name) ===
undefined) {
var durationHistogramMap = new tr.v.d.RelatedHistogramMap();
durationHistogramMap.set(name, durationHistogram);
durationRelatedHistsByGroupName.set(runtimeGroup.name,
durationHistogramMap);
} else {
durationRelatedHistsByGroupName.get(runtimeGroup.name).set(name,
durationHistogram);
}
var countHistogram = createCountHistogram_(histogramName);
countHistogram.addSample(runtimeGroup.count);
histograms.addHistogram(countHistogram);
// Record this histogram in RelatedHistogramMap.
if (countRelatedHistsByGroupName.get(runtimeGroup.name) === undefined) {
var countHistogramMap = new tr.v.d.RelatedHistogramMap();
countHistogramMap.set(name, countHistogram);
countRelatedHistsByGroupName.set(runtimeGroup.name,
countHistogramMap);
} else {
countRelatedHistsByGroupName.get(runtimeGroup.name).set(name,
countHistogram);
}
}
}
// Add the runtimeStats for all the samples. Please note, the values in
// the UE buckets may not add upto the values computed here. Since UEs
// can overlap, we count some of the samples in multiple UE buckets.
var runtimeGroupCollection = new tr.e.v8.RuntimeStatsGroupCollection();
runtimeGroupCollection.addSlices(slices);
for (var runtimeGroup of runtimeGroupCollection.runtimeGroups) {
var histogramName = runtimeGroup.name;
var durationHistogram = createDurationHistogram_(histogramName);
durationHistogram.addSample(convertMicroToMilli_(runtimeGroup.time));
histograms.addHistogram(durationHistogram);
// Add UE histograms as a diagnostic, so they can be hidden in the main
// view, and the data across UE buckets can be grouped together.
var durationRelatedHistogram = durationRelatedHistsByGroupName.get(
runtimeGroup.name);
if (durationRelatedHistogram !== undefined) {
durationHistogram.diagnostics.set('RAIL stages',
durationRelatedHistogram);
}
var countHistogram = createCountHistogram_(histogramName);
countHistogram.addSample(runtimeGroup.count);
// Add UE histograms as a diagnostic, so they can be hidden in the main
// view, and the data across UE buckets can be grouped together.
var countRelatedHistogram = countRelatedHistsByGroupName.get(
runtimeGroup.name);
if (countRelatedHistogram !== undefined) {
countHistogram.diagnostics.set('RAIL stages', countRelatedHistogram);
}
histograms.addHistogram(countHistogram);
}
}
function runtimeStatsTotalMetric(histograms, model) {
var v8ThreadSlices = [...model.getDescendantEvents()].filter(event =>
event instanceof tr.e.v8.V8ThreadSlice).sort((e1, e2) =>
e1.start - e2.start);
var v8SlicesBucketOnUEMap = new Map();
// User expectations can sometime overlap. So, certain v8 slices can be
// included in more than one expectation. We count such slices in each
// of the expectations. This is done so as to minimize the noise due to
// the differences in the extent of overlap between the runs.
for (var expectation of model.userModel.expectations) {
var slices = expectation.range.filterArray(v8ThreadSlices,
event => event.start);
if (slices.length === 0) continue;
// filterArray filters the array that intersects the range inclusively.
// Expectations are not inclusive i.e. expectations are like [0, 1),
// [1, 2). v8ThreadSlices that start at 1 should be counted only in [1,2)
// bucket. Filter out sample at the boundary so that they are not counted
// twice.
var lastSlice = slices[slices.length - 1];
if (!expectation.range.intersectsRangeExclusive(lastSlice.range)) {
slices.pop();
}
if (v8SlicesBucketOnUEMap.get(expectation.stageTitle) === undefined) {
v8SlicesBucketOnUEMap.set(expectation.stageTitle, slices);
} else {
var totalSlices = v8SlicesBucketOnUEMap.get(expectation.stageTitle)
.concat(slices);
v8SlicesBucketOnUEMap.set(expectation.stageTitle, totalSlices);
}
}
// Compute runtimeStats in each of the UE buckets and also compute
// runtimeStats on all of the samples. The values in UE buckets do not add
// up to the total of all samples, since we duplicate some of the samples in
// multiple buckets when the UEs overlap.
computeRuntimeStatsBucketOnUE(histograms, v8ThreadSlices,
v8SlicesBucketOnUEMap);
}
tr.metrics.MetricRegistry.register(runtimeStatsTotalMetric);
tr.metrics.MetricRegistry.register(runtimeStatsMetric);
return {
runtimeStatsMetric,
runtimeStatsTotalMetric,
};
});
</script>