blob: 959006e9749c12550f98f3c990022945b4fe6404 [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/base64.html">
<link rel="import" href="/tracing/base/unit.html">
<link rel="import" href="/tracing/base/utils.html">
<link rel="import" href="/tracing/extras/chrome/chrome_processes.html">
<link rel="import" href="/tracing/metrics/metric_registry.html">
<link rel="import" href="/tracing/value/histogram.html">
<script>
'use strict';
/**
* @fileoverfiew This file contains implementation of extracting UMA histograms.
*
* UMA histograms are logged in trace events titled 'UMAHistogramSamples'. The
* event arguments contain the histogram name and the base-64 coded of an
* snapshot of histogram samples serialized in a pickle. These are emitted at
* the end of tracing, and represent the difference in the UMA histograms from
* when the tracing began.
*
* If there are several processes that have snapshots of the same histogram,
* the snapshots will be merged.
*/
tr.exportTo('tr.metrics', function() {
function parseBuckets_(event, processName) {
const len = tr.b.Base64.getDecodedBufferLength(event.args.buckets);
const buffer = new ArrayBuffer(len);
const dataView = new DataView(buffer);
tr.b.Base64.DecodeToTypedArray(event.args.buckets, dataView);
// Negative numbers are not supported, for now.
const decoded = new Uint32Array(buffer);
const sum = decoded[1] + decoded[2] * 0x100000000;
const bins = [];
let position = 4;
while (position <= decoded.length - 4) {
const min = decoded[position++];
const max = decoded[position++] + decoded[position++] * 0x100000000;
const count = decoded[position++];
const processes = new tr.v.d.Breakdown();
processes.set(processName, count);
const events = new tr.v.d.RelatedEventSet([event]);
bins.push({min, max, count, processes, events});
}
return {sum, bins};
}
function mergeBins_(x, y) {
x.sum += y.sum;
const allBins = [...x.bins, ...y.bins];
allBins.sort((a, b) => a.min - b.min);
x.bins = [];
let last = undefined;
for (const bin of allBins) {
if (last !== undefined && bin.min === last.min) {
if (last.max !== bin.max) throw new Error('Incompatible bins');
if (bin.count === 0) continue;
last.count += bin.count;
for (const event of bin.events) {
last.events.add(event);
}
last.processes.addDiagnostic(bin.processes);
} else {
if (last !== undefined && bin.min < last.max) {
throw new Error('Incompatible bins');
}
x.bins.push(bin);
last = bin;
}
}
}
function getHistogramUnit_(name) {
// Customize histogram units here.
return tr.b.Unit.byName.unitlessNumber_smallerIsBetter;
}
function getIsHistogramBinsLinear_(histogramName) {
return histogramName.startsWith('Graphics.Smoothness.Throughput') ||
histogramName.startsWith('Memory.Memory.GPU.PeakMemoryUsage');
}
function getHistogramBoundaries_(name) {
// Customize histogram boundaries here. Ideally, this would not be
// necessary.
// crbug.com/987273
if (name.startsWith('Event.Latency.Scroll')) {
return tr.v.HistogramBinBoundaries.createExponential(1e3, 1e5, 50);
}
if (name.startsWith('Graphics.Smoothness.Throughput')) {
return tr.v.HistogramBinBoundaries.createLinear(0, 100, 101);
}
if (name.startsWith('Memory.Memory.GPU.PeakMemoryUsage')) {
return tr.v.HistogramBinBoundaries.createLinear(0, 1e6, 100);
}
return tr.v.HistogramBinBoundaries.createExponential(1e-3, 1e3, 50);
}
function umaMetric(histograms, model) {
const histogramValues = new Map();
const nameCounts = new Map();
for (const process of model.getAllProcesses()) {
const histogramEvents = new Map();
for (const event of process.instantEvents) {
if (event.title !== 'UMAHistogramSamples') continue;
const name = event.args.name;
const events = histogramEvents.get(name) || [];
if (!histogramEvents.has(name)) histogramEvents.set(name, events);
events.push(event);
}
let processName =
tr.e.chrome.chrome_processes.canonicalizeProcessName(process.name);
// Increase the counter even if histogramEvents is empty so that process
// names of all UMA metrics match.
nameCounts.set(processName, (nameCounts.get(processName) || 0) + 1);
processName = `${processName}_${nameCounts.get(processName)}`;
for (const [name, events] of histogramEvents) {
const values = histogramValues.get(name) || {sum: 0, bins: []};
if (!histogramValues.has(name)) histogramValues.set(name, values);
const endValues = parseBuckets_(events[events.length - 1], processName);
if (events.length === 1) {
mergeBins_(values, endValues, name);
} else {
throw new Error('There should be at most one snapshot of UMA ' +
`histogram for ${name} in each process.`);
}
}
}
for (const [name, values] of histogramValues) {
const histogram = new tr.v.Histogram(
name, getHistogramUnit_(name), getHistogramBoundaries_(name));
const isLinear = getIsHistogramBinsLinear_(name);
// If we just put samples at the middle of the bins, their sum may not
// match the sum we read from traces. Compute how much samples should be
// shifted so that their sum matches what we expect.
let sumOfMiddles = 0;
let sumOfBinLengths = 0;
for (const bin of values.bins) {
sumOfMiddles += bin.count * (bin.min + bin.max) / 2;
sumOfBinLengths += bin.count * (bin.max - bin.min);
}
if (name.startsWith('CompositorLatency.Type')) {
let histogramBoundaries = tr.v.HistogramBinBoundaries.createLinear(0, 100, 101);
let histogramUnit = getHistogramUnit_(name);
let presentedCount = values.bins[0] ? values.bins[0].count : 0;
let delayedCount = values.bins[1] ? values.bins[1].count : 0;
let droppedCount = values.bins[2] ? values.bins[2].count : 0;
let inTimeCount = presentedCount - delayedCount;
let totalCount = presentedCount + droppedCount;
const inTimeHistogram = new tr.v.Histogram(
name+'.Percentage_of_in_time_frames', histogramUnit, histogramBoundaries);
inTimeHistogram.addSample(100.0 * inTimeCount / totalCount);
histograms.addHistogram(inTimeHistogram);
const delayedHistogram = new tr.v.Histogram(
name+'.Percentage_of_delayed_frames', histogramUnit, histogramBoundaries);
delayedHistogram.addSample(100.0 * delayedCount / totalCount);
histograms.addHistogram(delayedHistogram);
const droppedHistogram = new tr.v.Histogram(
name+'.Percentage_of_dropped_frames', histogramUnit, histogramBoundaries);
droppedHistogram.addSample(100.0 * droppedCount / totalCount);
histograms.addHistogram(droppedHistogram);
}
const shift = (values.sum - sumOfMiddles) / sumOfBinLengths;
// Note: for linear bins, if shift is less than -0.5, it means that even
// if we put all samples at the lowest value of their bins their sum will
// be less than the sum we read from traces. So, there is an
// inconsistency: either the bins are reported incorrectly, or the sum is
// reported incorrectly.
//
// Similarly, if shift is greater than 0.5, the sum of samples cannot add
// up to the sum we read from traces, even if we put all samples at the
// highest value of their bins.
if (isLinear && Math.abs(shift) > 0.5) {
throw new Error(`Samples sum is wrong for ${name}.`);
}
for (const bin of values.bins) {
if (bin.count === 0) continue;
const shiftedValue =
(bin.min + bin.max) / 2 + shift * (bin.max - bin.min);
for (const [processName, count] of bin.processes) {
bin.processes.set(processName, shiftedValue * count / bin.count);
}
for (let i = 0; i < bin.count; i++) {
histogram.addSample(
shiftedValue, {processes: bin.processes, events: bin.events});
}
}
histograms.addHistogram(histogram);
}
}
tr.metrics.MetricRegistry.register(umaMetric, {
requiredCategories: ['benchmark'],
});
return {
umaMetric,
};
});
</script>