1# Copyright 2015-2016 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""" 17**Signals** 18 19 - Definition 20 21 A signal is a string representation of a TRAPpy event and the 22 column in the same event. The signal can be of two types: 23 24 - *Pivoted Signal* 25 26 A pivoted signal has a pivot specified in its event class. 27 This means that the signal in the event is a concatenation of different 28 signals which belong to different **pivot** nodes. The analysis for pivoted 29 signals must be done by decomposing them into pivoted signals for each node. 30 31 For example, an even that represents the load of the CPU can be pivoted on 32 :code:`"cpu"` which should be a column in the event's `DataFrame` 33 34 - *Non-Pivoted Signal* 35 36 A non pivoted signal has an event that has no pivot value associated with it. 37 This probably means that signal has one component and can be analysed without 38 decomposing it into smaller signals. 39 40 - Representation 41 42 The following are valid representations of a signal 43 44 - :code:`"event_name:event_column"` 45 - :code:`"trappy.event.class:event_column"` 46 47""" 48 49from trappy.stats.grammar import Parser 50from trappy.stats import StatConf 51from bart.common.Utils import area_under_curve, interval_sum 52 53# pylint: disable=invalid-name 54# pylint: disable=anomalous-backslash-in-string 55 56class SignalCompare(object): 57 58 """ 59 :param data: TRAPpy FTrace Object 60 :type data: :mod:`trappy.ftrace.FTrace` 61 62 :param sig_a: The first signal 63 :type sig_a: str 64 65 :param sig_b: The first signal 66 :type sig_b: str 67 68 :param config: A dictionary of variables, classes 69 and functions that can be used in the statements 70 :type config: dict 71 72 :param method: The method to be used for reindexing data 73 This can be one of the standard :mod:`pandas.DataFrame` 74 methods (eg. pad, bfill, nearest). The default is pad 75 or use the last valid observation. 76 :type method: str 77 78 :param limit: The number of indices a value will be propagated 79 when reindexing. The default is None 80 :type limit: int 81 82 :param fill: Whether to fill the NaNs in the data. 83 The default value is True. 84 :type fill: bool 85 86 .. note:: 87 88 Both the signals must have the same pivots. For example: 89 90 - Signal A has a pivot as :code:`"cpu"` which means that 91 the trappy event (:mod:`trappy.base.Base`) has a pivot 92 parameter which is equal to :code:`"cpu"`. Then the signal B 93 should also have :code:`"cpu"` as it's pivot. 94 95 - Signal A and B can both have undefined or None 96 as their pivots 97 """ 98 99 def __init__(self, data, sig_a, sig_b, **kwargs): 100 101 self._parser = Parser( 102 data, 103 config=kwargs.pop( 104 "config", 105 None), 106 **kwargs) 107 self._a = sig_a 108 self._b = sig_b 109 self._pivot_vals, self._pivot = self._get_signal_pivots() 110 111 # Concatenate the indices by doing any operation (say add) 112 self._a_data = self._parser.solve(sig_a) 113 self._b_data = self._parser.solve(sig_b) 114 115 def _get_signal_pivots(self): 116 """Internal function to check pivot conditions and 117 return an intersection of pivot on the signals""" 118 119 sig_a_info = self._parser.inspect(self._a) 120 sig_b_info = self._parser.inspect(self._b) 121 122 if sig_a_info["pivot"] != sig_b_info["pivot"]: 123 raise RuntimeError("The pivot column for both signals" + 124 "should be same (%s,%s)" 125 % (sig_a_info["pivot"], sig_b_info["pivot"])) 126 127 if sig_a_info["pivot"]: 128 pivot_vals = set( 129 sig_a_info["pivot_values"]).intersection(sig_b_info["pivot_values"]) 130 pivoted = sig_a_info["pivot"] 131 else: 132 pivot_vals = [StatConf.GRAMMAR_DEFAULT_PIVOT] 133 pivoted = False 134 135 return pivot_vals, pivoted 136 137 def conditional_compare(self, condition, **kwargs): 138 """Conditionally compare two signals 139 140 The conditional comparison of signals has two components: 141 142 - **Value Coefficient** :math:`\\alpha_{v}` which measures the difference in values of 143 of the two signals when the condition is true: 144 145 .. math:: 146 147 \\alpha_{v} = \\frac{area\_under\_curve(S_A\ |\ C(t)\ is\ true)} 148 {area\_under\_curve(S_B\ |\ C(t)\ is\ true)} \\\\ 149 150 \\alpha_{v} = \\frac{\int S_A(\{t\ |\ C(t)\})dt}{\int S_B(\{t\ |\ C(t)\})dt} 151 152 - **Time Coefficient** :math:`\\alpha_{t}` which measures the time during which the 153 condition holds true. 154 155 .. math:: 156 157 \\alpha_{t} = \\frac{T_{valid}}{T_{total}} 158 159 :param condition: A condition that returns a truth value and obeys the grammar syntax 160 :: 161 162 "event_x:sig_a > event_x:sig_b" 163 164 :type condition: str 165 166 :param method: The method for area calculation. This can 167 be any of the integration methods supported in `numpy` 168 or `rect` 169 :type param: str 170 171 :param step: The step behaviour for area and time 172 summation calculation 173 :type step: str 174 175 Consider the two signals A and B as follows: 176 177 .. code:: 178 179 A = [0, 0, 0, 3, 3, 0, 0, 0] 180 B = [0, 0, 2, 2, 2, 2, 1, 1] 181 182 183 .. code:: 184 185 186 A = xxxx 187 3 *xxxx*xxxx+ B = ---- 188 | | 189 2 *----*----*----+ 190 | | | 191 1 | | *----*----+ 192 | | | 193 0 *x-x-*x-x-+xxxx+ +xxxx*xxxx+ 194 0 1 2 3 4 5 6 7 195 196 The condition: 197 198 .. math:: 199 200 A > B 201 202 is valid between T=3 and T=5. Therefore, 203 204 .. math:: 205 206 \\alpha_v=1.5 \\\\ 207 \\alpha_t=\\frac{2}{7} 208 209 :returns: There are two cases: 210 211 - **Pivoted Signals** 212 :: 213 214 { 215 "pivot_name" : { 216 "pval_1" : (v1,t1), 217 "pval_2" : (v2, t2) 218 } 219 } 220 - **Non Pivoted Signals** 221 222 The tuple of :math:`(\\alpha_v, \\alpha_t)` 223 """ 224 225 if self._pivot: 226 result = {self._pivot: {}} 227 228 mask = self._parser.solve(condition) 229 step = kwargs.get("step", "post") 230 231 for pivot_val in self._pivot_vals: 232 233 a_piv = self._a_data[pivot_val] 234 b_piv = self._b_data[pivot_val] 235 236 area = area_under_curve(a_piv[mask[pivot_val]], **kwargs) 237 try: 238 area /= area_under_curve(b_piv[mask[pivot_val]], **kwargs) 239 except ZeroDivisionError: 240 area = float("nan") 241 242 duration = min(a_piv.last_valid_index(), b_piv.last_valid_index()) 243 duration -= max(a_piv.first_valid_index(), 244 b_piv.first_valid_index()) 245 duration = interval_sum(mask[pivot_val], step=step) / duration 246 247 if self._pivot: 248 result[self._pivot][pivot_val] = area, duration 249 else: 250 result = area, duration 251 252 return result 253 254 def get_overshoot(self, **kwargs): 255 """Special case for :func:`conditional_compare` 256 where the condition is: 257 :: 258 259 "sig_a > sig_b" 260 261 :param method: The method for area calculation. This can 262 be any of the integration methods supported in `numpy` 263 or `rect` 264 :type param: str 265 266 :param step: The step behaviour for calculation of area 267 and time summation 268 :type step: str 269 270 .. seealso:: 271 272 :func:`conditional_compare` 273 """ 274 275 condition = " ".join([self._a, ">", self._b]) 276 return self.conditional_compare(condition, **kwargs) 277 278 def get_undershoot(self, **kwargs): 279 """Special case for :func:`conditional_compare` 280 where the condition is: 281 :: 282 283 "sig_a < sig_b" 284 285 :param method: The method for area calculation. This can 286 be any of the integration methods supported in `numpy` 287 or `rect` 288 :type param: str 289 290 :param step: The step behaviour for calculation of area 291 and time summation 292 :type step: str 293 294 .. seealso:: 295 296 :func:`conditional_compare` 297 """ 298 299 condition = " ".join([self._a, "<", self._b]) 300 return self.conditional_compare(condition, **kwargs) 301