Home | History | Annotate | Download | only in sched
      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 """A library for asserting scheduler scenarios based on the
     17 statistics aggregation framework"""
     18 
     19 import re
     20 import inspect
     21 import trappy
     22 from bart.sched import functions as sched_funcs
     23 from bart.sched.SchedAssert import SchedAssert
     24 from bart.common import Utils
     25 
     26 class SchedMultiAssert(object):
     27     """This is vector assertion class built on top of
     28     :mod:`bart.sched.SchedAssert.SchedAssert`
     29 
     30     :param ftrace: A single trappy.FTrace object
     31         or a path that can be passed to trappy.FTrace
     32     :type ftrace: :mod:`trappy.ftrace.FTrace`
     33 
     34     :param topology: A topology that describes the arrangement of
     35         CPU's on a system. This is useful for multi-cluster systems
     36         where data needs to be aggregated at different topological
     37         levels
     38     :type topology: :mod:`trappy.stats.Topology.Topology`
     39 
     40     :param execnames: The execnames of the task to be analysed
     41 
     42         A single execname or a list of execnames can be passed.
     43         There can be multiple processes associated with a single
     44         execname parameter. The execnames are searched using a prefix
     45         match.
     46     :type execname: list, str
     47 
     48     :param pids: The process IDs of the tasks to be analysed
     49     :type pids: list, int
     50 
     51     Consider the following processes which need to be analysed
     52 
     53         ===== ==============
     54          PID    execname
     55         ===== ==============
     56          11     task_1
     57          22     task_2
     58          33     task_3
     59         ===== ==============
     60 
     61     A :mod:`bart.sched.SchedMultiAssert.SchedMultiAssert` instance be created
     62     following different ways:
     63 
     64         - Using execname prefix match
     65           ::
     66 
     67             SchedMultiAssert(ftrace, topology, execnames="task_")
     68 
     69         - Individual Task names
     70           ::
     71 
     72             SchedMultiAssert(ftrace, topology, execnames=["task_1", "task_2", "task_3"])
     73 
     74         - Using Process IDs
     75           ::
     76 
     77             SchedMultiAssert(ftrace, topology, pids=[11, 22, 33])
     78 
     79 
     80     All the functionality provided in :mod:`bart.sched.SchedAssert.SchedAssert` is available
     81     in this class with the addition of handling vector assertions.
     82 
     83     For example consider the use of :func:`getDutyCycle`
     84     ::
     85 
     86         >>> s = SchedMultiAssert(ftrace, topology, execnames="task_")
     87         >>> s.getDutyCycle(window=(start, end))
     88         {
     89             "11": {
     90                 "task_name": "task_1",
     91                 "dutycycle": 10.0
     92             },
     93             "22": {
     94                 "task_name": "task_2",
     95                 "dutycycle": 20.0
     96             },
     97             "33": {
     98                 "task_name": "task_3",
     99                 "dutycycle": 30.0
    100             },
    101         }
    102 
    103     The assertions can be used in a similar way
    104     ::
    105 
    106         >>> import operator as op
    107         >>> s = SchedMultiAssert(ftrace, topology, execnames="task_")
    108         >>> s.assertDutyCycle(15, op.ge, window=(start, end))
    109         {
    110             "11": {
    111                 "task_name": "task_1",
    112                 "dutycycle": False
    113             },
    114             "22": {
    115                 "task_name": "task_2",
    116                 "dutycycle": True
    117             },
    118             "33": {
    119                 "task_name": "task_3",
    120                 "dutycycle": True
    121             },
    122         }
    123 
    124     The above result can be coalesced using a :code:`rank` parameter
    125     As we know that only 2 processes have duty cycles greater than 15%
    126     we can do the following:
    127     ::
    128 
    129         >>> import operator as op
    130         >>> s = SchedMultiAssert(ftrace, topology, execnames="task_")
    131         >>> s.assertDutyCycle(15, op.ge, window=(start, end), rank=2)
    132         True
    133 
    134     See :mod:`bart.sched.SchedAssert.SchedAssert` for the available
    135     functionality
    136     """
    137 
    138     def __init__(self, ftrace, topology, execnames=None, pids=None):
    139 
    140         self._ftrace = Utils.init_ftrace(ftrace)
    141         self._topology = topology
    142 
    143         if execnames and pids:
    144             raise ValueError('Either pids or execnames must be specified')
    145         if execnames:
    146             self._execnames = Utils.listify(execnames)
    147             self._pids = self._populate_pids()
    148         elif pids:
    149             self._pids = pids
    150         else:
    151             raise ValueError('One of PIDs or execnames must be specified')
    152 
    153         self._asserts = self._populate_asserts()
    154         self._populate_methods()
    155 
    156     def _populate_asserts(self):
    157         """Populate SchedAsserts for the PIDs"""
    158 
    159         asserts = {}
    160 
    161         for pid in self._pids:
    162             asserts[pid] = SchedAssert(self._ftrace, self._topology, pid=pid)
    163 
    164         return asserts
    165 
    166     def _populate_pids(self):
    167         """Map the input execnames to PIDs"""
    168 
    169         if len(self._execnames) == 1:
    170             return sched_funcs.get_pids_for_process(self._ftrace, self._execnames[0])
    171 
    172         pids = []
    173 
    174         for proc in self._execnames:
    175             pids += sched_funcs.get_pids_for_process(self._ftrace, proc)
    176 
    177         return list(set(pids))
    178 
    179     def _create_method(self, attr_name):
    180         """A wrapper function to create a dispatch function"""
    181 
    182         return lambda *args, **kwargs: self._dispatch(attr_name, *args, **kwargs)
    183 
    184     def _populate_methods(self):
    185         """Populate Methods from SchedAssert"""
    186 
    187         for attr_name in dir(SchedAssert):
    188             attr = getattr(SchedAssert, attr_name)
    189 
    190             valid_method = attr_name.startswith("get") or \
    191                            attr_name.startswith("assert")
    192             if inspect.ismethod(attr) and valid_method:
    193                 func = self._create_method(attr_name)
    194                 setattr(self, attr_name, func)
    195 
    196     def get_task_name(self, pid):
    197         """Get task name for the PID"""
    198         return self._asserts[pid].execname
    199 
    200 
    201     def _dispatch(self, func_name, *args, **kwargs):
    202         """The dispatch function to call into the SchedAssert
    203            Method
    204         """
    205 
    206         assert_func = func_name.startswith("assert")
    207         num_true = 0
    208 
    209         rank = kwargs.pop("rank", None)
    210         result = kwargs.pop("result", {})
    211         param = kwargs.pop("param", re.sub(r"assert|get", "", func_name, count=1).lower())
    212 
    213         for pid in self._pids:
    214 
    215             if pid not in result:
    216                 result[pid] = {}
    217                 result[pid]["task_name"] = self.get_task_name(pid)
    218 
    219             attr = getattr(self._asserts[pid], func_name)
    220             result[pid][param] = attr(*args, **kwargs)
    221 
    222             if assert_func and result[pid][param]:
    223                 num_true += 1
    224 
    225         if assert_func and rank:
    226             return num_true == rank
    227         else:
    228             return result
    229 
    230     def getCPUBusyTime(self, level, node, window=None, percent=False):
    231         """Get the amount of time the cpus in the system were busy executing the
    232         tasks
    233 
    234         :param level: The topological level to which the group belongs
    235         :type level: string
    236 
    237         :param node: The group of CPUs for which to calculate busy time
    238         :type node: list
    239 
    240         :param window: A (start, end) tuple to limit the scope of the
    241         calculation.
    242         :type window: tuple
    243 
    244         :param percent: If True the result is normalized to the total
    245         time of the period, either the window or the full lenght of
    246         the trace.
    247         :type percent: bool
    248 
    249         .. math::
    250 
    251             R = \\frac{T_{busy} \\times 100}{T_{total}}
    252 
    253         """
    254         residencies = self.getResidency(level, node, window=window)
    255 
    256         busy_time = sum(v["residency"] for v in residencies.itervalues())
    257 
    258         if percent:
    259             if window:
    260                 total_time = window[1] - window[0]
    261             else:
    262                 total_time = self._ftrace.get_duration()
    263             num_cpus = len(node)
    264             return busy_time / (total_time * num_cpus) * 100
    265         else:
    266             return busy_time
    267 
    268     def generate_events(self, level, window=None):
    269         """Generate Events for the trace plot
    270 
    271         .. note::
    272             This is an internal function for plotting data
    273         """
    274 
    275         events = {}
    276         for s_assert in self._asserts.values():
    277             events[s_assert.name] = s_assert.generate_events(level, window=window)
    278 
    279         return events
    280 
    281     def plot(self, level="cpu", window=None, xlim=None):
    282         """
    283         :return: :mod:`trappy.plotter.AbstractDataPlotter` instance
    284             Call :func:`view` to draw the graph
    285         """
    286 
    287         if not xlim:
    288             if not window:
    289                 xlim = [0, self._ftrace.get_duration()]
    290             else:
    291                 xlim = list(window)
    292 
    293         events = self.generate_events(level, window)
    294         names = [s.name for s in self._asserts.values()]
    295         num_lanes = self._topology.level_span(level)
    296         lane_prefix = level.upper() + ": "
    297         return trappy.EventPlot(events, names, xlim,
    298                                 lane_prefix=lane_prefix,
    299                                 num_lanes=num_lanes)
    300