| # Copyright 2015-2016 ARM Limited |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| |
| """ |
| **Signals** |
| |
| - Definition |
| |
| A signal is a string representation of a TRAPpy event and the |
| column in the same event. The signal can be of two types: |
| |
| - *Pivoted Signal* |
| |
| A pivoted signal has a pivot specified in its event class. |
| This means that the signal in the event is a concatenation of different |
| signals which belong to different **pivot** nodes. The analysis for pivoted |
| signals must be done by decomposing them into pivoted signals for each node. |
| |
| For example, an even that represents the load of the CPU can be pivoted on |
| :code:`"cpu"` which should be a column in the event's `DataFrame` |
| |
| - *Non-Pivoted Signal* |
| |
| A non pivoted signal has an event that has no pivot value associated with it. |
| This probably means that signal has one component and can be analysed without |
| decomposing it into smaller signals. |
| |
| - Representation |
| |
| The following are valid representations of a signal |
| |
| - :code:`"event_name:event_column"` |
| - :code:`"trappy.event.class:event_column"` |
| |
| """ |
| |
| from trappy.stats.grammar import Parser |
| from trappy.stats import StatConf |
| from bart.common.Utils import area_under_curve, interval_sum |
| |
| # pylint: disable=invalid-name |
| # pylint: disable=anomalous-backslash-in-string |
| |
| 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) |