blob: f4eff7934848a45e508c2d64692cb6e5f5adbffc [file] [edit]
# Copyright 2018 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 contextlib
import json
import os
import tempfile
from tracing.value import histogram_set
from tracing.value import merge_histograms
from tracing.value.diagnostics import generic_set
from tracing.value.diagnostics import reserved_infos
ALL_NAMES = list(reserved_infos.AllNames())
def _LoadHistogramSet(dicts):
hs = histogram_set.HistogramSet()
hs.ImportDicts(dicts)
return hs
@contextlib.contextmanager
def TempFile():
try:
temp = tempfile.NamedTemporaryFile(delete=False)
yield temp
finally:
os.unlink(temp.name)
def GetGroupingLabelFromHistogram(hist):
tags = hist.diagnostics.get(reserved_infos.STORY_TAGS.name) or []
tags_to_use = [t.split(':') for t in tags if ':' in t]
return '_'.join(v for _, v in sorted(tags_to_use))
def ComputeTestPath(hist):
path = hist.name
# If a Histogram represents a summary across multiple stories, then its
# 'stories' diagnostic will contain the names of all of the stories.
# If a Histogram is not a summary, then its 'stories' diagnostic will contain
# the singular name of its story.
is_summary = list(
hist.diagnostics.get(reserved_infos.SUMMARY_KEYS.name, []))
grouping_label = GetGroupingLabelFromHistogram(hist)
if grouping_label and (
not is_summary or reserved_infos.STORY_TAGS.name in is_summary):
path += '/' + grouping_label
is_ref = hist.diagnostics.get(reserved_infos.IS_REFERENCE_BUILD.name)
if is_ref and len(is_ref) == 1:
is_ref = is_ref.GetOnlyElement()
story_name = hist.diagnostics.get(reserved_infos.STORIES.name)
if story_name and len(story_name) == 1 and not is_summary:
escaped_story_name = story_name.GetOnlyElement()
path += '/' + escaped_story_name
if is_ref:
path += '_ref'
elif is_ref:
path += '/ref'
return path
def Dumps(obj):
return json.dumps(obj, separators=(',', ':')).encode('utf-8')
def _MergeHistogramSetByPath(hs):
with TempFile() as temp:
temp.write(Dumps(hs.AsDicts()))
temp.close()
return merge_histograms.MergeHistograms(temp.name, (
reserved_infos.TEST_PATH.name,))
def _GetAndDeleteHadFailures(hs):
had_failures = False
for h in hs:
had_failures_diag = h.diagnostics.get(reserved_infos.HAD_FAILURES.name)
if had_failures_diag:
del h.diagnostics[reserved_infos.HAD_FAILURES.name]
had_failures = True
return had_failures
def _MergeAndReplaceSharedDiagnostics(diagnostic_name, hs):
merged = None
for h in hs:
d = h.diagnostics.get(diagnostic_name)
if not d:
continue
if not merged:
merged = d
else:
merged.AddDiagnostic(d)
h.diagnostics[diagnostic_name] = merged
def Batch(histograms, max_bytes, strict=False):
all_json = Dumps(histograms.AsDicts())
if max_bytes == 0:
return [all_json]
avg_hist_size = len(all_json) / len(histograms)
del all_json
avg_batch_size = max(1, int(round(max_bytes / avg_hist_size)))
# Return an array of HistogramSet json strings, each not larger than
# max_bytes. Take |avg_batch_size| histograms, then add or remove one at a
# time until the batch_json size is just under max_bytes.
histograms = list(histograms)
def DumpsFirst(n):
hs = histogram_set.HistogramSet(histograms[:n])
hs.DeduplicateDiagnostics()
return Dumps(hs.AsDicts())
batches = []
while histograms:
batch_size = avg_batch_size
batch_json = DumpsFirst(batch_size)
while len(batch_json) < max_bytes and batch_size < len(histograms):
batch_size += 1
batch_json = DumpsFirst(batch_size)
while len(batch_json) > max_bytes and batch_size > 1:
batch_size -= 1
batch_json = DumpsFirst(batch_size)
if strict and len(batch_json) > max_bytes and batch_size == 1:
raise ValueError('Found a single Histogram larger than max_bytes')
histograms = histograms[batch_size:]
batches.append(batch_json)
return batches
def AddReservedDiagnostics(histogram_dicts, names_to_values, max_bytes=0):
# We need to generate summary statistics for anything that had a story, so
# filter out every histogram with no stories, then merge. If you keep the
# histograms with no story, you end up with duplicates.
hs_with_stories = _LoadHistogramSet(histogram_dicts)
if len(hs_with_stories) == 0:
return []
hs_with_stories.FilterHistograms(
lambda h: not h.diagnostics.get(reserved_infos.STORIES.name, []))
hs_with_no_stories = _LoadHistogramSet(histogram_dicts)
hs_with_no_stories.FilterHistograms(
lambda h: h.diagnostics.get(reserved_infos.STORIES.name, []))
# TODO(#3987): Refactor recipes to call merge_histograms separately.
# This call combines all repetitions of a metric for a given story into a
# single histogram.
hs = histogram_set.HistogramSet()
hs.ImportDicts(hs_with_stories.AsDicts())
for h in hs:
h.diagnostics[reserved_infos.TEST_PATH.name] = (
generic_set.GenericSet([ComputeTestPath(h)]))
_GetAndDeleteHadFailures(hs)
dicts_across_repeats = _MergeHistogramSetByPath(hs)
had_failures = _GetAndDeleteHadFailures(hs_with_stories)
if not had_failures:
# This call creates summary metrics across each tag set of stories.
hs = histogram_set.HistogramSet()
hs.ImportDicts(hs_with_stories.AsDicts())
hs.FilterHistograms(lambda h: not GetGroupingLabelFromHistogram(h))
for h in hs:
h.diagnostics[reserved_infos.SUMMARY_KEYS.name] = (
generic_set.GenericSet(['name', 'storyTags']))
h.diagnostics[reserved_infos.TEST_PATH.name] = (
generic_set.GenericSet([ComputeTestPath(h)]))
dicts_across_stories = _MergeHistogramSetByPath(hs)
# This call creates summary metrics across the entire story set.
hs = histogram_set.HistogramSet()
hs.ImportDicts(hs_with_stories.AsDicts())
for h in hs:
h.diagnostics[reserved_infos.SUMMARY_KEYS.name] = (
generic_set.GenericSet(['name']))
h.diagnostics[reserved_infos.TEST_PATH.name] = (
generic_set.GenericSet([ComputeTestPath(h)]))
dicts_across_names = _MergeHistogramSetByPath(hs)
else:
dicts_across_stories = []
dicts_across_names = []
# Now load everything into one histogram set. First we load the summary
# histograms, since we need to mark them with SUMMARY_KEYS.
# After that we load the rest, and then apply all the diagnostics specified
# on the command line. Finally, since we end up with a lot of diagnostics
# that no histograms refer to, we make sure to prune those.
histograms = histogram_set.HistogramSet()
histograms.ImportDicts(dicts_across_names)
histograms.ImportDicts(dicts_across_stories)
histograms.ImportDicts(dicts_across_repeats)
histograms.ImportDicts(hs_with_no_stories.AsDicts())
histograms.DeduplicateDiagnostics()
for name, value in names_to_values.items():
assert name in ALL_NAMES
histograms.AddSharedDiagnosticToAllHistograms(
name, generic_set.GenericSet([value]))
histograms.RemoveOrphanedDiagnostics()
if len(histograms) == 0:
return []
return Batch(histograms, max_bytes)