1 # Copyright 2015-2017 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 The EventPlot is used to represent Events with two characteristics: 18 19 - A name, which determines the colour on the plot 20 - A lane, which determines the lane in which the event occurred 21 22 In the case of a cpu residency plot, the term lane can be equated to 23 a CPU and the name attribute can be the PID of the task 24 """ 25 26 from trappy.plotter import AttrConf 27 import uuid 28 import json 29 import os 30 from trappy.plotter.AbstractDataPlotter import AbstractDataPlotter 31 from trappy.plotter import IPythonConf 32 from collections import defaultdict 33 from copy import deepcopy 34 35 if not IPythonConf.check_ipython(): 36 raise ImportError("Ipython Environment not Found") 37 38 from IPython.display import display, HTML 39 # pylint: disable=R0201 40 # pylint: disable=R0921 41 42 43 class EventPlot(AbstractDataPlotter): 44 """ 45 Input Data should be of the format 46 :: 47 48 { "<name1>" : [ 49 [event_start, event_end, lane], 50 . 51 . 52 [event_start, event_end, lane], 53 ], 54 . 55 . 56 . 57 58 "<nameN>" : [ 59 [event_start, event_end, lane], 60 . 61 . 62 [event_start, event_end, lane], 63 ], 64 } 65 66 :param data: Input Data 67 :type data: dict 68 69 :param keys: List of unique names in the data dictionary 70 :type keys: list 71 72 :param domain: Domain of the event data 73 :type domain: tuple 74 75 :param lane_prefix: A string prefix to be used to name each lane 76 :type lane_prefix: str 77 78 :param num_lanes: Total number of expected lanes 79 :type num_lanes: int 80 81 :param summary: Show a mini plot below the main plot with an 82 overview of where your current view is with respect to the 83 whole trace 84 :type summary: bool 85 86 :param stride: Stride can be used if the trace is very large. 87 It results in sampled rendering 88 :type stride: bool 89 90 :param lanes: The sorted order of lanes 91 :type lanes: list 92 93 :param color_map: A mapping between events and colours 94 :: 95 { "<name1>" : "colour1", 96 . 97 . 98 . 99 "<nameN>" : "colourN" 100 } 101 102 Colour string can be: 103 104 - Colour names (supported colours are listed in 105 https://www.w3.org/TR/SVG/types.html#ColorKeywords) 106 107 - HEX representation of colour, like #FF0000 for "red", #008000 for 108 "green", #0000FF for "blue" and so on 109 110 :type color_map: dict 111 """ 112 113 def __init__( 114 self, 115 data, 116 keys, 117 domain, 118 lane_prefix="Lane: ", 119 num_lanes=0, 120 summary=True, 121 stride=False, 122 lanes=None, 123 color_map=None): 124 125 _data = deepcopy(data) 126 self._html = [] 127 self._fig_name = self._generate_fig_name() 128 # Function to get the average duration of each event 129 avgFunc = lambda x: sum([(evt[1] - evt[0]) for evt in x]) / float(len(x) + 1) 130 avg = {k: avgFunc(v) for k, v in data.iteritems()} 131 # Filter keys with zero average time 132 keys = filter(lambda x : avg[x] != 0, avg) 133 graph = {} 134 graph["lanes"] = self._get_lanes(lanes, lane_prefix, num_lanes, _data) 135 graph["xDomain"] = domain 136 graph["keys"] = sorted(keys, key=lambda x: avg[x], reverse=True) 137 graph["showSummary"] = summary 138 graph["stride"] = AttrConf.EVENT_PLOT_STRIDE 139 graph["colorMap"] = color_map 140 graph["data"] = self._group_data_by_lanes(_data) 141 self._data = json.dumps(graph) 142 143 # Initialize the HTML, CSS and JS Components 144 self._add_css() 145 self._init_html() 146 147 def _group_data_by_lanes(self, data): 148 """Group data by lanes. 149 150 This enables the Javascript code to handle the same event 151 occuring simultaneously in different lanes. 152 """ 153 lane_data = {} 154 for key, value in data.items(): 155 lane_data[key] = defaultdict(list) 156 for tsinfo in value: 157 lane_data[key][tsinfo[2]].append(tsinfo[:2]) 158 return lane_data 159 160 def view(self): 161 """Views the Graph Object""" 162 163 # Defer installation of IPython components 164 # to the .view call to avoid any errors at 165 # when importing the module. This facilitates 166 # the importing of the module from outside 167 # an IPython notebook 168 IPythonConf.iplot_install("EventPlot") 169 display(HTML(self.html())) 170 171 def savefig(self, path): 172 """Save the plot in the provided path 173 174 .. warning:: Not Implemented for :mod:`trappy.plotter.EventPlot` 175 """ 176 177 raise NotImplementedError( 178 "Save is not currently implemented for EventPlot") 179 180 def _get_lanes(self, 181 input_lanes, 182 lane_prefix, 183 num_lanes, 184 data): 185 """Populate the lanes for the plot""" 186 187 # If the user has specified lanes explicitly 188 lanes = [] 189 if input_lanes: 190 lane_map = {} 191 for idx, lane in enumerate(input_lanes): 192 lane_map[lane] = idx 193 194 for name in data: 195 for event in data[name]: 196 lane = event[2] 197 198 try: 199 event[2] = lane_map[lane] 200 except KeyError: 201 raise RuntimeError("Invalid Lane %s" % lane) 202 203 for idx, lane in enumerate(input_lanes): 204 lanes.append({"id": idx, "label": lane}) 205 206 else: 207 208 if not num_lanes: 209 raise RuntimeError("Either lanes or num_lanes must be specified") 210 211 for idx in range(num_lanes): 212 lanes.append({"id": idx, "label": "{}{}".format(lane_prefix, idx)}) 213 214 return lanes 215 216 def _generate_fig_name(self): 217 """Generate a unqiue name for the figure""" 218 219 fig_name = "fig_" + uuid.uuid4().hex 220 return fig_name 221 222 def _init_html(self): 223 """Initialize HTML for the plot""" 224 div_js = '' 225 for url in [IPythonConf.D3_PLOTTER_URL, IPythonConf.D3_TIP_URL]: 226 div_js += '<!-- TRAPPY_PUBLISH_SOURCE_LIB = "{}" -->\n'.format(url) 227 228 div_js += """ 229 <script> 230 /* TRAPPY_PUBLISH_IMPORT = "plotter/js/EventPlot.js" */ 231 /* TRAPPY_PUBLISH_REMOVE_START */ 232 var req = require.config( { 233 234 paths: { 235 236 "EventPlot": '""" + IPythonConf.add_web_base("plotter_scripts/EventPlot/EventPlot") + """', 237 "d3-tip": '""" + IPythonConf.add_web_base("plotter_scripts/EventPlot/d3.tip.v0.6.3") + """', 238 "d3-plotter": '""" + IPythonConf.add_web_base("plotter_scripts/EventPlot/d3.min") + """' 239 }, 240 waitSeconds: 15, 241 shim: { 242 "d3-plotter" : { 243 "exports" : "d3" 244 }, 245 "d3-tip": ["d3-plotter"], 246 "EventPlot": { 247 248 "deps": ["d3-tip", "d3-plotter" ], 249 "exports": "EventPlot" 250 } 251 } 252 }); 253 /* TRAPPY_PUBLISH_REMOVE_STOP */ 254 """ 255 256 div_js += """ 257 req(["require", "EventPlot"], function() { /* TRAPPY_PUBLISH_REMOVE_LINE */ 258 EventPlot.generate('""" + self._fig_name + "', '" + IPythonConf.add_web_base("") + "', " + self._data + """); 259 }); /* TRAPPY_PUBLISH_REMOVE_LINE */ 260 </script> 261 """ 262 263 self._html.append( 264 '<div id="{}" class="eventplot">\n{}</div>'.format(self._fig_name, 265 div_js)) 266 267 def _add_css(self): 268 """Append the CSS to the HTML code generated""" 269 270 base_dir = os.path.dirname(os.path.realpath(__file__)) 271 css_file = os.path.join(base_dir, "css/EventPlot.css") 272 self._html.append("<style>") 273 274 with open(css_file, 'r') as css_fh: 275 self._html += [l[:-1] for l in css_fh.readlines()] 276 277 self._html.append("</style>") 278 279 def html(self): 280 """Return a Raw HTML string for the plot""" 281 282 return "\n".join(self._html) 283