def test_exp_ops(self): """Test exponentiation: Numeric""" parser = Parser(trappy.BareTrace()) eqn = "3**3 * 2**4" self.assertEquals(parser.solve(eqn), 432) eqn = "3**(4/2)" self.assertEquals(parser.solve(eqn), 9)
def test_mul_ops(self): """Test Mult and Division: Numeric""" parser = Parser(trappy.Run()) eqn = "(10 * 2 / 10)" self.assertEquals(parser.solve(eqn), 2) eqn = "-2 * 2 + 2 * 10 / 10" self.assertEquals(parser.solve(eqn), -2)
def test_funcparams_mul(self): """Test Mult and Division: Data""" thermal_zone_id = 0 parser = Parser(trappy.FTrace()) eqn = "trappy.thermal.Thermal:temp * 10.0" series = parser.data.thermal.data_frame["temp"] assert_series_equal(parser.solve(eqn)[thermal_zone_id], series * 10.0, check_names=False) eqn = "trappy.thermal.Thermal:temp / trappy.thermal.Thermal:temp * 10" assert_series_equal(parser.solve(eqn)[thermal_zone_id], series / series * 10, check_names=False)
def test_bool_ops_scalar(self): """Test Logical Operations: Vector""" thermal_zone_id=0 parser = Parser(trappy.FTrace()) # The equation returns a boolean scalar eqn = "(numpy.mean(trappy.thermal.Thermal:temp) > 65000) && (numpy.mean(trappy.cpu_power.CpuOutPower) > 500)" self.assertTrue(parser.solve(eqn)[thermal_zone_id]) eqn = "(numpy.mean(trappy.thermal.Thermal:temp) > 65000) || (numpy.mean(trappy.cpu_power.CpuOutPower) < 500)" self.assertTrue(parser.solve(eqn)[thermal_zone_id])
def test_sum_operator(self): """Test Addition And Subtraction: Numeric""" parser = Parser(trappy.BareTrace()) # Simple equation eqn = "10 + 2 - 3" self.assertEquals(parser.solve(eqn), 9) # Equation with bracket and unary ops eqn = "(10 + 2) - (-3 + 2)" self.assertEquals(parser.solve(eqn), 13)
def test_super_indexing(self): "Test if super-indexing works correctly""" trace = trappy.FTrace() parser = Parser(trace) # The first event has less index values sol1 = parser.solve("trappy.thermal.Thermal:temp") # The second index has more index values sol2 = parser.solve("trappy.pid_controller.PIDController:output") # Super Indexing should result in len(sol2) > len(sol1) self.assertGreater(len(sol2), len(sol1))
def test_filtered_parse(self): """The Parser can filter a trace""" trace = trappy.FTrace() prs = Parser(trace, filters={"cdev_state": 3}) dfr_res = prs.solve("devfreq_out_power:freq") self.assertEquals(len(dfr_res), 1)
def test_var_forward(self): """Test Forwarding: Variable""" thermal_zone_id = 0 pvars = {} pvars["control_temp"] = 78000 parser = Parser(trappy.FTrace(), pvars=pvars) eqn = "numpy.mean(trappy.thermal.Thermal:temp) < control_temp" self.assertTrue(parser.solve(eqn)[thermal_zone_id])
def test_single_func_call(self): """Test Single Function Call""" thermal_zone_id = 0 parser = Parser(trappy.FTrace()) eqn = "numpy.mean(trappy.thermal.Thermal:temp)" self.assertEquals( parser.solve(eqn)[thermal_zone_id], np.mean( parser.data.thermal.data_frame["temp"]))
def test_parser_with_name(self): """Test equation using event name""" thermal_zone_id = 0 parser = Parser(trappy.FTrace()) # Equation with functions as parameters (Mixed) eqn = "numpy.mean(thermal:temp) + 1000" self.assertEquals( parser.solve(eqn)[thermal_zone_id], np.mean(parser.data.thermal.data_frame["temp"]) + 1000)
def test_func_forward(self): """Test Forwarding: Mixed""" thermal_zone_id = 0 pvars = {} pvars["mean"] = np.mean pvars["control_temp"] = 78000 parser = Parser(trappy.FTrace(), pvars=pvars) eqn = "mean(trappy.thermal.Thermal:temp) < control_temp" self.assertTrue(parser.solve(eqn)[thermal_zone_id])
def test_bool_ops_vector(self): """Test Logical Operations: Vector""" thermal_zone_id = 0 # The equation returns a vector mask parser = Parser(trappy.FTrace()) eqn = "(trappy.thermal.ThermalGovernor:current_temperature > 77000)\ & (trappy.pid_controller.PIDController:output > 2500)" mask = parser.solve(eqn) self.assertEquals(len(parser.ref(mask.dropna()[0])), 0)
def test_funcparams_sum(self): """Test Addition And Subtraction: Functions""" thermal_zone_id = 0 parser = Parser(trappy.FTrace()) # Equation with functions as parameters (Mixed) eqn = "numpy.mean(trappy.thermal.Thermal:temp) + 1000" self.assertEquals( parser.solve(eqn)[thermal_zone_id], np.mean( parser.data.thermal.data_frame["temp"]) + 1000) # Multiple func params eqn = "numpy.mean(trappy.thermal.Thermal:temp) + numpy.mean(trappy.thermal.Thermal:temp)" self.assertEquals( parser.solve(eqn)[thermal_zone_id], np.mean( parser.data.thermal.data_frame["temp"]) * 2)
def test_parser_with_name(self): """Test equation using event name""" thermal_zone_id = 0 parser = Parser(trappy.FTrace()) # Equation with functions as parameters (Mixed) eqn = "numpy.mean(thermal:temp) + 1000" self.assertEquals( parser.solve(eqn)[thermal_zone_id], np.mean( parser.data.thermal.data_frame["temp"]) + 1000)
def test_bool_ops_vector(self): """Test Logical Operations: Vector""" thermal_zone_id = 0 # The equation returns a vector mask parser = Parser(trappy.FTrace()) eqn = "(trappy.thermal.ThermalGovernor:current_temperature > 77000)\ & (trappy.pid_controller.PIDController:output > 2500)" mask = parser.solve(eqn) self.assertEqual(len(parser.ref(mask.dropna()[0])), 0)
def test_accessors_sum(self): """Test Addition And Subtraction: Data""" thermal_zone_id = 0 parser = Parser(trappy.FTrace()) # Equation with dataframe accessors eqn = "trappy.thermal.Thermal:temp + \ trappy.thermal.Thermal:temp" assert_series_equal(parser.solve(eqn)[thermal_zone_id], 2 * parser.data.thermal.data_frame["temp"], check_names=False)
class Analyzer(object): """ :param data: TRAPpy Run Object :type data: :mod:`trappy.run.Run` :param config: A dictionary of variables, classes and functions that can be used in the statements :type config: dict """ def __init__(self, data, config, topology=None): self._parser = Parser(data, config, topology) def assertStatement(self, statement, select=None): """Solve the statement for a boolean result :param statement: A string representing a valid :mod:`trappy.stats.grammar` statement :type statement: str :param select: If the result represents a boolean mask and the data was derived from a TRAPpy event with a pivot value. The :code:`select` can be used to select a particular pivot value :type select: :mod:`pandas.DataFrame` column """ result = self.getStatement(statement, select=select) # pylint: disable=no-member if not (isinstance(result, bool) or isinstance(result, np.bool_)): warnings.warn( "solution of {} is not an instance of bool".format(statement)) return result # pylint: enable=no-member def getStatement(self, statement, reference=False, select=None): """Evaluate the statement""" result = self._parser.solve(statement) # pylint: disable=no-member if np.isscalar(result): return result # pylint: enable=no-member if select is not None and len(result): result = result[select] if reference: result = self._parser.ref(result) return result
def test_windowed_parse(self): """Test that the parser can operate on a window of the trace""" trace = trappy.FTrace() prs = Parser(trace, window=(2, 3)) dfr_res = prs.solve("thermal:temp") self.assertGreater(dfr_res.index[0], 2) self.assertLess(dfr_res.index[-1], 3) prs = Parser(trace, window=(4, None)) dfr_res = prs.solve("thermal:temp") self.assertGreater(dfr_res.index[0], 4) self.assertEquals(dfr_res.index[-1], trace.thermal.data_frame.index[-1]) prs = Parser(trace, window=(0, 1)) dfr_res = prs.solve("thermal:temp") self.assertEquals(dfr_res.index[0], trace.thermal.data_frame.index[0]) self.assertLess(dfr_res.index[-1], 1)
def test_for_parsed_event(self): """Test if an added parsed event can be accessed""" trace = trappy.FTrace(scope="custom") dfr = pandas.DataFrame({"l1_misses": [24, 535, 41], "l2_misses": [155, 11, 200], "cpu": [ 0, 1, 0]}, index=pandas.Series([1.020, 1.342, 1.451], name="Time")) trace.add_parsed_event("pmu_counters", dfr) p = Parser(trace) self.assertTrue(len(p.solve("pmu_counters:cpu")), 3)
def test_windowed_parse(self): """Test that the parser can operate on a window of the trace""" trace = trappy.FTrace() prs = Parser(trace, window=(2, 3)) dfr_res = prs.solve("thermal:temp") self.assertGreater(dfr_res.index[0], 2) self.assertLess(dfr_res.index[-1], 3) prs = Parser(trace, window=(4, None)) dfr_res = prs.solve("thermal:temp") self.assertGreater(dfr_res.index[0], 4) self.assertEqual(dfr_res.index[-1], trace.thermal.data_frame.index[-1]) prs = Parser(trace, window=(0, 1)) dfr_res = prs.solve("thermal:temp") self.assertEqual(dfr_res.index[0], trace.thermal.data_frame.index[0]) self.assertLess(dfr_res.index[-1], 1)
def test_accessors_sum(self): """Test Addition And Subtraction: Data""" thermal_zone_id = 0 parser = Parser(trappy.FTrace()) # Equation with dataframe accessors eqn = "trappy.thermal.Thermal:temp + \ trappy.thermal.Thermal:temp" assert_series_equal( parser.solve(eqn)[thermal_zone_id], 2 * parser.data.thermal.data_frame["temp"], check_names=False)
def test_cls_forward(self): """Test Forwarding: Classes""" cls = trappy.thermal.Thermal pvars = {} pvars["mean"] = np.mean pvars["control_temp"] = 78000 pvars["therm"] = cls thermal_zone_id = 0 parser = Parser(trappy.FTrace(), pvars=pvars) eqn = "mean(therm:temp) < control_temp" self.assertTrue(parser.solve(eqn)[thermal_zone_id])
def test_cls_forward(self): """Test Forwarding: Classes""" cls = trappy.thermal.Thermal pvars = {} pvars["mean"] = np.mean pvars["control_temp"] = 78000 pvars["therm"] = cls thermal_zone_id = 0 parser = Parser(trappy.Run(), pvars=pvars) eqn = "mean(therm:temp) < control_temp" self.assertTrue(parser.solve(eqn)[thermal_zone_id])
def test_for_parsed_event(self): """Test if an added parsed event can be accessed""" trace = trappy.FTrace(scope="custom") dfr = pandas.DataFrame( { "l1_misses": [24, 535, 41], "l2_misses": [155, 11, 200], "cpu": [0, 1, 0] }, index=pandas.Series([1.020, 1.342, 1.451], name="Time")) trace.add_parsed_event("pmu_counters", dfr) p = Parser(trace) self.assertTrue(len(p.solve("pmu_counters:cpu")), 3)
class Analyzer(object): """ Args: data (trappy.Run): A trappy.Run instance config (dict): A dictionary of variables, classes and functions that can be used in the statements """ def __init__(self, data, config, topology=None): self._parser = Parser(data, config, topology) def assertStatement(self, statement, select=None): """Solve the statement for a boolean result""" result = self.getStatement(statement, select=select) # pylint: disable=no-member if not (isinstance(result, bool) or isinstance(result, np.bool_)): warnings.warn( "solution of {} is not an instance of bool".format(statement)) return result # pylint: enable=no-member def getStatement(self, statement, reference=False, select=None): """Evaluate the statement""" result = self._parser.solve(statement) # pylint: disable=no-member if np.isscalar(result): return result # pylint: enable=no-member if select is not None and len(result): result = result[select] if reference: result = self._parser.ref(result) return result
class SignalCompare(object): """ :param data: TRAPpy FTrace Object :type data: :mod:`trappy.ftrace.FTrace` :param sig_a: The first signal :type sig_a: str :param sig_b: The first signal :type sig_b: str :param config: A dictionary of variables, classes and functions that can be used in the statements :type config: dict :param method: The method to be used for reindexing data This can be one of the standard :mod:`pandas.DataFrame` methods (eg. pad, bfill, nearest). The default is pad or use the last valid observation. :type method: str :param limit: The number of indices a value will be propagated when reindexing. The default is None :type limit: int :param fill: Whether to fill the NaNs in the data. The default value is True. :type fill: bool .. note:: Both the signals must have the same pivots. For example: - Signal A has a pivot as :code:`"cpu"` which means that the trappy event (:mod:`trappy.base.Base`) has a pivot parameter which is equal to :code:`"cpu"`. Then the signal B should also have :code:`"cpu"` as it's pivot. - Signal A and B can both have undefined or None as their pivots """ def __init__(self, data, sig_a, sig_b, **kwargs): self._parser = Parser(data, config=kwargs.pop("config", None), **kwargs) self._a = sig_a self._b = sig_b self._pivot_vals, self._pivot = self._get_signal_pivots() # Concatenate the indices by doing any operation (say add) self._a_data = self._parser.solve(sig_a) self._b_data = self._parser.solve(sig_b) def _get_signal_pivots(self): """Internal function to check pivot conditions and return an intersection of pivot on the signals""" sig_a_info = self._parser.inspect(self._a) sig_b_info = self._parser.inspect(self._b) if sig_a_info["pivot"] != sig_b_info["pivot"]: raise RuntimeError("The pivot column for both signals" + "should be same (%s,%s)" % (sig_a_info["pivot"], sig_b_info["pivot"])) if sig_a_info["pivot"]: pivot_vals = set(sig_a_info["pivot_values"]).intersection( sig_b_info["pivot_values"]) pivoted = sig_a_info["pivot"] else: pivot_vals = [StatConf.GRAMMAR_DEFAULT_PIVOT] pivoted = False return pivot_vals, pivoted def conditional_compare(self, condition, **kwargs): """Conditionally compare two signals The conditional comparison of signals has two components: - **Value Coefficient** :math:`\\alpha_{v}` which measures the difference in values of of the two signals when the condition is true: .. math:: \\alpha_{v} = \\frac{area\_under\_curve(S_A\ |\ C(t)\ is\ true)} {area\_under\_curve(S_B\ |\ C(t)\ is\ true)} \\\\ \\alpha_{v} = \\frac{\int S_A(\{t\ |\ C(t)\})dt}{\int S_B(\{t\ |\ C(t)\})dt} - **Time Coefficient** :math:`\\alpha_{t}` which measures the time during which the condition holds true. .. math:: \\alpha_{t} = \\frac{T_{valid}}{T_{total}} :param condition: A condition that returns a truth value and obeys the grammar syntax :: "event_x:sig_a > event_x:sig_b" :type condition: str :param method: The method for area calculation. This can be any of the integration methods supported in `numpy` or `rect` :type param: str :param step: The step behaviour for area and time summation calculation :type step: str Consider the two signals A and B as follows: .. code:: A = [0, 0, 0, 3, 3, 0, 0, 0] B = [0, 0, 2, 2, 2, 2, 1, 1] .. code:: A = xxxx 3 *xxxx*xxxx+ B = ---- | | 2 *----*----*----+ | | | 1 | | *----*----+ | | | 0 *x-x-*x-x-+xxxx+ +xxxx*xxxx+ 0 1 2 3 4 5 6 7 The condition: .. math:: A > B is valid between T=3 and T=5. Therefore, .. math:: \\alpha_v=1.5 \\\\ \\alpha_t=\\frac{2}{7} :returns: There are two cases: - **Pivoted Signals** :: { "pivot_name" : { "pval_1" : (v1,t1), "pval_2" : (v2, t2) } } - **Non Pivoted Signals** The tuple of :math:`(\\alpha_v, \\alpha_t)` """ if self._pivot: result = {self._pivot: {}} mask = self._parser.solve(condition) step = kwargs.get("step", "post") for pivot_val in self._pivot_vals: a_piv = self._a_data[pivot_val] b_piv = self._b_data[pivot_val] area = area_under_curve(a_piv[mask[pivot_val]], **kwargs) try: area /= area_under_curve(b_piv[mask[pivot_val]], **kwargs) except ZeroDivisionError: area = float("nan") duration = min(a_piv.last_valid_index(), b_piv.last_valid_index()) duration -= max(a_piv.first_valid_index(), b_piv.first_valid_index()) duration = interval_sum(mask[pivot_val], step=step) / duration if self._pivot: result[self._pivot][pivot_val] = area, duration else: result = area, duration return result def get_overshoot(self, **kwargs): """Special case for :func:`conditional_compare` where the condition is: :: "sig_a > sig_b" :param method: The method for area calculation. This can be any of the integration methods supported in `numpy` or `rect` :type param: str :param step: The step behaviour for calculation of area and time summation :type step: str .. seealso:: :func:`conditional_compare` """ condition = " ".join([self._a, ">", self._b]) return self.conditional_compare(condition, **kwargs) def get_undershoot(self, **kwargs): """Special case for :func:`conditional_compare` where the condition is: :: "sig_a < sig_b" :param method: The method for area calculation. This can be any of the integration methods supported in `numpy` or `rect` :type param: str :param step: The step behaviour for calculation of area and time summation :type step: str .. seealso:: :func:`conditional_compare` """ condition = " ".join([self._a, "<", self._b]) return self.conditional_compare(condition, **kwargs)
class SignalCompare(object): """ :param data: TRAPpy FTrace Object :type data: :mod:`trappy.ftrace.FTrace` :param sig_a: The first signal :type sig_a: str :param sig_b: The first signal :type sig_b: str :param config: A dictionary of variables, classes and functions that can be used in the statements :type config: dict :param method: The method to be used for reindexing data This can be one of the standard :mod:`pandas.DataFrame` methods (eg. pad, bfill, nearest). The default is pad or use the last valid observation. :type method: str :param limit: The number of indices a value will be propagated when reindexing. The default is None :type limit: int :param fill: Whether to fill the NaNs in the data. The default value is True. :type fill: bool .. note:: Both the signals must have the same pivots. For example: - Signal A has a pivot as :code:`"cpu"` which means that the trappy event (:mod:`trappy.base.Base`) has a pivot parameter which is equal to :code:`"cpu"`. Then the signal B should also have :code:`"cpu"` as it's pivot. - Signal A and B can both have undefined or None as their pivots """ def __init__(self, data, sig_a, sig_b, **kwargs): self._parser = Parser( data, config=kwargs.pop( "config", None), **kwargs) self._a = sig_a self._b = sig_b self._pivot_vals, self._pivot = self._get_signal_pivots() # Concatenate the indices by doing any operation (say add) self._a_data = self._parser.solve(sig_a) self._b_data = self._parser.solve(sig_b) def _get_signal_pivots(self): """Internal function to check pivot conditions and return an intersection of pivot on the signals""" sig_a_info = self._parser.inspect(self._a) sig_b_info = self._parser.inspect(self._b) if sig_a_info["pivot"] != sig_b_info["pivot"]: raise RuntimeError("The pivot column for both signals" + "should be same (%s,%s)" % (sig_a_info["pivot"], sig_b_info["pivot"])) if sig_a_info["pivot"]: pivot_vals = set( sig_a_info["pivot_values"]).intersection(sig_b_info["pivot_values"]) pivoted = sig_a_info["pivot"] else: pivot_vals = [StatConf.GRAMMAR_DEFAULT_PIVOT] pivoted = False return pivot_vals, pivoted def conditional_compare(self, condition, **kwargs): """Conditionally compare two signals The conditional comparison of signals has two components: - **Value Coefficient** :math:`\\alpha_{v}` which measures the difference in values of of the two signals when the condition is true: .. math:: \\alpha_{v} = \\frac{area\_under\_curve(S_A\ |\ C(t)\ is\ true)} {area\_under\_curve(S_B\ |\ C(t)\ is\ true)} \\\\ \\alpha_{v} = \\frac{\int S_A(\{t\ |\ C(t)\})dt}{\int S_B(\{t\ |\ C(t)\})dt} - **Time Coefficient** :math:`\\alpha_{t}` which measures the time during which the condition holds true. .. math:: \\alpha_{t} = \\frac{T_{valid}}{T_{total}} :param condition: A condition that returns a truth value and obeys the grammar syntax :: "event_x:sig_a > event_x:sig_b" :type condition: str :param method: The method for area calculation. This can be any of the integration methods supported in `numpy` or `rect` :type param: str :param step: The step behaviour for area and time summation calculation :type step: str Consider the two signals A and B as follows: .. code:: A = [0, 0, 0, 3, 3, 0, 0, 0] B = [0, 0, 2, 2, 2, 2, 1, 1] .. code:: A = xxxx 3 *xxxx*xxxx+ B = ---- | | 2 *----*----*----+ | | | 1 | | *----*----+ | | | 0 *x-x-*x-x-+xxxx+ +xxxx*xxxx+ 0 1 2 3 4 5 6 7 The condition: .. math:: A > B is valid between T=3 and T=5. Therefore, .. math:: \\alpha_v=1.5 \\\\ \\alpha_t=\\frac{2}{7} :returns: There are two cases: - **Pivoted Signals** :: { "pivot_name" : { "pval_1" : (v1,t1), "pval_2" : (v2, t2) } } - **Non Pivoted Signals** The tuple of :math:`(\\alpha_v, \\alpha_t)` """ if self._pivot: result = {self._pivot: {}} mask = self._parser.solve(condition) step = kwargs.get("step", "post") for pivot_val in self._pivot_vals: a_piv = self._a_data[pivot_val] b_piv = self._b_data[pivot_val] area = area_under_curve(a_piv[mask[pivot_val]], **kwargs) try: area /= area_under_curve(b_piv[mask[pivot_val]], **kwargs) except ZeroDivisionError: area = float("nan") duration = min(a_piv.last_valid_index(), b_piv.last_valid_index()) duration -= max(a_piv.first_valid_index(), b_piv.first_valid_index()) duration = interval_sum(mask[pivot_val], step=step) / duration if self._pivot: result[self._pivot][pivot_val] = area, duration else: result = area, duration return result def get_overshoot(self, **kwargs): """Special case for :func:`conditional_compare` where the condition is: :: "sig_a > sig_b" :param method: The method for area calculation. This can be any of the integration methods supported in `numpy` or `rect` :type param: str :param step: The step behaviour for calculation of area and time summation :type step: str .. seealso:: :func:`conditional_compare` """ condition = " ".join([self._a, ">", self._b]) return self.conditional_compare(condition, **kwargs) def get_undershoot(self, **kwargs): """Special case for :func:`conditional_compare` where the condition is: :: "sig_a < sig_b" :param method: The method for area calculation. This can be any of the integration methods supported in `numpy` or `rect` :type param: str :param step: The step behaviour for calculation of area and time summation :type step: str .. seealso:: :func:`conditional_compare` """ condition = " ".join([self._a, "<", self._b]) return self.conditional_compare(condition, **kwargs)