aboutsummaryrefslogtreecommitdiff
path: root/trappy/plot_utils.py
blob: 850d51f729b1d11f73d839089a9f379c7840b2c6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
#    Copyright 2015-2016 ARM Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

"""Small functions to help with plots"""

# pylint disable=star-args

from matplotlib import pyplot as plt
import os
import re

from trappy.wa import SysfsExtractor

GOLDEN_RATIO = 1.618034

def normalize_title(title, opt_title):
    """Return a string with that contains the title and opt_title if it's
not the empty string

    See test_normalize_title() for usage

    """
    if opt_title is not "":
        title = opt_title + " - " + title

    return title

def set_lim(lim, get_lim_f, set_lim_f):
    """Set x or y limitis of the plot

    lim can be a tuple containing the limits or the string "default"
    or "range".  "default" does nothing and uses matplotlib default.
    "range" extends the current margin by 10%.  This is useful since
    the default xlim and ylim of the plots sometimes make it harder to
    see data that is just in the margin.

    """
    if lim == "default":
        return

    if lim == "range":
        cur_lim = get_lim_f()
        lim = (cur_lim[0] - 0.1 * (cur_lim[1] - cur_lim[0]),
               cur_lim[1] + 0.1 * (cur_lim[1] - cur_lim[0]))

    set_lim_f(lim[0], lim[1])

def set_xlim(ax, xlim):
    """Set the xlim of the plot

    See set_lim() for the details
    """
    set_lim(xlim, ax.get_xlim, ax.set_xlim)

def set_ylim(ax, ylim):
    """Set the ylim of the plot

    See set_lim() for the details
    """
    set_lim(ylim, ax.get_ylim, ax.set_ylim)

def pre_plot_setup(width=None, height=None, ncols=1, nrows=1):
    """initialize a figure

    width and height are the height and width of each row of plots.
    For 1x1 plots, that's the height and width of the plot.  This
    function should be called before any calls to plot()

    """

    if height is None:
        if width is None:
            height = 6
            width = 10
        else:
            height = width / GOLDEN_RATIO
    else:
        if width is None:
            width = height * GOLDEN_RATIO

    height *= nrows

    _, axis = plt.subplots(ncols=ncols, nrows=nrows, figsize=(width, height))

    # Needed for multirow blots to not overlap with each other
    plt.tight_layout(h_pad=3.5)

    return axis

def post_plot_setup(ax, title="", xlabel=None, ylabel=None, xlim="default",
                    ylim="range"):
    """Set xlabel, ylabel title, xlim and ylim of the plot

    This has to be called after calls to .plot().  The default ylim is
    to extend it by 10% because matplotlib default makes it hard
    values that are close to the margins

    """

    if xlabel is not None:
        ax.set_xlabel(xlabel)

    if ylabel is not None:
        ax.set_ylabel(ylabel)

    if title:
        ax.set_title(title)

    set_ylim(ax, ylim)
    set_xlim(ax, xlim)

def number_freq_plots(runs, map_label):
    """Calculate the number of plots needed for allfreq plots and frequency
    histogram plots

    """
    num_cpu_plots = len(map_label)

    has_devfreq_data = False
    for run in runs:
        if len(run.devfreq_in_power.data_frame) > 0:
            has_devfreq_data = True
            break

    num_freq_plots = num_cpu_plots
    if has_devfreq_data:
        num_freq_plots += 1

    return num_freq_plots

def plot_temperature(runs, width=None, height=None, ylim="range", tz_id=None):
    """Plot temperatures

    runs is an array of FTrace() instances.  Extract the control_temp
    from the governor data and plot the temperatures reported by the
    thermal framework.  The governor doesn't track temperature when
    it's off, so the thermal framework trace is more reliable.

    """

    ax = pre_plot_setup(width, height)

    for run in runs:
        gov_dfr = run.thermal_governor.data_frame
        if tz_id:
            gov_dfr = gov_dfr[gov_dfr["thermal_zone_id"] == tz_id]

        try:
            current_temp = gov_dfr["current_temperature"]
            delta_temp = gov_dfr["delta_temperature"]
            control_series = (current_temp + delta_temp) / 1000
        except KeyError:
            control_series = None

        try:
            run.thermal.plot_temperature(control_temperature=control_series,
                                         ax=ax, legend_label=run.name,
                                         tz_id=tz_id)
        except ValueError:
            run.thermal_governor.plot_temperature(ax=ax, legend_label=run.name)

    post_plot_setup(ax, title="Temperature", ylim=ylim)
    plt.legend(loc="best")

def plot_hist(data, ax, title, unit, bins, xlabel, xlim, ylim):
    """Plot a histogram"""

    mean = data.mean()
    std = data.std()
    title += " (mean = {:.2f}{}, std = {:.2f})".format(mean, unit, std)
    xlabel += " ({})".format(unit)

    data.hist(ax=ax, bins=bins)
    post_plot_setup(ax, title=title, xlabel=xlabel, ylabel="count", xlim=xlim,
                    ylim=ylim)

def plot_load(runs, map_label, width=None, height=None):
    """Make a multiplot of all the loads"""
    num_runs = len(runs)
    axis = pre_plot_setup(width=width, height=height, ncols=num_runs, nrows=2)

    if num_runs == 1:
        axis = [axis]
    else:
        axis = zip(*axis)

    for ax, run in zip(axis, runs):
        run.plot_load(map_label, title=run.name, ax=ax[0])
        run.plot_normalized_load(map_label, title=run.name, ax=ax[1])

def plot_allfreqs(runs, map_label, width=None, height=None):
    """Make a multicolumn plots of the allfreqs plots of each run"""
    num_runs = len(runs)
    nrows = number_freq_plots(runs, map_label)

    axis = pre_plot_setup(width=width, height=height, nrows=nrows,
                          ncols=num_runs)

    if num_runs == 1:
        if nrows == 1:
            axis = [[axis]]
        else:
            axis = [axis]
    elif nrows == 1:
        axis = [[ax] for ax in axis]
    else:
        axis = zip(*axis)

    for ax, run in zip(axis, runs):
        run.plot_allfreqs(map_label, ax=ax)

def plot_controller(runs, width=None, height=None):
    """Make a multicolumn plot of the pid controller of each run"""
    num_runs = len(runs)
    axis = pre_plot_setup(width=width, height=height, ncols=num_runs)

    if num_runs == 1:
        axis = [axis]

    for ax, run in zip(axis, runs):
        run.pid_controller.plot_controller(title=run.name, ax=ax)

def plot_weighted_input_power(runs, actor_order, width=None, height=None):
    """Make a multicolumn plot of the weighted input power of each run"""

    actor_weights = []
    for run in runs:
        run_path = os.path.dirname(run.trace_path)
        sysfs = SysfsExtractor(run_path)

        thermal_params = sysfs.get_parameters()

        sorted_weights = []
        for param in sorted(thermal_params):
            if re.match(r"cdev\d+_weight", param):
                sorted_weights.append(thermal_params[param])

        actor_weights.append(zip(actor_order, sorted_weights))

    # Do nothing if we don't have actor weights for any run
    if not any(actor_weights):
        return

    num_runs = len(runs)
    axis = pre_plot_setup(width=width, height=height, ncols=num_runs)

    if num_runs == 1:
        axis = [axis]

    for ax, run, weights in zip(axis, runs, actor_weights):
        run.thermal_governor.plot_weighted_input_power(weights, title=run.name,
                                                       ax=ax)

def plot_input_power(runs, actor_order, width=None, height=None):
    """Make a multicolumn plot of the input power of each run"""
    num_runs = len(runs)
    axis = pre_plot_setup(width=width, height=height, ncols=num_runs)

    if num_runs == 1:
        axis = [axis]

    for ax, run in zip(axis, runs):
        run.thermal_governor.plot_input_power(actor_order, title=run.name,
                                              ax=ax)

    plot_weighted_input_power(runs, actor_order, width, height)

def plot_output_power(runs, actor_order, width=None, height=None):
    """Make a multicolumn plot of the output power of each run"""
    num_runs = len(runs)
    axis = pre_plot_setup(width=width, height=height, ncols=num_runs)

    if num_runs == 1:
        axis = [axis]

    for ax, run in zip(axis, runs):
        run.thermal_governor.plot_output_power(actor_order, title=run.name,
                                               ax=ax)

def plot_freq_hists(runs, map_label):
    """Plot frequency histograms of multiple runs"""
    num_runs = len(runs)
    nrows = 2 * number_freq_plots(runs, map_label)
    axis = pre_plot_setup(ncols=num_runs, nrows=nrows)

    if num_runs == 1:
        axis = [axis]
    else:
        axis = zip(*axis)

    for ax, run in zip(axis, runs):
        run.plot_freq_hists(map_label, ax=ax)

def plot_temperature_hist(runs):
    """Plot temperature histograms for all the runs"""
    num_runs = 0
    for run in runs:
        if len(run.thermal.data_frame):
            num_runs += 1

    if num_runs == 0:
        return

    axis = pre_plot_setup(ncols=num_runs)

    if num_runs == 1:
        axis = [axis]

    for ax, run in zip(axis, runs):
        run.thermal.plot_temperature_hist(ax, run.name)