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"""Small functions to help with plots""" 17 18# pylint disable=star-args 19 20from matplotlib import pyplot as plt 21import os 22import re 23 24from trappy.wa import SysfsExtractor 25 26GOLDEN_RATIO = 1.618034 27 28def normalize_title(title, opt_title): 29 """Return a string with that contains the title and opt_title if it's 30not the empty string 31 32 See test_normalize_title() for usage 33 34 """ 35 if opt_title is not "": 36 title = opt_title + " - " + title 37 38 return title 39 40def set_lim(lim, get_lim_f, set_lim_f): 41 """Set x or y limitis of the plot 42 43 lim can be a tuple containing the limits or the string "default" 44 or "range". "default" does nothing and uses matplotlib default. 45 "range" extends the current margin by 10%. This is useful since 46 the default xlim and ylim of the plots sometimes make it harder to 47 see data that is just in the margin. 48 49 """ 50 if lim == "default": 51 return 52 53 if lim == "range": 54 cur_lim = get_lim_f() 55 lim = (cur_lim[0] - 0.1 * (cur_lim[1] - cur_lim[0]), 56 cur_lim[1] + 0.1 * (cur_lim[1] - cur_lim[0])) 57 58 set_lim_f(lim[0], lim[1]) 59 60def set_xlim(ax, xlim): 61 """Set the xlim of the plot 62 63 See set_lim() for the details 64 """ 65 set_lim(xlim, ax.get_xlim, ax.set_xlim) 66 67def set_ylim(ax, ylim): 68 """Set the ylim of the plot 69 70 See set_lim() for the details 71 """ 72 set_lim(ylim, ax.get_ylim, ax.set_ylim) 73 74def pre_plot_setup(width=None, height=None, ncols=1, nrows=1): 75 """initialize a figure 76 77 width and height are the height and width of each row of plots. 78 For 1x1 plots, that's the height and width of the plot. This 79 function should be called before any calls to plot() 80 81 """ 82 83 if height is None: 84 if width is None: 85 height = 6 86 width = 10 87 else: 88 height = width / GOLDEN_RATIO 89 else: 90 if width is None: 91 width = height * GOLDEN_RATIO 92 93 height *= nrows 94 95 _, axis = plt.subplots(ncols=ncols, nrows=nrows, figsize=(width, height)) 96 97 # Needed for multirow blots to not overlap with each other 98 plt.tight_layout(h_pad=3.5) 99 100 return axis 101 102def post_plot_setup(ax, title="", xlabel=None, ylabel=None, xlim="default", 103 ylim="range"): 104 """Set xlabel, ylabel title, xlim and ylim of the plot 105 106 This has to be called after calls to .plot(). The default ylim is 107 to extend it by 10% because matplotlib default makes it hard 108 values that are close to the margins 109 110 """ 111 112 if xlabel is not None: 113 ax.set_xlabel(xlabel) 114 115 if ylabel is not None: 116 ax.set_ylabel(ylabel) 117 118 if title: 119 ax.set_title(title) 120 121 set_ylim(ax, ylim) 122 set_xlim(ax, xlim) 123 124def number_freq_plots(runs, map_label): 125 """Calculate the number of plots needed for allfreq plots and frequency 126 histogram plots 127 128 """ 129 num_cpu_plots = len(map_label) 130 131 has_devfreq_data = False 132 for run in runs: 133 if len(run.devfreq_in_power.data_frame) > 0: 134 has_devfreq_data = True 135 break 136 137 num_freq_plots = num_cpu_plots 138 if has_devfreq_data: 139 num_freq_plots += 1 140 141 return num_freq_plots 142 143def plot_temperature(runs, width=None, height=None, ylim="range", tz_id=None): 144 """Plot temperatures 145 146 runs is an array of FTrace() instances. Extract the control_temp 147 from the governor data and plot the temperatures reported by the 148 thermal framework. The governor doesn't track temperature when 149 it's off, so the thermal framework trace is more reliable. 150 151 """ 152 153 ax = pre_plot_setup(width, height) 154 155 for run in runs: 156 gov_dfr = run.thermal_governor.data_frame 157 if tz_id: 158 gov_dfr = gov_dfr[gov_dfr["thermal_zone_id"] == tz_id] 159 160 try: 161 current_temp = gov_dfr["current_temperature"] 162 delta_temp = gov_dfr["delta_temperature"] 163 control_series = (current_temp + delta_temp) / 1000 164 except KeyError: 165 control_series = None 166 167 try: 168 run.thermal.plot_temperature(control_temperature=control_series, 169 ax=ax, legend_label=run.name, 170 tz_id=tz_id) 171 except ValueError: 172 run.thermal_governor.plot_temperature(ax=ax, legend_label=run.name) 173 174 post_plot_setup(ax, title="Temperature", ylim=ylim) 175 plt.legend(loc="best") 176 177def plot_hist(data, ax, title, unit, bins, xlabel, xlim, ylim): 178 """Plot a histogram""" 179 180 mean = data.mean() 181 std = data.std() 182 title += " (mean = {:.2f}{}, std = {:.2f})".format(mean, unit, std) 183 xlabel += " ({})".format(unit) 184 185 data.hist(ax=ax, bins=bins) 186 post_plot_setup(ax, title=title, xlabel=xlabel, ylabel="count", xlim=xlim, 187 ylim=ylim) 188 189def plot_load(runs, map_label, width=None, height=None): 190 """Make a multiplot of all the loads""" 191 num_runs = len(runs) 192 axis = pre_plot_setup(width=width, height=height, ncols=num_runs, nrows=2) 193 194 if num_runs == 1: 195 axis = [axis] 196 else: 197 axis = zip(*axis) 198 199 for ax, run in zip(axis, runs): 200 run.plot_load(map_label, title=run.name, ax=ax[0]) 201 run.plot_normalized_load(map_label, title=run.name, ax=ax[1]) 202 203def plot_allfreqs(runs, map_label, width=None, height=None): 204 """Make a multicolumn plots of the allfreqs plots of each run""" 205 num_runs = len(runs) 206 nrows = number_freq_plots(runs, map_label) 207 208 axis = pre_plot_setup(width=width, height=height, nrows=nrows, 209 ncols=num_runs) 210 211 if num_runs == 1: 212 if nrows == 1: 213 axis = [[axis]] 214 else: 215 axis = [axis] 216 elif nrows == 1: 217 axis = [[ax] for ax in axis] 218 else: 219 axis = zip(*axis) 220 221 for ax, run in zip(axis, runs): 222 run.plot_allfreqs(map_label, ax=ax) 223 224def plot_controller(runs, width=None, height=None): 225 """Make a multicolumn plot of the pid controller of each run""" 226 num_runs = len(runs) 227 axis = pre_plot_setup(width=width, height=height, ncols=num_runs) 228 229 if num_runs == 1: 230 axis = [axis] 231 232 for ax, run in zip(axis, runs): 233 run.pid_controller.plot_controller(title=run.name, ax=ax) 234 235def plot_weighted_input_power(runs, actor_order, width=None, height=None): 236 """Make a multicolumn plot of the weighted input power of each run""" 237 238 actor_weights = [] 239 for run in runs: 240 run_path = os.path.dirname(run.trace_path) 241 sysfs = SysfsExtractor(run_path) 242 243 thermal_params = sysfs.get_parameters() 244 245 sorted_weights = [] 246 for param in sorted(thermal_params): 247 if re.match(r"cdev\d+_weight", param): 248 sorted_weights.append(thermal_params[param]) 249 250 actor_weights.append(zip(actor_order, sorted_weights)) 251 252 # Do nothing if we don't have actor weights for any run 253 if not any(actor_weights): 254 return 255 256 num_runs = len(runs) 257 axis = pre_plot_setup(width=width, height=height, ncols=num_runs) 258 259 if num_runs == 1: 260 axis = [axis] 261 262 for ax, run, weights in zip(axis, runs, actor_weights): 263 run.thermal_governor.plot_weighted_input_power(weights, title=run.name, 264 ax=ax) 265 266def plot_input_power(runs, actor_order, width=None, height=None): 267 """Make a multicolumn plot of the input power of each run""" 268 num_runs = len(runs) 269 axis = pre_plot_setup(width=width, height=height, ncols=num_runs) 270 271 if num_runs == 1: 272 axis = [axis] 273 274 for ax, run in zip(axis, runs): 275 run.thermal_governor.plot_input_power(actor_order, title=run.name, 276 ax=ax) 277 278 plot_weighted_input_power(runs, actor_order, width, height) 279 280def plot_output_power(runs, actor_order, width=None, height=None): 281 """Make a multicolumn plot of the output power of each run""" 282 num_runs = len(runs) 283 axis = pre_plot_setup(width=width, height=height, ncols=num_runs) 284 285 if num_runs == 1: 286 axis = [axis] 287 288 for ax, run in zip(axis, runs): 289 run.thermal_governor.plot_output_power(actor_order, title=run.name, 290 ax=ax) 291 292def plot_freq_hists(runs, map_label): 293 """Plot frequency histograms of multiple runs""" 294 num_runs = len(runs) 295 nrows = 2 * number_freq_plots(runs, map_label) 296 axis = pre_plot_setup(ncols=num_runs, nrows=nrows) 297 298 if num_runs == 1: 299 axis = [axis] 300 else: 301 axis = zip(*axis) 302 303 for ax, run in zip(axis, runs): 304 run.plot_freq_hists(map_label, ax=ax) 305 306def plot_temperature_hist(runs): 307 """Plot temperature histograms for all the runs""" 308 num_runs = 0 309 for run in runs: 310 if len(run.thermal.data_frame): 311 num_runs += 1 312 313 if num_runs == 0: 314 return 315 316 axis = pre_plot_setup(ncols=num_runs) 317 318 if num_runs == 1: 319 axis = [axis] 320 321 for ax, run in zip(axis, runs): 322 run.thermal.plot_temperature_hist(ax, run.name) 323