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"""
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 collections import defaultdict
33from copy import deepcopy
34
35if not IPythonConf.check_ipython():
36    raise ImportError("Ipython Environment not Found")
37
38from IPython.display import display, HTML
39# pylint: disable=R0201
40# pylint: disable=R0921
41
42
43class 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