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"""This module contains the class for plotting and customizing 17Line/Linear Plots with :mod:`trappy.trace.BareTrace` or derived 18classes. This plot only works when run from an IPython notebook 19 20""" 21 22from collections import OrderedDict 23import matplotlib.pyplot as plt 24from trappy.plotter import AttrConf 25from trappy.plotter import Utils 26from trappy.plotter.Constraint import ConstraintManager 27from trappy.plotter.ILinePlotGen import ILinePlotGen 28from trappy.plotter.AbstractDataPlotter import AbstractDataPlotter 29from trappy.plotter.ColorMap import ColorMap 30from trappy.plotter import IPythonConf 31from trappy.utils import handle_duplicate_index 32import pandas as pd 33 34if not IPythonConf.check_ipython(): 35 raise ImportError("Ipython Environment not Found") 36 37class ILinePlot(AbstractDataPlotter): 38 """ 39 This class uses :mod:`trappy.plotter.Constraint.Constraint` to 40 represent different permutations of input parameters. These 41 constraints are generated by creating an instance of 42 :mod:`trappy.plotter.Constraint.ConstraintManager`. 43 44 :param traces: The input data 45 :type traces: a list of :mod:`trappy.trace.FTrace`, 46 :mod:`trappy.trace.SysTrace`, :mod:`trappy.trace.BareTrace` 47 or :mod:`pandas.DataFrame` or a single instance of them. 48 49 :param column: specifies the name of the column to 50 be plotted. 51 :type column: (str, list(str)) 52 53 :param templates: TRAPpy events 54 55 .. note:: 56 57 This is not required if a :mod:`pandas.DataFrame` is 58 used 59 60 :type templates: :mod:`trappy.base.Base` 61 62 :param filters: Filter the column to be plotted as per the 63 specified criteria. For Example: 64 :: 65 66 filters = 67 { 68 "pid": [ 3338 ], 69 "cpu": [0, 2, 4], 70 } 71 :type filters: dict 72 73 :param per_line: Used to control the number of graphs 74 in each graph subplot row 75 :type per_line: int 76 77 :param concat: Draw all the pivots on a single graph 78 :type concat: bool 79 80 :param permute: Draw one plot for each of the traces specified 81 :type permute: bool 82 83 :param fill: Fill the area under the plots 84 :type fill: bool 85 86 :param fill_alpha: Opacity of filled area under the plots. 87 Implies fill=True. 88 :type fill_alpha: float 89 90 :param xlim: A tuple representing the upper and lower xlimits 91 :type xlim: tuple 92 93 :param ylim: A tuple representing the upper and lower ylimits 94 :type ylim: tuple 95 96 :param drawstyle: Set the drawstyle to a matplotlib compatible 97 drawing style. 98 99 .. note:: 100 101 Only "steps-post" is supported as a valid value for 102 the drawstyle. This creates a step plot. 103 104 :type drawstyle: str 105 106 :param sync_zoom: Synchronize the zoom of a group of plots. 107 Zooming in one plot of a group (see below) will zoom in every 108 plot of that group. Defaults to False. 109 :type sync_zoom: boolean 110 111 :param group: Name given to the plots created by this ILinePlot 112 instance. This name is only used for synchronized zoom. If 113 you zoom on any plot in a group all plots will zoom at the 114 same time. 115 :type group: string 116 117 :param signals: A string of the type event_name:column to indicate 118 the value that needs to be plotted. You can add an additional 119 parameter to specify the color of the lin in rgb: 120 "event_name:column:color". The color is specified as a comma 121 separated list of rgb values, from 0 to 255 or from 0x0 to 122 0xff. E.g. 0xff,0x0,0x0 is red and 100,40,32 is brown. 123 124 .. note:: 125 126 - Only one of `signals` or both `templates` and 127 `columns` should be specified 128 - Signals format won't work for :mod:`pandas.DataFrame` 129 input 130 131 :type signals: str 132 """ 133 134 def __init__(self, traces, templates=None, **kwargs): 135 # Default keys, each can be overridden in kwargs 136 self._layout = None 137 super(ILinePlot, self).__init__(traces=traces, 138 templates=templates) 139 140 self.set_defaults() 141 142 for key in kwargs: 143 self._attr[key] = kwargs[key] 144 145 if "signals" in self._attr: 146 self._describe_signals() 147 148 self._check_data() 149 150 if "column" not in self._attr: 151 raise RuntimeError("Value Column not specified") 152 153 if self._attr["drawstyle"] and self._attr["drawstyle"].startswith("steps"): 154 self._attr["step_plot"] = True 155 156 zip_constraints = not self._attr["permute"] 157 158 window = self._attr["xlim"] if "xlim" in self._attr else None 159 160 self.c_mgr = ConstraintManager(traces, self._attr["column"], self.templates, 161 self._attr["pivot"], 162 self._attr["filters"], 163 window=window, 164 zip_constraints=zip_constraints) 165 166 167 def savefig(self, *args, **kwargs): 168 raise NotImplementedError("Not Available for ILinePlot") 169 170 def view(self, max_datapoints=75000, test=False): 171 """Displays the graph 172 173 :param max_datapoints: Maximum number of datapoints to plot. 174 Dygraph can make the browser unresponsive if it tries to plot 175 too many datapoints. Chrome 50 chokes at around 75000 on an 176 i7-4770 @ 3.4GHz, Firefox 47 can handle up to 200000 before 177 becoming too slow in the same machine. You can increase this 178 number if you know what you're doing and are happy to wait for 179 the plot to render. :type max_datapoints: int 180 181 :param test: For testing purposes. Only set to true if run 182 from the testsuite. 183 :type test: boolean 184 """ 185 186 # Defer installation of IPython components 187 # to the .view call to avoid any errors at 188 # when importing the module. This facilitates 189 # the importing of the module from outside 190 # an IPython notebook 191 if not test: 192 IPythonConf.iplot_install("ILinePlot") 193 194 self._attr["max_datapoints"] = max_datapoints 195 196 if self._attr["concat"]: 197 self._plot_concat() 198 else: 199 self._plot(self._attr["permute"], test) 200 201 def set_defaults(self): 202 """Sets the default attrs""" 203 self._attr["per_line"] = AttrConf.PER_LINE 204 self._attr["concat"] = AttrConf.CONCAT 205 self._attr["filters"] = {} 206 self._attr["pivot"] = AttrConf.PIVOT 207 self._attr["permute"] = False 208 self._attr["drawstyle"] = None 209 self._attr["step_plot"] = False 210 self._attr["fill"] = AttrConf.FILL 211 self._attr["scatter"] = AttrConf.PLOT_SCATTER 212 self._attr["point_size"] = AttrConf.POINT_SIZE 213 self._attr["map_label"] = {} 214 self._attr["title"] = AttrConf.TITLE 215 216 def _plot(self, permute, test): 217 """Internal Method called to draw the plot""" 218 pivot_vals, len_pivots = self.c_mgr.generate_pivots(permute) 219 220 self._layout = ILinePlotGen(len_pivots, **self._attr) 221 plot_index = 0 222 for p_val in pivot_vals: 223 data_dict = OrderedDict() 224 for constraint in self.c_mgr: 225 if permute: 226 trace_idx, pivot = p_val 227 if constraint.trace_index != trace_idx: 228 continue 229 legend = constraint._template.name + ":" + constraint.column 230 else: 231 pivot = p_val 232 legend = str(constraint) 233 234 result = constraint.result 235 if pivot in result: 236 data_dict[legend] = result[pivot] 237 238 if permute: 239 title = self.traces[plot_index].name 240 elif pivot != AttrConf.PIVOT_VAL: 241 title = "{0}: {1}".format(self._attr["pivot"], self._attr["map_label"].get(pivot, pivot)) 242 else: 243 title = "" 244 245 if len(data_dict) > 1: 246 data_frame = self._fix_indexes(data_dict) 247 else: 248 data_frame = pd.DataFrame(data_dict) 249 250 self._layout.add_plot(plot_index, data_frame, title, test=test) 251 plot_index += 1 252 253 self._layout.finish() 254 255 def _plot_concat(self): 256 """Plot all lines on a single figure""" 257 258 pivot_vals, _ = self.c_mgr.generate_pivots() 259 plot_index = 0 260 261 self._layout = ILinePlotGen(len(self.c_mgr), **self._attr) 262 263 for constraint in self.c_mgr: 264 result = constraint.result 265 title = str(constraint) 266 data_dict = OrderedDict() 267 268 for pivot in pivot_vals: 269 if pivot in result: 270 if pivot == AttrConf.PIVOT_VAL: 271 key = ",".join(self._attr["column"]) 272 else: 273 key = "{0}: {1}".format(self._attr["pivot"], self._attr["map_label"].get(pivot, pivot)) 274 275 data_dict[key] = result[pivot] 276 277 if len(data_dict) > 1: 278 data_frame = self._fix_indexes(data_dict) 279 else: 280 data_frame = pd.DataFrame(data_dict) 281 282 self._layout.add_plot(plot_index, data_frame, title) 283 plot_index += 1 284 285 self._layout.finish() 286 287 def _fix_indexes(self, data_dict): 288 """ 289 In case of multiple traces with different indexes (i.e. x-axis values), 290 create new ones with same indexes 291 """ 292 # 1) Check if we are processing multiple traces 293 if len(data_dict) <= 1: 294 raise ValueError("Cannot fix indexes for single trace. "\ 295 "Expecting multiple traces!") 296 297 # 2) Merge the data frames to obtain common indexes 298 df_columns = list(data_dict.keys()) 299 dedup_data = [handle_duplicate_index(s) for s in data_dict.values()] 300 ret = pd.Series(dedup_data, index=df_columns) 301 merged_df = pd.concat(ret.get_values(), axis=1) 302 merged_df.columns = df_columns 303 # 3) Fill NaN values depending on drawstyle 304 if self._attr["drawstyle"] == "steps-post": 305 merged_df = merged_df.ffill() 306 elif self._attr["drawstyle"] == "steps-pre": 307 merged_df = merged_df.bfill() 308 elif self._attr["drawstyle"] == "steps-mid": 309 merged_df = merged_df.ffill() 310 else: 311 # default 312 merged_df = merged_df.interpolate() 313 314 return merged_df 315