1#    Copyright 2015-2017 ARM Limited
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14#
15
16
17from test_thermal import BaseTestThermal
18import trappy
19from trappy.stats.grammar import Parser
20from pandas.util.testing import assert_series_equal
21import numpy as np
22import pandas
23from distutils.version import LooseVersion as V
24import unittest
25
26
27class TestStatsGrammar(BaseTestThermal):
28
29    def __init__(self, *args, **kwargs):
30        super(TestStatsGrammar, self).__init__(*args, **kwargs)
31
32    def test_sum_operator(self):
33        """Test Addition And Subtraction: Numeric"""
34
35        parser = Parser(trappy.BareTrace())
36        # Simple equation
37        eqn = "10 + 2 - 3"
38        self.assertEquals(parser.solve(eqn), 9)
39        # Equation with bracket and unary ops
40        eqn = "(10 + 2) - (-3 + 2)"
41        self.assertEquals(parser.solve(eqn), 13)
42
43    @unittest.skipIf(V(pandas.__version__) < V('0.16.1'),
44                     "check_names is not supported in pandas < 0.16.1")
45    def test_accessors_sum(self):
46        """Test Addition And Subtraction: Data"""
47
48        thermal_zone_id = 0
49        parser = Parser(trappy.FTrace())
50        # Equation with dataframe accessors
51        eqn = "trappy.thermal.Thermal:temp + \
52trappy.thermal.Thermal:temp"
53
54        assert_series_equal(
55            parser.solve(eqn)[thermal_zone_id],
56            2 *
57            parser.data.thermal.data_frame["temp"], check_names=False)
58
59    def test_funcparams_sum(self):
60        """Test Addition And Subtraction: Functions"""
61
62        thermal_zone_id = 0
63        parser = Parser(trappy.FTrace())
64        # Equation with functions as parameters (Mixed)
65        eqn = "numpy.mean(trappy.thermal.Thermal:temp) + 1000"
66        self.assertEquals(
67            parser.solve(eqn)[thermal_zone_id],
68            np.mean(
69                parser.data.thermal.data_frame["temp"]) +
70            1000)
71        # Multiple func params
72        eqn = "numpy.mean(trappy.thermal.Thermal:temp) + numpy.mean(trappy.thermal.Thermal:temp)"
73        self.assertEquals(
74            parser.solve(eqn)[thermal_zone_id],
75            np.mean(
76                parser.data.thermal.data_frame["temp"]) *
77            2)
78
79    def test_parser_with_name(self):
80        """Test equation using event name"""
81
82        thermal_zone_id = 0
83        parser = Parser(trappy.FTrace())
84        # Equation with functions as parameters (Mixed)
85        eqn = "numpy.mean(thermal:temp) + 1000"
86        self.assertEquals(
87            parser.solve(eqn)[thermal_zone_id],
88            np.mean(
89                parser.data.thermal.data_frame["temp"]) + 1000)
90
91    def test_bool_ops_vector(self):
92        """Test Logical Operations: Vector"""
93
94        thermal_zone_id = 0
95        # The equation returns a vector mask
96        parser = Parser(trappy.FTrace())
97        eqn = "(trappy.thermal.ThermalGovernor:current_temperature > 77000)\
98                & (trappy.pid_controller.PIDController:output > 2500)"
99        mask = parser.solve(eqn)
100        self.assertEquals(len(parser.ref(mask.dropna()[0])), 0)
101
102    def test_bool_ops_scalar(self):
103        """Test Logical Operations: Vector"""
104
105        thermal_zone_id=0
106        parser = Parser(trappy.FTrace())
107        # The equation returns a boolean scalar
108        eqn = "(numpy.mean(trappy.thermal.Thermal:temp) > 65000) && (numpy.mean(trappy.cpu_power.CpuOutPower) > 500)"
109        self.assertTrue(parser.solve(eqn)[thermal_zone_id])
110        eqn = "(numpy.mean(trappy.thermal.Thermal:temp) > 65000) || (numpy.mean(trappy.cpu_power.CpuOutPower) < 500)"
111        self.assertTrue(parser.solve(eqn)[thermal_zone_id])
112
113    def test_super_indexing(self):
114        "Test if super-indexing works correctly"""
115
116        trace = trappy.FTrace()
117        parser = Parser(trace)
118        # The first event has less index values
119        sol1 = parser.solve("trappy.thermal.Thermal:temp")
120        # The second index has more index values
121        sol2 = parser.solve("trappy.pid_controller.PIDController:output")
122        # Super Indexing should result in len(sol2) > len(sol1)
123        self.assertGreater(len(sol2), len(sol1))
124
125    def test_single_func_call(self):
126        """Test Single Function Call"""
127
128        thermal_zone_id = 0
129        parser = Parser(trappy.FTrace())
130        eqn = "numpy.mean(trappy.thermal.Thermal:temp)"
131        self.assertEquals(
132            parser.solve(eqn)[thermal_zone_id],
133            np.mean(
134                parser.data.thermal.data_frame["temp"]))
135
136    def test_mul_ops(self):
137        """Test Mult and Division: Numeric"""
138
139        parser = Parser(trappy.BareTrace())
140        eqn = "(10 * 2 / 10)"
141        self.assertEquals(parser.solve(eqn), 2)
142        eqn = "-2 * 2 + 2 * 10 / 10"
143        self.assertEquals(parser.solve(eqn), -2)
144        eqn = "3.5 // 2"
145        self.assertEquals(parser.solve(eqn), 1)
146        eqn = "5 % 2"
147        self.assertEquals(parser.solve(eqn), 1)
148
149    def test_exp_ops(self):
150        """Test exponentiation: Numeric"""
151        parser = Parser(trappy.BareTrace())
152        eqn = "3**3 * 2**4"
153        self.assertEquals(parser.solve(eqn), 432)
154        eqn = "3**(4/2)"
155        self.assertEquals(parser.solve(eqn), 9)
156
157    @unittest.skipIf(V(pandas.__version__) < V('0.16.1'),
158                     "check_names is not supported in pandas < 0.16.1")
159    def test_funcparams_mul(self):
160        """Test Mult and Division: Data"""
161
162        thermal_zone_id = 0
163        parser = Parser(trappy.FTrace())
164        eqn = "trappy.thermal.Thermal:temp * 10.0"
165        series = parser.data.thermal.data_frame["temp"]
166        assert_series_equal(parser.solve(eqn)[thermal_zone_id], series * 10.0, check_names=False)
167        eqn = "trappy.thermal.Thermal:temp / trappy.thermal.Thermal:temp * 10"
168        assert_series_equal(parser.solve(eqn)[thermal_zone_id], series / series * 10, check_names=False)
169
170    def test_var_forward(self):
171        """Test Forwarding: Variable"""
172
173        thermal_zone_id = 0
174        pvars = {}
175        pvars["control_temp"] = 78000
176        parser = Parser(trappy.FTrace(), pvars=pvars)
177        eqn = "numpy.mean(trappy.thermal.Thermal:temp) < control_temp"
178        self.assertTrue(parser.solve(eqn)[thermal_zone_id])
179
180    def test_func_forward(self):
181        """Test Forwarding: Mixed"""
182
183        thermal_zone_id = 0
184        pvars = {}
185        pvars["mean"] = np.mean
186        pvars["control_temp"] = 78000
187        parser = Parser(trappy.FTrace(), pvars=pvars)
188        eqn = "mean(trappy.thermal.Thermal:temp) < control_temp"
189        self.assertTrue(parser.solve(eqn)[thermal_zone_id])
190
191    def test_cls_forward(self):
192        """Test Forwarding: Classes"""
193
194        cls = trappy.thermal.Thermal
195        pvars = {}
196        pvars["mean"] = np.mean
197        pvars["control_temp"] = 78000
198        pvars["therm"] = cls
199
200        thermal_zone_id = 0
201        parser = Parser(trappy.FTrace(), pvars=pvars)
202        eqn = "mean(therm:temp) < control_temp"
203        self.assertTrue(parser.solve(eqn)[thermal_zone_id])
204
205    def test_for_parsed_event(self):
206        """Test if an added parsed event can be accessed"""
207
208        trace = trappy.FTrace(scope="custom")
209        dfr = pandas.DataFrame({"l1_misses": [24, 535,  41],
210                                "l2_misses": [155, 11, 200],
211                                "cpu":       [ 0,   1,   0]},
212                           index=pandas.Series([1.020, 1.342, 1.451], name="Time"))
213        trace.add_parsed_event("pmu_counters", dfr)
214
215        p = Parser(trace)
216        self.assertTrue(len(p.solve("pmu_counters:cpu")), 3)
217
218    def test_windowed_parse(self):
219        """Test that the parser can operate on a window of the trace"""
220        trace = trappy.FTrace()
221
222        prs = Parser(trace, window=(2, 3))
223        dfr_res = prs.solve("thermal:temp")
224
225        self.assertGreater(dfr_res.index[0], 2)
226        self.assertLess(dfr_res.index[-1], 3)
227
228        prs = Parser(trace, window=(4, None))
229        dfr_res = prs.solve("thermal:temp")
230
231        self.assertGreater(dfr_res.index[0], 4)
232        self.assertEquals(dfr_res.index[-1], trace.thermal.data_frame.index[-1])
233
234        prs = Parser(trace, window=(0, 1))
235        dfr_res = prs.solve("thermal:temp")
236
237        self.assertEquals(dfr_res.index[0], trace.thermal.data_frame.index[0])
238        self.assertLess(dfr_res.index[-1], 1)
239
240    def test_filtered_parse(self):
241        """The Parser can filter a trace"""
242        trace = trappy.FTrace()
243
244        prs = Parser(trace, filters={"cdev_state": 3})
245        dfr_res = prs.solve("devfreq_out_power:freq")
246        self.assertEquals(len(dfr_res), 1)
247
248    def test_no_events(self):
249        """Test trying to parse absent data"""
250        trace = trappy.FTrace()
251        prs = Parser(trace)
252
253        # cpu_frequency is an event we know how to parse, but it isn't present
254        # in the test trace.
255        self.assertRaisesRegexp(ValueError, "No events found for cpu_frequency",
256                                prs.solve, "cpu_frequency:frequency")
257