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"""Process the output of the cpu_cooling devices in the current
17directory's trace.dat"""
18
19import pandas as pd
20
21from trappy.base import Base
22from trappy.dynamic import register_ftrace_parser
23
24def pivot_with_labels(dfr, data_col_name, new_col_name, mapping_label):
25    """Pivot a :mod:`pandas.DataFrame` row into columns
26
27    :param dfr: The :mod:`pandas.DataFrame` to operate on.
28
29    :param data_col_name: The name of the column in the :mod:`pandas.DataFrame`
30        which contains the values.
31
32    :param new_col_name: The name of the column in the :mod:`pandas.DataFrame` that will
33        become the new columns.
34
35    :param mapping_label: A dictionary whose keys are the values in
36        new_col_name and whose values are their
37        corresponding name in the :mod:`pandas.DataFrame` to be returned.
38
39    :type dfr: :mod:`pandas.DataFrame`
40    :type data_col_name: str
41    :type new_col_name: str
42    :type mapping_label: dict
43
44    Example:
45
46        >>> dfr_in = pd.DataFrame({'cpus': ["000000f0",
47        >>>                                 "0000000f",
48        >>>                                 "000000f0",
49        >>>                                 "0000000f"
50        >>>                                 ],
51        >>>                        'freq': [1, 3, 2, 6]})
52        >>> dfr_in
53               cpus  freq
54        0  000000f0     1
55        1  0000000f     3
56        2  000000f0     2
57        3  0000000f     6
58
59        >>> map_label = {"000000f0": "A15", "0000000f": "A7"}
60        >>> power.pivot_with_labels(dfr_in, "freq", "cpus", map_label)
61           A15  A7
62        0    1 NaN
63        1    1   3
64        2    2   3
65        3    2   6
66
67    """
68
69    # There has to be a more "pandas" way of doing this.
70
71    col_set = set(dfr[new_col_name])
72
73    ret_series = {}
74    for col in col_set:
75        try:
76            label = mapping_label[col]
77        except KeyError:
78            available_keys = ", ".join(mapping_label.keys())
79            error_str = '"{}" not found, available keys: {}'.format(col,
80                                                                 available_keys)
81            raise KeyError(error_str)
82        data = dfr[dfr[new_col_name] == col][data_col_name]
83
84        ret_series[label] = data
85
86    return pd.DataFrame(ret_series).fillna(method="pad")
87
88def num_cpus_in_mask(mask):
89    """Return the number of cpus in a cpumask"""
90
91    mask = mask.replace(",", "")
92    value = int(mask, 16)
93
94    return bin(value).count("1")
95
96class CpuOutPower(Base):
97    """Process the cpufreq cooling power actor data in a ftrace dump"""
98
99    unique_word = "thermal_power_cpu_limit"
100    """The unique word that will be matched in a trace line"""
101
102    name = "cpu_out_power"
103    """The name of the :mod:`pandas.DataFrame` member that will be created in a
104    :mod:`trappy.ftrace.FTrace` object"""
105
106    pivot = "cpus"
107    """The Pivot along which the data is orthogonal"""
108
109    def get_all_freqs(self, mapping_label):
110        """Get a :mod:`pandas.DataFrame` with the maximum frequencies allowed by the governor
111
112        :param mapping_label: A dictionary that maps cpumasks to name
113            of the cpu.
114        :type mapping_label: dict
115
116        :return: freqs are in MHz
117        """
118
119        dfr = self.data_frame
120
121        return pivot_with_labels(dfr, "freq", "cpus", mapping_label) / 1000
122
123register_ftrace_parser(CpuOutPower, "thermal")
124
125class CpuInPower(Base):
126    """Process the cpufreq cooling power actor data in a ftrace dump
127    """
128
129    unique_word = "thermal_power_cpu_get"
130    """The unique word that will be matched in a trace line"""
131
132    name = "cpu_in_power"
133    """The name of the :mod:`pandas.DataFrame` member that will be created in a
134    :mod:`trappy.ftrace.FTrace` object"""
135
136    pivot = "cpus"
137    """The Pivot along which the data is orthogonal"""
138
139    def _get_load_series(self):
140        """get a :mod:`pandas.Series` with the aggregated load"""
141
142        dfr = self.data_frame
143        load_cols = [s for s in dfr.columns if s.startswith("load")]
144
145        load_series = dfr[load_cols[0]].copy()
146        for col in load_cols[1:]:
147            load_series += dfr[col]
148
149        return load_series
150
151    def get_load_data(self, mapping_label):
152        """Return :mod:`pandas.DataFrame` suitable for plot_load()
153
154        :param mapping_label: A Dictionary mapping cluster cpumasks to labels
155        :type mapping_label: dict
156        """
157
158        dfr = self.data_frame
159        load_series = self._get_load_series()
160        load_dfr = pd.DataFrame({"cpus": dfr["cpus"], "load": load_series})
161
162        return pivot_with_labels(load_dfr, "load", "cpus", mapping_label)
163
164    def get_normalized_load_data(self, mapping_label):
165        """Return a :mod:`pandas.DataFrame` for plotting normalized load data
166
167        :param mapping_label: should be a dictionary mapping cluster cpumasks
168            to labels
169        :type mapping_label: dict
170        """
171
172        dfr = self.data_frame
173        load_series = self._get_load_series()
174
175        load_series *= dfr['freq']
176        for cpumask in mapping_label:
177            num_cpus = num_cpus_in_mask(cpumask)
178            idx = dfr["cpus"] == cpumask
179            max_freq = max(dfr[idx]["freq"])
180            load_series[idx] = load_series[idx] / (max_freq * num_cpus)
181
182        load_dfr = pd.DataFrame({"cpus": dfr["cpus"], "load": load_series})
183
184        return pivot_with_labels(load_dfr, "load", "cpus", mapping_label)
185
186    def get_all_freqs(self, mapping_label):
187        """get a :mod:`pandas.DataFrame` with the "in" frequencies as seen by the governor
188
189        .. note::
190
191            Frequencies are in MHz
192        """
193
194        dfr = self.data_frame
195
196        return pivot_with_labels(dfr, "freq", "cpus", mapping_label) / 1000
197
198register_ftrace_parser(CpuInPower, "thermal")
199