blob: 56703da5897f979140c06e211f96afbc86175f39 [file] [edit]
<!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.
-->
<!--
Include all Diagnostic subclasses here so that DiagnosticMap.addDicts() and
DiagnosticMap.fromDict() always have access to all subclasses in the
Diagnostic registry.
-->
<link rel="import" href="/tracing/value/diagnostics/all_diagnostics.html">
<link rel="import" href="/tracing/value/diagnostics/reserved_names.html">
<script>
'use strict';
tr.exportTo('tr.v.d', function() {
class DiagnosticMap extends Map {
/**
* @param {boolean=} opt_allowReservedNames defaults to true
*/
constructor(opt_allowReservedNames) {
super();
if (opt_allowReservedNames === undefined) {
opt_allowReservedNames = true;
}
this.allowReservedNames_ = opt_allowReservedNames;
}
/**
* Add a new Diagnostic to this map.
*
* @param {string} name
* @param {!tr.v.d.Diagnostic} diagnostic
*/
set(name, diagnostic) {
if (typeof(name) !== 'string') {
throw new Error(`name must be string, not ${name}`);
}
if (!(diagnostic instanceof tr.v.d.Diagnostic) &&
!(diagnostic instanceof tr.v.d.DiagnosticRef)) {
throw new Error(`Must be instanceof Diagnostic: ${diagnostic}`);
}
// TODO(#3507): Reserved names should never be UnmergeableDiagnosticSet.
if (!this.allowReservedNames_ &&
tr.v.d.RESERVED_NAMES_SET.has(name) &&
!(diagnostic instanceof tr.v.d.UnmergeableDiagnosticSet) &&
!(diagnostic instanceof tr.v.d.DiagnosticRef)) {
const type = tr.v.d.RESERVED_NAMES_TO_TYPES.get(name);
if (type && !(diagnostic instanceof type)) {
throw new Error(
`Diagnostics named "${name}" must be ${type.name}, ` +
`not ${diagnostic.constructor.name}`);
}
}
Map.prototype.set.call(this, name, diagnostic);
}
delete(name) {
if (name === undefined) throw new Error('missing name');
Map.prototype.delete.call(this, name);
}
deserializeAdd(data, deserializer) {
for (const id of data) {
const {name, diagnostic} = deserializer.getDiagnostic(id);
this.set(name, diagnostic);
}
}
/**
* Add Diagnostics from a dictionary of dictionaries.
*
* @param {Object} dict
*/
addDicts(dict) {
for (const [name, diagnosticDict] of Object.entries(dict)) {
if (name === 'tagmap') continue;
if (typeof diagnosticDict === 'string') {
this.set(name, new tr.v.d.DiagnosticRef(diagnosticDict));
} else if (diagnosticDict.type !== 'RelatedHistogramMap' &&
diagnosticDict.type !== 'RelatedHistogramBreakdown' &&
diagnosticDict.type !== 'TagMap') {
// Ignore RelatedHistograms and TagMaps.
// TODO(benjhayden): Forget about them in 2019 Q2.
this.set(name, tr.v.d.Diagnostic.fromDict(diagnosticDict));
}
}
}
resolveSharedDiagnostics(histograms, opt_required) {
for (const [name, value] of this) {
if (!(value instanceof tr.v.d.DiagnosticRef)) {
continue;
}
const guid = value.guid;
const diagnostic = histograms.lookupDiagnostic(guid);
if (diagnostic instanceof tr.v.d.Diagnostic) {
this.set(name, diagnostic);
} else if (opt_required) {
throw new Error('Unable to find shared Diagnostic ' + guid);
}
}
}
serialize(serializer) {
const data = [];
for (const [name, diagnostic] of this) {
data.push(serializer.getOrAllocateDiagnosticId(name, diagnostic));
}
return data;
}
asDict() {
const dict = {};
for (const [name, diagnostic] of this) {
dict[name] = diagnostic.asDictOrReference();
}
return dict;
}
static deserialize(data, deserializer) {
const diagnostics = new DiagnosticMap();
diagnostics.deserializeAdd(data, deserializer);
return diagnostics;
}
static fromDict(d) {
const diagnostics = new DiagnosticMap();
diagnostics.addDicts(d);
return diagnostics;
}
/**
* Convert dictionary or ES6 Map to DiagnosticMap.
* @param {!Object|!Map.<string, !tr.v.d.Diagnostic>} obj
* @return {tr.v.d.DiagnosticMap}
*/
static fromObject(obj) {
const diagnostics = new DiagnosticMap();
if (!(obj instanceof Map)) obj = Object.entries(obj);
for (const [name, diagnostic] of obj) {
if (!diagnostic) continue;
diagnostics.set(name, diagnostic);
}
return diagnostics;
}
addDiagnostics(other) {
for (const [name, otherDiagnostic] of other) {
const myDiagnostic = this.get(name);
if (myDiagnostic !== undefined &&
myDiagnostic.canAddDiagnostic(otherDiagnostic)) {
myDiagnostic.addDiagnostic(otherDiagnostic);
continue;
}
// We need to avoid storing references to |otherDiagnostic| in both
// |this| and |other| because future merge()s may add yet other
// Diagnostics to |this|, and they shouldn't accidentally modify
// anything in |other|.
// Now, either |this| doesn't already have a Diagnostic named |name|
// (myDiagnostic is undefined), or
// |this| already has a Diagnostic named |name| that can't be merged
// with |otherDiagnostic|.
// Either way, we need to clone |otherDiagnostic|.
// However, clones produced via fromDict/toDict cannot necessarily be
// merged with yet other Diagnostics, either because of semantics (as in
// the case of TelemtryInfo and the like) or because guids must not be
// shared by distinct Diagnostics. Therefore, Diagnostics support
// another way of cloning that is specifically targeted at supporting
// merging: clone().
const clone = otherDiagnostic.clone();
if (myDiagnostic === undefined) {
this.set(name, clone);
continue;
}
// Now, |myDiagnostic| exists and it is unmergeable with |clone|, which
// is safe to store in |this|.
this.set(name, new tr.v.d.UnmergeableDiagnosticSet(
[myDiagnostic, clone]));
}
}
}
return {DiagnosticMap};
});
</script>