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