Brendan Jackman | e81fdcb | 2017-01-04 17:10:29 +0000 | [diff] [blame] | 1 | # Copyright 2015-2017 ARM Limited |
Javi Merino | c47d2df | 2015-02-06 16:04:03 +0000 | [diff] [blame] | 2 | # |
Javi Merino | aace7c0 | 2015-08-10 14:10:47 +0100 | [diff] [blame] | 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 | |
Javi Merino | 7f3c8a6 | 2014-06-13 11:38:58 +0100 | [diff] [blame] | 16 | """Small functions to help with plots""" |
| 17 | |
Javi Merino | 8625d73 | 2015-06-17 17:21:31 +0100 | [diff] [blame] | 18 | # pylint disable=star-args |
| 19 | |
Javi Merino | 7f3c8a6 | 2014-06-13 11:38:58 +0100 | [diff] [blame] | 20 | from matplotlib import pyplot as plt |
Javi Merino | 898783c | 2015-06-16 11:45:16 +0100 | [diff] [blame] | 21 | import os |
| 22 | import re |
| 23 | |
Javi Merino | 435457c | 2015-08-10 15:59:10 +0100 | [diff] [blame] | 24 | from trappy.wa import SysfsExtractor |
Javi Merino | 7f3c8a6 | 2014-06-13 11:38:58 +0100 | [diff] [blame] | 25 | |
| 26 | GOLDEN_RATIO = 1.618034 |
| 27 | |
Javi Merino | 7f3c8a6 | 2014-06-13 11:38:58 +0100 | [diff] [blame] | 28 | def normalize_title(title, opt_title): |
Javi Merino | 8625d73 | 2015-06-17 17:21:31 +0100 | [diff] [blame] | 29 | """Return a string with that contains the title and opt_title if it's |
| 30 | not the empty string |
Javi Merino | 7f3c8a6 | 2014-06-13 11:38:58 +0100 | [diff] [blame] | 31 | |
| 32 | See test_normalize_title() for usage |
Javi Merino | 8625d73 | 2015-06-17 17:21:31 +0100 | [diff] [blame] | 33 | |
Javi Merino | 7f3c8a6 | 2014-06-13 11:38:58 +0100 | [diff] [blame] | 34 | """ |
| 35 | if opt_title is not "": |
| 36 | title = opt_title + " - " + title |
| 37 | |
| 38 | return title |
Javi Merino | 3a73655 | 2014-06-19 19:22:44 +0100 | [diff] [blame] | 39 | |
Javi Merino | 8625d73 | 2015-06-17 17:21:31 +0100 | [diff] [blame] | 40 | def set_lim(lim, get_lim_f, set_lim_f): |
Javi Merino | a156127 | 2014-06-21 17:49:02 +0100 | [diff] [blame] | 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": |
Javi Merino | 8625d73 | 2015-06-17 17:21:31 +0100 | [diff] [blame] | 54 | cur_lim = get_lim_f() |
Javi Merino | a156127 | 2014-06-21 17:49:02 +0100 | [diff] [blame] | 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 | |
Javi Merino | 8625d73 | 2015-06-17 17:21:31 +0100 | [diff] [blame] | 58 | set_lim_f(lim[0], lim[1]) |
Javi Merino | a156127 | 2014-06-21 17:49:02 +0100 | [diff] [blame] | 59 | |
| 60 | def 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 | |
| 67 | def 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 | |
Javi Merino | 270caac | 2014-08-12 10:41:38 +0100 | [diff] [blame] | 74 | def pre_plot_setup(width=None, height=None, ncols=1, nrows=1): |
Javi Merino | 3a73655 | 2014-06-19 19:22:44 +0100 | [diff] [blame] | 75 | """initialize a figure |
| 76 | |
Javi Merino | fdf4458 | 2014-08-12 14:30:05 +0100 | [diff] [blame] | 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() |
Javi Merino | f9ac578 | 2014-06-21 19:02:50 +0100 | [diff] [blame] | 80 | |
Javi Merino | 3a73655 | 2014-06-19 19:22:44 +0100 | [diff] [blame] | 81 | """ |
| 82 | |
Javi Merino | f9ac578 | 2014-06-21 19:02:50 +0100 | [diff] [blame] | 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 |
Javi Merino | 3a73655 | 2014-06-19 19:22:44 +0100 | [diff] [blame] | 92 | |
Javi Merino | fdf4458 | 2014-08-12 14:30:05 +0100 | [diff] [blame] | 93 | height *= nrows |
| 94 | |
Javi Merino | 270caac | 2014-08-12 10:41:38 +0100 | [diff] [blame] | 95 | _, axis = plt.subplots(ncols=ncols, nrows=nrows, figsize=(width, height)) |
Javi Merino | 3a73655 | 2014-06-19 19:22:44 +0100 | [diff] [blame] | 96 | |
Javi Merino | 0c48426 | 2014-08-12 11:55:04 +0100 | [diff] [blame] | 97 | # Needed for multirow blots to not overlap with each other |
| 98 | plt.tight_layout(h_pad=3.5) |
| 99 | |
Javi Merino | 7f88ae3 | 2014-08-11 16:53:35 +0100 | [diff] [blame] | 100 | return axis |
Javi Merino | 3a73655 | 2014-06-19 19:22:44 +0100 | [diff] [blame] | 101 | |
Javi Merino | 0aa7cd8 | 2015-03-09 18:57:37 +0000 | [diff] [blame] | 102 | def 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 |
Javi Merino | 3a73655 | 2014-06-19 19:22:44 +0100 | [diff] [blame] | 105 | |
Javi Merino | a156127 | 2014-06-21 17:49:02 +0100 | [diff] [blame] | 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 | |
Javi Merino | 3a73655 | 2014-06-19 19:22:44 +0100 | [diff] [blame] | 110 | """ |
| 111 | |
Javi Merino | b839b90 | 2014-06-21 19:30:13 +0100 | [diff] [blame] | 112 | if xlabel is not None: |
Javi Merino | ece0ab1 | 2014-08-12 16:24:55 +0100 | [diff] [blame] | 113 | ax.set_xlabel(xlabel) |
Javi Merino | f9d43af | 2014-06-21 16:32:04 +0100 | [diff] [blame] | 114 | |
Javi Merino | 0aa7cd8 | 2015-03-09 18:57:37 +0000 | [diff] [blame] | 115 | if ylabel is not None: |
| 116 | ax.set_ylabel(ylabel) |
| 117 | |
Javi Merino | 3a73655 | 2014-06-19 19:22:44 +0100 | [diff] [blame] | 118 | if title: |
Javi Merino | fd38c55 | 2014-08-11 17:14:52 +0100 | [diff] [blame] | 119 | ax.set_title(title) |
Javi Merino | 3a73655 | 2014-06-19 19:22:44 +0100 | [diff] [blame] | 120 | |
Javi Merino | a156127 | 2014-06-21 17:49:02 +0100 | [diff] [blame] | 121 | set_ylim(ax, ylim) |
| 122 | set_xlim(ax, xlim) |
Javi Merino | d04643f | 2014-06-21 17:00:16 +0100 | [diff] [blame] | 123 | |
Javi Merino | 9d9c9b8 | 2015-04-29 11:15:11 +0100 | [diff] [blame] | 124 | def 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 | |
Javi Merino | 1217c65 | 2016-08-22 20:04:15 +0100 | [diff] [blame] | 143 | def plot_temperature(runs, width=None, height=None, ylim="range", tz_id=None): |
Javi Merino | 2919e8d | 2014-06-26 15:16:05 +0100 | [diff] [blame] | 144 | """Plot temperatures |
| 145 | |
Javi Merino | c26a323 | 2015-12-11 18:00:30 +0000 | [diff] [blame] | 146 | runs is an array of FTrace() instances. Extract the control_temp |
Javi Merino | 1cc4bfc | 2014-08-11 16:29:11 +0100 | [diff] [blame] | 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. |
Javi Merino | 2919e8d | 2014-06-26 15:16:05 +0100 | [diff] [blame] | 150 | |
| 151 | """ |
| 152 | |
Javi Merino | 8011416 | 2014-08-08 16:48:32 +0100 | [diff] [blame] | 153 | ax = pre_plot_setup(width, height) |
| 154 | |
Javi Merino | 1cc4bfc | 2014-08-11 16:29:11 +0100 | [diff] [blame] | 155 | for run in runs: |
Javi Merino | 1217c65 | 2016-08-22 20:04:15 +0100 | [diff] [blame] | 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 | |
Javi Merino | 2526583 | 2016-08-22 17:37:19 +0100 | [diff] [blame] | 160 | try: |
Javi Merino | 1217c65 | 2016-08-22 20:04:15 +0100 | [diff] [blame] | 161 | current_temp = gov_dfr["current_temperature"] |
| 162 | delta_temp = gov_dfr["delta_temperature"] |
Javi Merino | 2526583 | 2016-08-22 17:37:19 +0100 | [diff] [blame] | 163 | control_series = (current_temp + delta_temp) / 1000 |
| 164 | except KeyError: |
| 165 | control_series = None |
Javi Merino | fa3ed92 | 2015-02-06 18:44:44 +0000 | [diff] [blame] | 166 | |
| 167 | try: |
| 168 | run.thermal.plot_temperature(control_temperature=control_series, |
Javi Merino | 1217c65 | 2016-08-22 20:04:15 +0100 | [diff] [blame] | 169 | ax=ax, legend_label=run.name, |
| 170 | tz_id=tz_id) |
Javi Merino | fa3ed92 | 2015-02-06 18:44:44 +0000 | [diff] [blame] | 171 | except ValueError: |
| 172 | run.thermal_governor.plot_temperature(ax=ax, legend_label=run.name) |
Javi Merino | 8011416 | 2014-08-08 16:48:32 +0100 | [diff] [blame] | 173 | |
| 174 | post_plot_setup(ax, title="Temperature", ylim=ylim) |
| 175 | plt.legend(loc="best") |
Javi Merino | 2919e8d | 2014-06-26 15:16:05 +0100 | [diff] [blame] | 176 | |
Javi Merino | 23dff5d | 2015-03-09 19:05:46 +0000 | [diff] [blame] | 177 | def plot_hist(data, ax, title, unit, bins, xlabel, xlim, ylim): |
Javi Merino | ed977c1 | 2014-06-25 17:46:17 +0100 | [diff] [blame] | 178 | """Plot a histogram""" |
| 179 | |
| 180 | mean = data.mean() |
| 181 | std = data.std() |
Javi Merino | 23dff5d | 2015-03-09 19:05:46 +0000 | [diff] [blame] | 182 | title += " (mean = {:.2f}{}, std = {:.2f})".format(mean, unit, std) |
| 183 | xlabel += " ({})".format(unit) |
Javi Merino | ed977c1 | 2014-06-25 17:46:17 +0100 | [diff] [blame] | 184 | |
Javi Merino | ed977c1 | 2014-06-25 17:46:17 +0100 | [diff] [blame] | 185 | data.hist(ax=ax, bins=bins) |
Javi Merino | 0aa7cd8 | 2015-03-09 18:57:37 +0000 | [diff] [blame] | 186 | post_plot_setup(ax, title=title, xlabel=xlabel, ylabel="count", xlim=xlim, |
| 187 | ylim=ylim) |
Javi Merino | 6bf4883 | 2014-08-11 17:15:35 +0100 | [diff] [blame] | 188 | |
| 189 | def plot_load(runs, map_label, width=None, height=None): |
| 190 | """Make a multiplot of all the loads""" |
Javi Merino | af05f31 | 2014-08-11 17:47:59 +0100 | [diff] [blame] | 191 | num_runs = len(runs) |
Javi Merino | f9a8ea2 | 2015-06-16 17:50:53 +0100 | [diff] [blame] | 192 | axis = pre_plot_setup(width=width, height=height, ncols=num_runs, nrows=2) |
Javi Merino | af05f31 | 2014-08-11 17:47:59 +0100 | [diff] [blame] | 193 | |
| 194 | if num_runs == 1: |
| 195 | axis = [axis] |
Javi Merino | f9a8ea2 | 2015-06-16 17:50:53 +0100 | [diff] [blame] | 196 | else: |
| 197 | axis = zip(*axis) |
Javi Merino | 6bf4883 | 2014-08-11 17:15:35 +0100 | [diff] [blame] | 198 | |
| 199 | for ax, run in zip(axis, runs): |
Javi Merino | f9a8ea2 | 2015-06-16 17:50:53 +0100 | [diff] [blame] | 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]) |
Javi Merino | 0c48426 | 2014-08-12 11:55:04 +0100 | [diff] [blame] | 202 | |
| 203 | def 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) |
Javi Merino | 9d9c9b8 | 2015-04-29 11:15:11 +0100 | [diff] [blame] | 206 | nrows = number_freq_plots(runs, map_label) |
| 207 | |
| 208 | axis = pre_plot_setup(width=width, height=height, nrows=nrows, |
Javi Merino | 0c48426 | 2014-08-12 11:55:04 +0100 | [diff] [blame] | 209 | ncols=num_runs) |
| 210 | |
| 211 | if num_runs == 1: |
Javi Merino | 94d89c1 | 2016-01-05 14:47:07 +0000 | [diff] [blame] | 212 | if nrows == 1: |
| 213 | axis = [[axis]] |
| 214 | else: |
| 215 | axis = [axis] |
| 216 | elif nrows == 1: |
| 217 | axis = [[ax] for ax in axis] |
Javi Merino | 0c48426 | 2014-08-12 11:55:04 +0100 | [diff] [blame] | 218 | else: |
| 219 | axis = zip(*axis) |
| 220 | |
| 221 | for ax, run in zip(axis, runs): |
| 222 | run.plot_allfreqs(map_label, ax=ax) |
Javi Merino | 38fd12d | 2014-08-12 15:02:47 +0100 | [diff] [blame] | 223 | |
| 224 | def 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) |
Javi Merino | f5cd04b | 2014-08-12 15:26:22 +0100 | [diff] [blame] | 234 | |
Javi Merino | 898783c | 2015-06-16 11:45:16 +0100 | [diff] [blame] | 235 | def 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 | |
Javi Merino | f5cd04b | 2014-08-12 15:26:22 +0100 | [diff] [blame] | 266 | def 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): |
Javi Merino | 8625d73 | 2015-06-17 17:21:31 +0100 | [diff] [blame] | 275 | run.thermal_governor.plot_input_power(actor_order, title=run.name, |
| 276 | ax=ax) |
Javi Merino | 9fde215 | 2014-08-12 15:34:24 +0100 | [diff] [blame] | 277 | |
Javi Merino | 898783c | 2015-06-16 11:45:16 +0100 | [diff] [blame] | 278 | plot_weighted_input_power(runs, actor_order, width, height) |
| 279 | |
Javi Merino | 9fde215 | 2014-08-12 15:34:24 +0100 | [diff] [blame] | 280 | def 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): |
Javi Merino | 8625d73 | 2015-06-17 17:21:31 +0100 | [diff] [blame] | 289 | run.thermal_governor.plot_output_power(actor_order, title=run.name, |
| 290 | ax=ax) |
Javi Merino | e5ea60a | 2014-08-12 16:41:42 +0100 | [diff] [blame] | 291 | |
| 292 | def plot_freq_hists(runs, map_label): |
| 293 | """Plot frequency histograms of multiple runs""" |
| 294 | num_runs = len(runs) |
Javi Merino | 9d9c9b8 | 2015-04-29 11:15:11 +0100 | [diff] [blame] | 295 | nrows = 2 * number_freq_plots(runs, map_label) |
Javi Merino | e5ea60a | 2014-08-12 16:41:42 +0100 | [diff] [blame] | 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 | |
| 306 | def plot_temperature_hist(runs): |
| 307 | """Plot temperature histograms for all the runs""" |
Javi Merino | fa3ed92 | 2015-02-06 18:44:44 +0000 | [diff] [blame] | 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 | |
Javi Merino | e5ea60a | 2014-08-12 16:41:42 +0100 | [diff] [blame] | 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) |