1# This Source Code Form is subject to the terms of the Mozilla Public
2# License, v. 2.0. If a copy of the MPL was not distributed with this
3# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5# originally taken from /testing/talos/talos/filter.py
6
7from __future__ import absolute_import, division
8
9import math
10
11"""
12data filters:
13takes a series of run data and applies statistical transforms to it
14
15Each filter is a simple function, but it also have attached a special
16`prepare` method that create a tuple with one instance of a
17:class:`Filter`; this allow to write stuff like::
18
19  from raptor import filters
20  filter_list = filters.ignore_first.prepare(1) + filters.median.prepare()
21
22  for filter in filter_list:
23      data = filter(data)
24  # data is filtered
25"""
26
27_FILTERS = {}
28
29
30class Filter(object):
31    def __init__(self, func, *args, **kwargs):
32        """
33        Takes a filter function, and save args and kwargs that
34        should be used when the filter is used.
35        """
36        self.func = func
37        self.args = args
38        self.kwargs = kwargs
39
40    def apply(self, data):
41        """
42        Apply the filter on the data, and return the new data
43        """
44        return self.func(data, *self.args, **self.kwargs)
45
46
47def define_filter(func):
48    """
49    decorator to attach the prepare method.
50    """
51
52    def prepare(*args, **kwargs):
53        return (Filter(func, *args, **kwargs),)
54
55    func.prepare = prepare
56    return func
57
58
59def register_filter(func):
60    """
61    all filters defined in this module
62    should be registered
63    """
64    global _FILTERS
65
66    _FILTERS[func.__name__] = func
67    return func
68
69
70def filters(*args):
71    global _FILTERS
72
73    filters_ = [_FILTERS[filter] for filter in args]
74    return filters_
75
76
77def apply(data, filters):
78    for filter in filters:
79        data = filter(data)
80
81    return data
82
83
84def parse(string_):
85    def to_number(string_number):
86        try:
87            return int(string_number)
88        except ValueError:
89            return float(string_number)
90
91    tokens = string_.split(":")
92
93    func = tokens[0]
94    digits = []
95    if len(tokens) > 1:
96        digits.extend(tokens[1].split(","))
97        digits = [to_number(digit) for digit in digits]
98
99    return [func, digits]
100
101
102# filters that return a scalar
103
104
105@register_filter
106@define_filter
107def mean(series):
108    """
109    mean of data; needs at least one data point
110    """
111    return sum(series) / float(len(series))
112
113
114@register_filter
115@define_filter
116def median(series):
117    """
118    median of data; needs at least one data point
119    """
120    series = sorted(series)
121    if len(series) % 2:
122        # odd
123        # pylint --py3k W1619
124        # must force to int to use as index.
125        return series[int(len(series) / 2)]
126    else:
127        # even
128        # pylint --py3k W1619
129        middle = int(len(series) / 2)  # the higher of the middle 2, actually
130        return 0.5 * (series[middle - 1] + series[middle])
131
132
133@register_filter
134@define_filter
135def variance(series):
136    """
137    variance: http://en.wikipedia.org/wiki/Variance
138    """
139
140    _mean = mean(series)
141    variance = sum([(i - _mean) ** 2 for i in series]) / float(len(series))
142    return variance
143
144
145@register_filter
146@define_filter
147def stddev(series):
148    """
149    standard deviation: http://en.wikipedia.org/wiki/Standard_deviation
150    """
151    return variance(series) ** 0.5
152
153
154@register_filter
155@define_filter
156def dromaeo(series):
157    """
158    dromaeo: https://wiki.mozilla.org/Dromaeo, pull the internal calculation
159    out
160      * This is for 'runs/s' based tests, not 'ms' tests.
161      * chunksize: defined in dromaeo: tests/dromaeo/webrunner.js#l8
162    """
163    means = []
164    chunksize = 5
165    series = list(dromaeo_chunks(series, chunksize))
166    for i in series:
167        means.append(mean(i))
168    return geometric_mean(means)
169
170
171@register_filter
172@define_filter
173def dromaeo_chunks(series, size):
174    for i in range(0, len(series), size):
175        yield series[i : i + size]
176
177
178@register_filter
179@define_filter
180def geometric_mean(series):
181    """
182    geometric_mean: http://en.wikipedia.org/wiki/Geometric_mean
183    """
184    total = 0
185    for i in series:
186        total += math.log(i + 1)
187    # pylint --py3k W1619
188    return math.exp(total / len(series)) - 1
189
190
191# filters that return a list
192
193
194@register_filter
195@define_filter
196def ignore_first(series, number=1):
197    """
198    ignore first datapoint
199    """
200    if len(series) <= number:
201        # don't modify short series
202        return series
203    return series[number:]
204
205
206@register_filter
207@define_filter
208def ignore(series, function):
209    """
210    ignore the first value of a list given by function
211    """
212    if len(series) <= 1:
213        # don't modify short series
214        return series
215    series = series[:]  # do not mutate the original series
216    value = function(series)
217    series.remove(value)
218    return series
219
220
221@register_filter
222@define_filter
223def ignore_max(series):
224    """
225    ignore maximum data point
226    """
227    return ignore(series, max)
228
229
230@register_filter
231@define_filter
232def ignore_min(series):
233    """
234    ignore minimum data point
235    """
236    return ignore(series, min)
237
238
239@register_filter
240@define_filter
241def ignore_negative(series):
242    """
243    ignore data points that have a negative value
244    caution: if all data values are < 0, this will return an empty list
245    """
246    if len(series) <= 1:
247        # don't modify short series
248        return series
249    series = series[:]  # do not mutate the original series
250    return list(filter(lambda x: x >= 0, series))
251
252
253@register_filter
254@define_filter
255def v8_subtest(series, name):
256    """
257    v8 benchmark score - modified for no sub benchmarks.
258    * removed Crypto and kept Encrypt/Decrypt standalone
259    * removed EarlyBoyer and kept Earley/Boyer standalone
260
261    this is not 100% in parity but within .3%
262    """
263    reference = {
264        "Encrypt": 266181.0,
265        "Decrypt": 266181.0,
266        "DeltaBlue": 66118.0,
267        "Earley": 666463.0,
268        "Boyer": 666463.0,
269        "NavierStokes": 1484000.0,
270        "RayTrace": 739989.0,
271        "RegExp": 910985.0,
272        "Richards": 35302.0,
273        "Splay": 81491.0,
274    }
275
276    # pylint --py3k W1619
277    return reference[name] / geometric_mean(series)
278
279
280@register_filter
281@define_filter
282def responsiveness_Metric(val_list):
283    return sum([float(x) * float(x) / 1000000.0 for x in val_list])
284