1# Copyright 2014 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5from collections import defaultdict
6
7from telemetry.value import failure
8from telemetry.value import merge_values
9from telemetry.value import skip
10
11
12class Summary(object):
13  """Computes summary values from the per-page-run values produced by a test.
14
15  Some telemetry benchmark repeat a number of times in order to get a reliable
16  measurement. The test does not have to handle merging of these runs:
17  summarizer does it for you.
18
19  For instance, if two pages run, 3 and 1 time respectively:
20      ScalarValue(page1, 'foo', units='ms', 1)
21      ScalarValue(page1, 'foo', units='ms', 1)
22      ScalarValue(page1, 'foo', units='ms', 1)
23      ScalarValue(page2, 'foo', units='ms', 2)
24
25  Then summarizer will produce two sets of values. First,
26  computed_per_page_values:
27      [
28         ListOfScalarValues(page1, 'foo', units='ms', [1,1,1])],
29         ListOfScalarValues(page2, 'foo', units='ms', [2])]
30      ]
31
32  In addition, it will produce a summary value:
33      [
34         ListOfScalarValues(page=None, 'foo', units='ms', [1,1,1,2])]
35      ]
36
37  """
38  def __init__(self, all_page_specific_values,
39               key_func=merge_values.DefaultKeyFunc):
40    had_failures = any(isinstance(v, failure.FailureValue) for v in
41        all_page_specific_values)
42    self.had_failures = had_failures
43    self._computed_per_page_values = []
44    self._computed_summary_values = []
45    self._interleaved_computed_per_page_values_and_summaries = []
46    self._key_func = key_func
47    self._ComputePerPageValues(all_page_specific_values)
48
49  @property
50  def computed_per_page_values(self):
51    return self._computed_per_page_values
52
53  @property
54  def computed_summary_values(self):
55    return self._computed_summary_values
56
57  @property
58  def interleaved_computed_per_page_values_and_summaries(self):
59    """Returns the computed per page values and summary values interleaved.
60
61    All the results for a given name are printed together. First per page
62    values, then summary values.
63
64    """
65    return self._interleaved_computed_per_page_values_and_summaries
66
67  def _ComputePerPageValues(self, all_page_specific_values):
68    all_successful_page_values = [
69        v for v in all_page_specific_values if not (isinstance(
70            v, failure.FailureValue) or isinstance(v, skip.SkipValue))]
71
72    # We will later need to determine how many values were originally created
73    # for each value name, to apply a workaround meant to clean up the printf
74    # output.
75    num_successful_pages_for_key = defaultdict(int)
76    for v in all_successful_page_values:
77      num_successful_pages_for_key[self._key_func(v)] += 1
78
79    # By here, due to page repeat options, all_values_from_successful_pages
80    # contains values of the same name not only from mulitple pages, but also
81    # from the same name. So even if, for instance, only one page ran, it may
82    # have run twice, producing two 'x' values.
83    #
84    # So, get rid of the repeated pages by merging.
85    merged_page_values = merge_values.MergeLikeValuesFromSamePage(
86        all_successful_page_values, self._key_func)
87
88    # Now we have a bunch of values, but there is only one value_name per page.
89    # Suppose page1 and page2 ran, producing values x and y. We want to print
90    #    x for page1
91    #    x for page2
92    #    x for page1, page2 combined
93    #
94    #    y for page1
95    #    y for page2
96    #    y for page1, page2 combined
97    #
98    # We already have the x values in the values array. But, we will need
99    # them indexable by summary key.
100    #
101    # The following dict maps summary_key -> list of pages that have values of
102    # that name.
103    per_page_values_by_key = defaultdict(list)
104    for value in merged_page_values:
105      per_page_values_by_key[self._key_func(value)].append(value)
106
107    # We already have the x values in the values array. But, we also need
108    # the values merged across the pages. And, we will need them indexed by
109    # summary key so that we can find them when printing out value names in
110    # alphabetical order.
111    merged_pages_value_by_key = {}
112    if not self.had_failures:
113      for value in merge_values.MergeLikeValuesFromDifferentPages(
114          all_successful_page_values, self._key_func):
115        value_key = self._key_func(value)
116        assert value_key not in merged_pages_value_by_key
117        merged_pages_value_by_key[value_key] = value
118
119    keys = sorted(set([self._key_func(v) for v in merged_page_values]))
120
121    # Time to walk through the values by key, printing first the page-specific
122    # values and then the merged_site value.
123    for key in keys:
124      per_page_values = per_page_values_by_key.get(key, [])
125
126      # Sort the values by their URL.
127      sorted_per_page_values = list(per_page_values)
128      sorted_per_page_values.sort(
129          key=lambda per_page_values: per_page_values.page.display_name)
130
131      # Output the page-specific results.
132      num_successful_pages_for_this_key = (
133          num_successful_pages_for_key[key])
134      for per_page_value in sorted_per_page_values:
135        self._ComputePerPageValue(per_page_value,
136                                  num_successful_pages_for_this_key)
137
138      # Output the combined values.
139      merged_pages_value = merged_pages_value_by_key.get(key, None)
140      if merged_pages_value:
141        self._computed_summary_values.append(merged_pages_value)
142        self._interleaved_computed_per_page_values_and_summaries.append(
143            merged_pages_value)
144
145  def _ComputePerPageValue(
146      self, value, num_successful_pages_for_this_value_name):
147    if num_successful_pages_for_this_value_name >= 1:
148      # Save the result.
149      self._computed_per_page_values.append(value)
150      self._interleaved_computed_per_page_values_and_summaries.append(value)
151