Constraint.py revision 5122084171b205379ed288e9dd45b0ad116d2ead
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"""This module provides the Constraint class for handling
17filters and pivots in a modular fashion. This enable easy
18constraint application.
19
20An implementation of :mod:`trappy.plotter.AbstractDataPlotter`
21is expected to use the :mod:`trappy.plotter.Constraint.ConstraintManager`
22class to pivot and filter data and handle multiple column,
23trace and event inputs.
24
25The underlying object that encapsulates a unique set of
26a data column, data event and the requisite filters is
27:mod:`trappy.plotter.Constraint.Constraint`
28"""
29# pylint: disable=R0913
30from trappy.plotter.Utils import decolonize, normalize_list
31from trappy.utils import listify
32from trappy.plotter import AttrConf
33
34
35class Constraint(object):
36
37    """
38    What is a Constraint?
39        It is collection of data based on two rules:
40
41        - A Pivot
42
43        - A Set of Filters
44
45        - A Data Column
46
47    For Example a :mod:`pandas.DataFrame`
48
49    =====  ======== =========
50    Time    CPU       Latency
51    =====  ======== =========
52    1       x           <val>
53    2       y           <val>
54    3       z           <val>
55    4       a           <val>
56    =====  ======== =========
57
58    The resultant data will be split for each unique pivot value
59    with the filters applied
60    ::
61
62        result["x"] = pd.Series.filtered()
63        result["y"] = pd.Series.filtered()
64        result["z"] = pd.Series.filtered()
65        result["a"] = pd.Series.filtered()
66
67
68    :param trappy_trace: Input Data
69    :type trappy_trace: :mod:`pandas.DataFrame` or a class derived from
70        :mod:`trappy.trace.BareTrace`
71
72    :param column: The data column
73    :type column: str
74
75    :param template: TRAPpy Event
76    :type template: :mod:`trappy.base.Base` event
77
78    :param trace_index: The index of the trace/data in the overall constraint
79        data
80    :type trace_index: int
81
82    :param filters: A dictionary of filter values
83    :type filters: dict
84    """
85
86    def __init__(self, trappy_trace, pivot, column, template, trace_index,
87                 filters):
88        self._trappy_trace = trappy_trace
89        self._filters = filters
90        self._pivot = pivot
91        self.column = column
92        self._template = template
93        self._dup_resolved = False
94        self._data = self.populate_data_frame()
95
96        self.result = self._apply()
97        self.trace_index = trace_index
98
99    def _apply(self):
100        """This method applies the filter on the resultant data
101        on the input column.
102        """
103        data = self._data
104        result = {}
105
106        try:
107            values = data[self.column]
108        except KeyError:
109            return result
110
111        if self._pivot == AttrConf.PIVOT:
112            pivot_vals = [AttrConf.PIVOT_VAL]
113        else:
114            pivot_vals = self.pivot_vals(data)
115
116        for pivot_val in pivot_vals:
117            criterion = values.map(lambda x: True)
118
119            for key in self._filters.keys():
120                if key != self._pivot and key in data.columns:
121                    criterion = criterion & data[key].map(
122                        lambda x: x in self._filters[key])
123
124            if pivot_val != AttrConf.PIVOT_VAL:
125                criterion &= data[self._pivot] == pivot_val
126
127            val_series = values[criterion]
128            if len(val_series) != 0:
129                result[pivot_val] = val_series
130
131        return result
132
133    def _uses_trappy_trace(self):
134        if not self._template:
135            return False
136        else:
137            return True
138
139    def populate_data_frame(self):
140        """Return the populated :mod:`pandas.DataFrame`"""
141        if not self._uses_trappy_trace():
142            return self._trappy_trace
143
144        data_container = getattr(
145            self._trappy_trace,
146            decolonize(self._template.name))
147        return data_container.data_frame
148
149    def pivot_vals(self, data):
150        """This method returns the unique pivot values for the
151        Constraint's pivot and the column
152
153        :param data: Input Data
154        :type data: :mod:`pandas.DataFrame`
155        """
156        if self._pivot == AttrConf.PIVOT:
157            return AttrConf.PIVOT_VAL
158
159        if self._pivot not in data.columns:
160            return []
161
162        pivot_vals = set(data[self._pivot])
163        if self._pivot in self._filters:
164            pivot_vals = pivot_vals & set(self._filters[self._pivot])
165
166        return list(pivot_vals)
167
168    def __str__(self):
169
170        name = self.get_data_name()
171
172        if not self._uses_trappy_trace():
173            return name + ":" + self.column
174
175        return name + ":" + \
176            self._template.name + ":" + self.column
177
178
179    def get_data_name(self):
180        """Get name for the data member. This method
181        relies on the "name" attribute for the name.
182        If the name attribute is absent, it associates
183        a numeric name to the respective data element
184
185        :returns: The name of the data member
186        """
187        if self._uses_trappy_trace():
188            if self._trappy_trace.name != "":
189                return self._trappy_trace.name
190            else:
191                return "Trace {}".format(self.trace_index)
192        else:
193            return "DataFrame {}".format(self.trace_index)
194
195class ConstraintManager(object):
196
197    """A class responsible for converting inputs
198    to constraints and also ensuring sanity
199
200
201    :param traces: Input Trace data
202    :type traces: :mod:`trappy.trace.BareTrace`, list(:mod:`trappy.trace.BareTrace`)
203        (or a class derived from :mod:`trappy.trace.BareTrace`)
204    :param columns: The column values from the corresponding
205        :mod:`pandas.DataFrame`
206    :type columns: str, list(str)
207    :param pivot: The column around which the data will be
208        pivoted:
209    :type pivot: str
210    :param filters: A dictionary of values to be applied on the
211        respective columns
212    :type filters: dict
213    :param zip_constraints: Permutes the columns and traces instead
214        of a one-to-one correspondence
215    :type zip_constraints: bool
216    """
217
218    def __init__(self, traces, columns, templates, pivot, filters,
219                 zip_constraints=True):
220
221        self._ip_vec = []
222        self._ip_vec.append(listify(traces))
223        self._ip_vec.append(listify(columns))
224        self._ip_vec.append(listify(templates))
225
226        self._lens = map(len, self._ip_vec)
227        self._max_len = max(self._lens)
228        self._pivot = pivot
229        self._filters = filters
230        self._constraints = []
231
232        self._trace_expanded = False
233        self._expand()
234        if zip_constraints:
235            self._populate_zip_constraints()
236        else:
237            self._populate_constraints()
238
239    def _expand(self):
240        """This is really important. We need to
241        meet the following criteria for constraint
242        expansion:
243        ::
244
245            Len[traces] == Len[columns] == Len[templates]
246
247        Or:
248        ::
249
250            Permute(
251                Len[traces] = 1
252                Len[columns] = 1
253                Len[templates] != 1
254            )
255
256            Permute(
257                   Len[traces] = 1
258                   Len[columns] != 1
259                   Len[templates] != 1
260            )
261        """
262        min_len = min(self._lens)
263        max_pos_comp = [
264            i for i,
265            j in enumerate(
266                self._lens) if j != self._max_len]
267
268        if self._max_len == 1 and min_len != 1:
269            raise RuntimeError("Essential Arg Missing")
270
271        if self._max_len > 1:
272
273            # Are they all equal?
274            if len(set(self._lens)) == 1:
275                return
276
277            if min_len > 1:
278                raise RuntimeError("Cannot Expand a list of Constraints")
279
280            for val in max_pos_comp:
281                if val == 0:
282                    self._trace_expanded = True
283                self._ip_vec[val] = normalize_list(self._max_len,
284                                                   self._ip_vec[val])
285
286    def _populate_constraints(self):
287        """Populate the constraints creating one for each column in
288        each trace
289
290        In a multi-trace, multicolumn scenario, constraints are created for
291        all the columns in each of the traces.  _populate_constraints()
292        creates one constraint for the first trace and first column, the
293        next for the second trace and second column,...  This function
294        creates a constraint for every combination of traces and columns
295        possible.
296        """
297
298        for trace_idx, trace in enumerate(self._ip_vec[0]):
299            for col in self._ip_vec[1]:
300                template = self._ip_vec[2][trace_idx]
301                constraint = Constraint(trace, self._pivot, col, template,
302                                        trace_idx, self._filters)
303                self._constraints.append(constraint)
304
305    def get_column_index(self, constraint):
306        return self._ip_vec[1].index(constraint.column)
307
308    def _populate_zip_constraints(self):
309        """Populate the expanded constraints
310
311        In a multitrace, multicolumn scenario, create constraints for
312        the first trace and the first column, second trace and second
313        column,... that is, as if you run zip(traces, columns)
314        """
315
316        for idx in range(self._max_len):
317            if self._trace_expanded:
318                trace_idx = 0
319            else:
320                trace_idx = idx
321
322            trace = self._ip_vec[0][idx]
323            col = self._ip_vec[1][idx]
324            template = self._ip_vec[2][idx]
325            self._constraints.append(
326                Constraint(trace, self._pivot, col, template, trace_idx,
327                           self._filters))
328
329    def generate_pivots(self, permute=False):
330        """Return a union of the pivot values
331
332        :param permute: Permute the Traces and Columns
333        :type permute: bool
334        """
335        pivot_vals = []
336        for constraint in self._constraints:
337            pivot_vals += constraint.result.keys()
338
339        p_list = list(set(pivot_vals))
340        traces = range(self._lens[0])
341
342        try:
343            sorted_plist = sorted(p_list, key=int)
344        except (ValueError, TypeError):
345            try:
346                sorted_plist = sorted(p_list, key=lambda x: int(x, 16))
347            except (ValueError, TypeError):
348                sorted_plist = sorted(p_list)
349
350        if permute:
351            pivot_gen = ((trace_idx, pivot) for trace_idx in traces for pivot in sorted_plist)
352            return pivot_gen, len(sorted_plist) * self._lens[0]
353        else:
354            return sorted_plist, len(sorted_plist)
355
356    def constraint_labels(self):
357        """
358        :return: string to represent the
359            set of Constraints
360
361        """
362        return map(str, self._constraints)
363
364    def __len__(self):
365        return len(self._constraints)
366
367    def __iter__(self):
368        return iter(self._constraints)
369