aboutsummaryrefslogtreecommitdiff
path: root/user_activity_benchmarks/benchmark_metrics.py
blob: 30ae31e0918c40bd6bc40caa7e7a5749f6b05e1b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# Copyright 2016 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Computes the metrics for functions, Chrome OS components and benchmarks."""

from collections import defaultdict


def ComputeDistanceForFunction(child_functions_statistics_sample,
                               child_functions_statistics_reference):
  """Computes the distance metric for a function.

  Args:
    child_functions_statistics_sample: A dict that has as a key the name of a
      function and as a value the inclusive count fraction. The keys are
      the child functions of a sample parent function.
    child_functions_statistics_reference: A dict that has as a key the name of
      a function and as a value the inclusive count fraction. The keys are
      the child functions of a reference parent function.

  Returns:
    A float value representing the sum of inclusive count fraction
    differences of pairs of common child functions. If a child function is
    present in a single data set, then we consider the missing inclusive
    count fraction as 0. This value describes the difference in behaviour
    between a sample and the reference parent function.
  """
  # We initialize the distance with a small value to avoid the further
  # division by zero.
  distance = 1.0

  for child_function, inclusive_count_fraction_reference in \
      child_functions_statistics_reference.iteritems():
    inclusive_count_fraction_sample = 0.0

    if child_function in child_functions_statistics_sample:
      inclusive_count_fraction_sample = \
          child_functions_statistics_sample[child_function]
    distance += \
        abs(inclusive_count_fraction_sample -
            inclusive_count_fraction_reference)

  for child_function, inclusive_count_fraction_sample in \
      child_functions_statistics_sample.iteritems():
    if child_function not in child_functions_statistics_reference:
      distance += inclusive_count_fraction_sample

  return distance


def ComputeScoreForFunction(distance, reference_fraction, sample_fraction):
  """Computes the score for a function.

  Args:
    distance: A float value representing the difference in behaviour between
      the sample and the reference function.
    reference_fraction: A float value representing the inclusive count
      fraction of the reference function.
    sample_fraction: A float value representing the inclusive count
      fraction of the sample function.

  Returns:
    A float value representing the score of the function.
  """
  return reference_fraction * sample_fraction / distance


def ComputeMetricsForComponents(cwp_function_groups, function_metrics):
  """Computes the metrics for a set of Chrome OS components.

  For every Chrome OS group, we compute the number of functions matching the
  group, the cumulative and average score, the cumulative and average distance
  of all those functions. A function matches a group if the path of the file
  containing its definition contains the common path describing the group.

  Args:
    cwp_function_groups: A dict having as a key the name of the group and as a
      value a common path describing the group.
    function_metrics: A dict having as a key the name of the function and the
      name of the file where it is declared concatenated by a ',', and as a
      value a tuple containing the distance and the score metrics.

  Returns:
    A dict containing as a key the name of the group and as a value a tuple
    with the group file path, the number of functions matching the group,
    the cumulative and average score, cumulative and average distance of all
    those functions.
  """
  function_groups_metrics = defaultdict(lambda: (0, 0.0, 0.0, 0.0, 0.0))

  for function_key, metric in function_metrics.iteritems():
    _, function_file = function_key.split(',')

    for group, common_path in cwp_function_groups:
      if common_path not in function_file:
        continue

      function_distance = metric[0]
      function_score = metric[1]
      group_statistic = function_groups_metrics[group]

      function_count = group_statistic[1] + 1
      function_distance_cum = function_distance + group_statistic[2]
      function_distance_avg = function_distance_cum / float(function_count)
      function_score_cum = function_score + group_statistic[4]
      function_score_avg = function_score_cum / float(function_count)

      function_groups_metrics[group] = \
          (common_path,
           function_count,
           function_distance_cum,
           function_distance_avg,
           function_score_cum,
           function_score_avg)
      break

  return function_groups_metrics


def ComputeMetricsForBenchmark(function_metrics):
  function_count = len(function_metrics.keys())
  distance_cum = 0.0
  distance_avg = 0.0
  score_cum = 0.0
  score_avg = 0.0

  for distance, score in function_metrics.values():
    distance_cum += distance
    score_cum += score

  distance_avg = distance_cum / float(function_count)
  score_avg = score_cum / float(function_count)
  return function_count, distance_cum, distance_avg, score_cum, score_avg


def ComputeFunctionCountForBenchmarkSet(set_function_metrics, cwp_functions,
                                        metric_string):
  """Computes the function count metric pair for the benchmark set.

     For the function count metric, we count the unique functions covered by the
     set of benchmarks. We compute the fraction of unique functions out
     of the amount of CWP functions given.

     We compute also the same metric pair for every group from the keys of the
     set_function_metrics dict.

  Args:
    set_function_metrics: A list of dicts having as a key the name of a group
      and as value a list of functions that match the given group.
    cwp_functions: A dict having as a key the name of the groups and as a value
      the list of CWP functions that match an individual group.
    metric_string: A tuple of strings that will be mapped to the tuple of metric
      values in the returned function group dict. This is done for convenience
      for the JSON output.

  Returns:
    A tuple with the metric pair and a dict with the group names and values
    of the metric pair. The first value of the metric pair represents the
    function count and the second value the function count fraction.
    The dict has as a key the name of the group and as a value a dict that
    maps the metric_string  to the values of the metric pair of the group.
  """
  cwp_functions_count = sum(len(functions)
                            for functions in cwp_functions.itervalues())
  set_groups_functions = defaultdict(set)
  for benchmark_function_metrics in set_function_metrics:
    for group_name in benchmark_function_metrics:
      set_groups_functions[group_name] |= \
          set(benchmark_function_metrics[group_name])

  set_groups_functions_count = {}
  set_functions_count = 0
  for group_name, functions \
      in set_groups_functions.iteritems():
    set_group_functions_count = len(functions)
    if group_name in cwp_functions:
      set_groups_functions_count[group_name] = {
          metric_string[0]: set_group_functions_count,
          metric_string[1]:
          set_group_functions_count / float(len(cwp_functions[group_name]))}
    else:
      set_groups_functions_count[group_name] = \
          {metric_string[0]: set_group_functions_count, metric_string[1]: 0.0}
    set_functions_count += set_group_functions_count

  set_functions_count_fraction = \
      set_functions_count / float(cwp_functions_count)
  return (set_functions_count, set_functions_count_fraction), \
      set_groups_functions_count


def ComputeDistanceForBenchmarkSet(set_function_metrics, cwp_functions,
                                   metric_string):
  """Computes the distance variation metric pair for the benchmark set.

     For the distance variation metric, we compute the sum of the distance
     variations of the functions covered by a set of benchmarks.
     We define the distance variation as the difference between the distance
     value of a functions and the ideal distance value (1.0).
     If a function appears in multiple common functions files, we consider
     only the minimum value. We compute also the distance variation per
     function.

     In addition, we compute also the same metric pair for every group from
     the keys of the set_function_metrics dict.

  Args:
    set_function_metrics: A list of dicts having as a key the name of a group
      and as value a list of functions that match the given group.
    cwp_functions: A dict having as a key the name of the groups and as a value
      the list of CWP functions that match an individual group.
    metric_string: A tuple of strings that will be mapped to the tuple of metric
      values in the returned function group dict. This is done for convenience
      for the JSON output.

  Returns:
    A tuple with the metric pair and a dict with the group names and values
    of the metric pair. The first value of the metric pair represents the
    distance variation per function and the second value the distance variation.
    The dict has as a key the name of the group and as a value a dict that
    maps the metric_string to the values of the metric pair of the group.
  """
  set_unique_functions = defaultdict(lambda: defaultdict(lambda: float('inf')))
  set_function_count = 0
  total_distance_variation = 0.0
  for benchmark_function_metrics in set_function_metrics:
    for group_name in benchmark_function_metrics:
      for function_key, metrics in \
          benchmark_function_metrics[group_name].iteritems():
        previous_distance = \
            set_unique_functions[group_name][function_key]
        min_distance = min(metrics[0], previous_distance)
        set_unique_functions[group_name][function_key] = min_distance
  groups_distance_variations = defaultdict(lambda: (0.0, 0.0))
  for group_name, functions_distances in set_unique_functions.iteritems():
    group_function_count = len(functions_distances)
    group_distance_variation = \
        sum(functions_distances.itervalues()) - float(group_function_count)
    total_distance_variation += group_distance_variation
    set_function_count += group_function_count
    groups_distance_variations[group_name] = \
        {metric_string[0]:
         group_distance_variation / float(group_function_count),
         metric_string[1]: group_distance_variation}

  return (total_distance_variation / set_function_count,
          total_distance_variation), groups_distance_variations


def ComputeScoreForBenchmarkSet(set_function_metrics, cwp_functions,
                                metric_string):
  """Computes the function count metric pair for the benchmark set.

     For the score metric, we compute the sum of the scores of the functions
     from a set of benchmarks. If a function appears in multiple common
     functions files, we consider only the maximum value. We compute also the
     fraction of this sum from the sum of all the scores of the functions from
     the CWP data covering the given groups, in the ideal case (the ideal
     score of a function is 1.0).

     In addition, we compute the same metric pair for every group from the
     keys of the set_function_metrics dict.

  Args:
    set_function_metrics: A list of dicts having as a key the name of a group
      and as value a list of functions that match the given group.
    cwp_functions: A dict having as a key the name of the groups and as a value
      the list of CWP functions that match an individual group.
    metric_string: A tuple of strings that will be mapped to the tuple of metric
      values in the returned function group dict. This is done for convenience
      for the JSON output.

  Returns:
    A tuple with the metric pair and a dict with the group names and values
    of the metric pair. The first value of the pair is the fraction of the sum
    of the scores from the ideal case and the second value represents the
    sum of scores of the functions. The dict has as a key the name of the group
    and as a value a dict that maps the metric_string to the values of the
    metric pair of the group.
  """
  cwp_functions_count = sum(len(functions)
                            for functions in cwp_functions.itervalues())
  set_unique_functions = defaultdict(lambda: defaultdict(lambda: 0.0))
  total_score = 0.0

  for benchmark_function_metrics in set_function_metrics:
    for group_name in benchmark_function_metrics:
      for function_key, metrics in \
          benchmark_function_metrics[group_name].iteritems():
        previous_score = \
            set_unique_functions[group_name][function_key]
        max_score = max(metrics[1], previous_score)
        set_unique_functions[group_name][function_key] = max_score

  groups_scores = defaultdict(lambda: (0.0, 0.0))

  for group_name, function_scores in set_unique_functions.iteritems():
    group_function_count = float(len(cwp_functions[group_name]))
    group_score = sum(function_scores.itervalues())
    total_score += group_score
    groups_scores[group_name] = {
        metric_string[0]: group_score / group_function_count,
        metric_string[1]: group_score
    }

  return (total_score / cwp_functions_count, total_score), groups_scores