blob: bed920344b0d69d1c5505377042d2c4fe4305efa [file] [edit]
# Copyright 2017 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.
import collections
from tracing.proto import histogram_proto
from tracing.value import histogram
from tracing.value import histogram_deserializer
from tracing.value.diagnostics import all_diagnostics
from tracing.value.diagnostics import diagnostic
from tracing.value.diagnostics import diagnostic_ref
from tracing.value.diagnostics import generic_set
class HistogramSet(object):
def __init__(self, histograms=()):
self._histograms = set()
self._shared_diagnostics_by_guid = {}
for hist in histograms:
self.AddHistogram(hist)
def CreateHistogram(self, name, unit, samples, **options):
hist = histogram.Histogram.Create(name, unit, samples, **options)
self.AddHistogram(hist)
return hist
@property
def shared_diagnostics(self):
return list(self._shared_diagnostics_by_guid.values())
def RemoveOrphanedDiagnostics(self):
orphans = set(self._shared_diagnostics_by_guid.keys())
for h in self._histograms:
for d in h.diagnostics.values():
if d.guid in orphans:
orphans.remove(d.guid)
for guid in orphans:
del self._shared_diagnostics_by_guid[guid]
def FilterHistograms(self, discard):
self._histograms = set(
hist
for hist in self._histograms
if not discard(hist))
def AddHistogram(self, hist, diagnostics=None):
if diagnostics:
for name, diag in diagnostics.items():
hist.diagnostics[name] = diag
self._histograms.add(hist)
def AddSharedDiagnostic(self, diag):
self._shared_diagnostics_by_guid[diag.guid] = diag
def AddSharedDiagnosticToAllHistograms(self, name, diag):
self._shared_diagnostics_by_guid[diag.guid] = diag
for hist in self:
hist.diagnostics[name] = diag
def Merge(self, other):
"""Merge another HistogramSet's contents."""
self._shared_diagnostics_by_guid.update(other._shared_diagnostics_by_guid)
self._histograms.update(other._histograms)
def GetFirstHistogram(self):
for hist in self._histograms:
return hist
def GetHistogramsNamed(self, name):
return [h for h in self if h.name == name]
def GetHistogramNamed(self, name):
hs = self.GetHistogramsNamed(name)
assert len(hs) == 1, 'Found %d Histograms names "%s"' % (len(hs), name)
return hs[0]
def GetSharedDiagnosticsOfType(self, typ):
return [d for d in self.shared_diagnostics if isinstance(d, typ)]
def LookupDiagnostic(self, guid):
return self._shared_diagnostics_by_guid.get(guid)
def __len__(self):
return len(self._histograms)
def __iter__(self):
for hist in self._histograms:
yield hist
def Deserialize(self, data):
for hist in histogram_deserializer.Deserialize(data):
self.AddHistogram(hist)
def ImportProto(self, serialized_proto):
hist_set = histogram_proto.Pb2().HistogramSet()
hist_set.ParseFromString(serialized_proto)
for guid, d in hist_set.shared_diagnostics.items():
diag = diagnostic.Diagnostic.FromProto(d)
diag.guid = guid
self._shared_diagnostics_by_guid[guid] = diag
for h in hist_set.histograms:
hist = histogram.Histogram.FromProto(h)
hist.diagnostics.ResolveSharedDiagnostics(self)
self.AddHistogram(hist)
def ImportDicts(self, dicts):
# The new HistogramSet JSON format is an array of at least 3 arrays.
# This format isn't finished yet and is currently unused.
if isinstance(dicts, list) and dicts and isinstance(dicts[0], list):
self.Deserialize(dicts)
return
# The original HistogramSet JSON format was a flat array of objects.
for d in dicts:
self.ImportLegacyDict(d)
def ImportLegacyDict(self, d):
if 'type' in d:
# TODO(benjhayden): Forget about TagMaps in 2019Q2.
if d['type'] == 'TagMap':
return
assert d['type'] in all_diagnostics.GetDiagnosticTypenames(), (
'Unrecognized shared diagnostic type ' + d['type'])
diag = diagnostic.Diagnostic.FromDict(d)
self._shared_diagnostics_by_guid[d['guid']] = diag
else:
hist = histogram.Histogram.FromDict(d)
hist.diagnostics.ResolveSharedDiagnostics(self)
self.AddHistogram(hist)
def AsDicts(self):
dcts = []
for d in self._shared_diagnostics_by_guid.values():
dcts.append(d.AsDict())
for h in self:
dcts.append(h.AsDict())
return dcts
def AsProto(self):
proto = histogram_proto.Pb2().HistogramSet()
for guid, d in self._shared_diagnostics_by_guid.items():
proto.shared_diagnostics[guid].CopyFrom(d.AsProto())
for h in self:
proto.histograms.extend([h.AsProto()])
return proto
def ReplaceSharedDiagnostic(self, old_guid, new_diagnostic):
if not isinstance(new_diagnostic, diagnostic_ref.DiagnosticRef):
self._shared_diagnostics_by_guid[new_diagnostic.guid] = new_diagnostic
old_diagnostic = self._shared_diagnostics_by_guid.get(old_guid)
# Fast path, if they're both generic_sets, we overwrite the contents of the
# old diagnostic.
if isinstance(new_diagnostic, generic_set.GenericSet) and (
isinstance(old_diagnostic, generic_set.GenericSet)):
old_diagnostic.SetValues(list(new_diagnostic))
old_diagnostic.ResetGuid(new_diagnostic.guid)
self._shared_diagnostics_by_guid[new_diagnostic.guid] = old_diagnostic
del self._shared_diagnostics_by_guid[old_guid]
return
for hist in self:
for name, diag in hist.diagnostics.items():
if diag.has_guid and diag.guid == old_guid:
hist.diagnostics[name] = new_diagnostic
def DeduplicateDiagnostics(self):
names_to_candidates = {}
diagnostics_to_histograms = collections.defaultdict(list)
for hist in self:
for name, candidate in hist.diagnostics.items():
diagnostics_to_histograms[candidate].append(hist)
if name not in names_to_candidates:
names_to_candidates[name] = set()
names_to_candidates[name].add(candidate)
for name, candidates in names_to_candidates.items():
deduplicated_diagnostics = set()
for candidate in candidates:
found = False
for test in deduplicated_diagnostics:
if candidate == test:
hists = diagnostics_to_histograms.get(candidate)
for h in hists:
h.diagnostics[name] = test
found = True
break
if not found:
deduplicated_diagnostics.add(candidate)
for diag in deduplicated_diagnostics:
self._shared_diagnostics_by_guid[diag.guid] = diag