1# Copyright 2013 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 telemetry.value import failure
6from telemetry.value import skip
7
8
9def MergeLikeValuesFromSamePage(all_values):
10  """Merges values that measure the same thing on the same page.
11
12  A page may end up being measured multiple times, meaning that we may end up
13  with something like this:
14       ScalarValue(page1, 'x', 1)
15       ScalarValue(page2, 'x', 4)
16       ScalarValue(page1, 'x', 2)
17       ScalarValue(page2, 'x', 5)
18
19  This function will produce:
20       ListOfScalarValues(page1, 'x', [1, 2])
21       ListOfScalarValues(page2, 'x', [4, 5])
22
23  The workhorse of this code is Value.MergeLikeValuesFromSamePage.
24
25  This requires (but assumes) that the values passed in with the same grouping
26  key pass the Value.IsMergableWith test. If this is not obeyed, the
27  results will be undefined.
28  """
29  return _MergeLikeValuesCommon(
30      all_values,
31      lambda x: (x.page, x.name),
32      lambda v0, merge_group: v0.MergeLikeValuesFromSamePage(merge_group))
33
34
35def MergeLikeValuesFromDifferentPages(all_values, group_by_name_suffix=False):
36  """Merges values that measure the same thing on different pages.
37
38  After using MergeLikeValuesFromSamePage, one still ends up with values from
39  different pages:
40       ScalarValue(page1, 'x', 1)
41       ScalarValue(page1, 'y', 30)
42       ScalarValue(page2, 'x', 2)
43       ScalarValue(page2, 'y', 40)
44
45  This function will group the values of the same value_name together:
46       ListOfScalarValues(None, 'x', [1, 2])
47       ListOfScalarValues(None, 'y', [30, 40])
48
49  If group_by_name_suffix is True, then x.z and y.z are considered to be the
50  same value and are grouped together. If false, then x.z and y.z are
51  considered different.
52
53  The workhorse of this code is Value.MergeLikeValuesFromDifferentPages.
54
55  Not all values that go into this function will come out: not every value can
56  be merged across pages. Values whose MergeLikeValuesFromDifferentPages returns
57  None will be omitted from the results.
58
59  This requires (but assumes) that the values passed in with the same name pass
60  the Value.IsMergableWith test. If this is not obeyed, the results
61  will be undefined.
62  """
63  if group_by_name_suffix:
64    def key(value):
65      return value.name_suffix
66  else:
67    key = lambda x: x.name
68  return _MergeLikeValuesCommon(
69      all_values,
70      key,
71      lambda v0, merge_group: v0.MergeLikeValuesFromDifferentPages(
72          merge_group, group_by_name_suffix=group_by_name_suffix))
73
74
75def _MergeLikeValuesCommon(all_values, key_func, merge_func):
76  """Groups all_values by key_func then applies merge_func to the groups.
77
78  This takes the all_values list and groups each item in that using the key
79  provided by key_func. This produces groups of values with like keys. Thes are
80  then handed to the merge_func to produce a new key. If merge_func produces a
81  non-None return, it is added to the list of returned values.
82  """
83  # When merging, we want to merge values in a consistent order, e.g. so that
84  # Scalar(1), Scalar(2) predictably produces ListOfScalarValues([1,2]) rather
85  # than 2,1.
86  #
87  # To do this, the values are sorted by key up front. Then, grouping is
88  # performed using a dictionary, but as new groups are found, the order in
89  # which they were found is also noted.
90  #
91  # Merging is then performed on groups in group-creation-order. This ensures
92  # that the returned array is in a stable order, group by group.
93  #
94  # Within a group, the order is stable because of the original sort.
95  all_values = list(all_values)
96  merge_groups = GroupStably(all_values, key_func)
97
98  res = []
99  for merge_group in merge_groups:
100    v0 = merge_group[0]
101    vM = merge_func(v0, merge_group)
102    if vM:
103      res.append(vM)
104  return res
105
106def GroupStably(all_values, key_func):
107  """Groups an array by key_func, with the groups returned in a stable order.
108
109  Returns a list of groups.
110  """
111  all_values = list(all_values)
112
113  merge_groups = {}
114  merge_groups_in_creation_order = []
115  for value in all_values:
116    # TODO(chrishenry): This is temporary. When we figure out the
117    # right summarization strategy for page runs with failures/skips, we
118    # should use that instead.
119    should_skip_value = (isinstance(value, failure.FailureValue) or
120                         isinstance(value, skip.SkipValue))
121
122    if should_skip_value:
123      continue
124
125    key = key_func(value)
126    if key not in merge_groups:
127      merge_groups[key] = []
128      merge_groups_in_creation_order.append(merge_groups[key])
129    merge_groups[key].append(value)
130  return merge_groups_in_creation_order
131