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