blob: 0cb63b11c8d68a88c89d91a9be418e2fb6819df8 [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/metrics/all_metrics.html">
<link rel="import" href="/tracing/metrics/metric_registry.html">
<link rel="import" href="/tracing/mre/failure.html">
<link rel="import" href="/tracing/mre/function_handle.html">
<link rel="import" href="/tracing/value/diagnostics/reserved_names.html">
<link rel="import" href="/tracing/value/histogram_set.html">
<script>
'use strict';
tr.exportTo('tr.metrics', function() {
/**
* @param {!tr.model.Model} model
* @param {!Object} options
* @param {!Array.<string>} options.metrics
* @param {!Function} addFailureCb
* @return {!tr.v.HistogramSet}
*/
function runMetrics(model, options, addFailureCb) {
if (options === undefined) {
throw new Error('Options are required.');
}
const metricNames = options.metrics;
if (!metricNames) {
throw new Error('Metric names should be specified.');
}
const allMetricsStart = new Date();
const durationBreakdown = new tr.v.d.Breakdown();
const categories = getTraceCategories(model);
const histograms = new tr.v.HistogramSet();
histograms.createHistogram('trace_import_duration',
tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
model.stats.traceImportDurationMs, {
binBoundaries: tr.v.HistogramBinBoundaries.createExponential(
1e-3, 1e5, 30),
description:
'Duration that trace viewer required to import the trace',
summaryOptions: tr.v.Histogram.AVERAGE_ONLY_SUMMARY_OPTIONS,
});
for (const metricName of metricNames) {
const metricStart = new Date();
const metric = tr.metrics.MetricRegistry.findTypeInfoWithName(metricName);
if (metric === undefined) {
throw new Error(`"${metricName}" is not a registered metric.`);
}
validateTraceCategories(metric.metadata.requiredCategories, categories);
try {
metric.constructor(histograms, model, options);
} catch (e) {
const err = tr.b.normalizeException(e);
addFailureCb(new tr.mre.Failure(
undefined, 'metricMapFunction', model.canonicalUrl, err.typeName,
err.message, err.stack));
}
const metricMs = new Date() - metricStart;
histograms.createHistogram(
metricName + '_duration',
tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
[metricMs]);
durationBreakdown.set(metricName, metricMs);
}
validateDiagnosticNames(histograms);
const allMetricsMs = new Date() - allMetricsStart +
model.stats.traceImportDurationMs;
durationBreakdown.set('traceImport', model.stats.traceImportDurationMs);
durationBreakdown.set('other', allMetricsMs - tr.b.math.Statistics.sum(
durationBreakdown, ([metricName, metricMs]) => metricMs));
const breakdownNames = tr.v.d.RelatedNameMap.fromEntries(new Map(
metricNames.map(metricName => [metricName, metricName + '_duration'])));
breakdownNames.set('traceImport', 'trace_import_duration');
histograms.createHistogram(
'metrics_duration',
tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
[
{
value: allMetricsMs,
diagnostics: {breakdown: durationBreakdown},
},
], {
diagnostics: {breakdown: breakdownNames},
});
return histograms;
}
function getTraceCategories(model) {
for (const metadata of model.metadata) {
let config;
if (metadata.name === 'TraceConfig' && metadata.value) {
config = metadata.value;
}
if (metadata.name === 'metadata' && metadata.value &&
metadata.value['trace-config'] &&
metadata.value['trace-config'] !== '__stripped__') {
config = JSON.parse(metadata.value['trace-config']);
}
if (config) {
return {
excluded: config.excluded_categories || [],
included: config.included_categories || [],
};
}
}
}
function validateTraceCategories(requiredCategories, categories) {
if (!requiredCategories) return;
if (!categories) throw new Error('Missing trace config metadata');
for (const cat of requiredCategories) {
const isDisabledByDefault = (cat.indexOf('disabled-by-default') === 0);
let missing = false;
if (isDisabledByDefault) {
if (!categories.included.includes(cat)) {
missing = true;
}
} else if (categories.excluded.includes(cat)) {
missing = true;
}
if (missing) {
throw new Error(`Trace is missing required category "${cat}"`);
}
}
}
/**
* Ensure that metrics don't use reserved diagnostic names.
*
* @param {!tr.v.HistogramSet} histograms
*/
function validateDiagnosticNames(histograms) {
for (const hist of histograms) {
for (const name of hist.diagnostics.keys()) {
if (name === tr.v.d.RESERVED_NAMES.ALERT_GROUPING) {
// Metrics can set alert grouping when they create histogram. It's
// still a reserved diagnostic because that helps us enforce the right
// diagnostic shape.
continue;
}
if (tr.v.d.RESERVED_NAMES_SET.has(name)) {
throw new Error(
`Illegal diagnostic name "${name}" on Histogram "${hist.name}"`);
}
}
}
}
/**
* @param {!tr.v.HistogramSet} histograms
* @param {!tr.model.Model} model
*/
function addTelemetryInfo(histograms, model) {
for (const metadata of model.metadata) {
if (!metadata.value || !metadata.value.telemetry) continue;
for (const [name, value] of Object.entries(metadata.value.telemetry)) {
const type = tr.v.d.RESERVED_NAMES_TO_TYPES.get(name);
if (type === undefined) {
throw new Error(`Unexpected telemetry.${name}`);
}
histograms.addSharedDiagnosticToAllHistograms(name, new type(value));
}
}
}
/**
* @param {!tr.mre.MreResult} result
* @param {!tr.model.Model} model
* @param {!Object} options
* @param {!Array.<string>} options.metrics
*/
function metricMapFunction(result, model, options) {
const histograms = runMetrics(
model, options, result.addFailure.bind(result));
addTelemetryInfo(histograms, model);
if (model.canonicalUrl !== undefined) {
const info = tr.v.d.RESERVED_INFOS.TRACE_URLS;
histograms.addSharedDiagnosticToAllHistograms(
info.name, new info.type([model.canonicalUrl]));
}
result.addPair('histograms', histograms.asDicts());
const scalarDicts = [];
for (const value of histograms) {
for (const [statName, scalar] of value.statisticsScalars) {
scalarDicts.push({
name: value.name + '_' + statName,
numeric: scalar.asDict(),
description: value.description,
});
}
}
result.addPair('scalars', scalarDicts);
}
tr.mre.FunctionRegistry.register(metricMapFunction);
return {
metricMapFunction,
runMetrics,
};
});
</script>