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 17statistics aggregation framework""" 18 19import re 20import inspect 21import trappy 22from bart.sched import functions as sched_funcs 23from bart.sched.SchedAssert import SchedAssert 24from bart.common import Utils 25 26class 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