| # Copyright 2015-2017 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. |
| # |
| |
| """Process the output of the cpu_cooling devices in the current |
| directory's trace.dat""" |
| |
| import pandas as pd |
| |
| from trappy.base import Base |
| from trappy.dynamic import register_ftrace_parser |
| |
| def pivot_with_labels(dfr, data_col_name, new_col_name, mapping_label): |
| """Pivot a :mod:`pandas.DataFrame` row into columns |
| |
| :param dfr: The :mod:`pandas.DataFrame` to operate on. |
| |
| :param data_col_name: The name of the column in the :mod:`pandas.DataFrame` |
| which contains the values. |
| |
| :param new_col_name: The name of the column in the :mod:`pandas.DataFrame` that will |
| become the new columns. |
| |
| :param mapping_label: A dictionary whose keys are the values in |
| new_col_name and whose values are their |
| corresponding name in the :mod:`pandas.DataFrame` to be returned. |
| |
| :type dfr: :mod:`pandas.DataFrame` |
| :type data_col_name: str |
| :type new_col_name: str |
| :type mapping_label: dict |
| |
| Example: |
| |
| >>> dfr_in = pd.DataFrame({'cpus': ["000000f0", |
| >>> "0000000f", |
| >>> "000000f0", |
| >>> "0000000f" |
| >>> ], |
| >>> 'freq': [1, 3, 2, 6]}) |
| >>> dfr_in |
| cpus freq |
| 0 000000f0 1 |
| 1 0000000f 3 |
| 2 000000f0 2 |
| 3 0000000f 6 |
| |
| >>> map_label = {"000000f0": "A15", "0000000f": "A7"} |
| >>> power.pivot_with_labels(dfr_in, "freq", "cpus", map_label) |
| A15 A7 |
| 0 1 NaN |
| 1 1 3 |
| 2 2 3 |
| 3 2 6 |
| |
| """ |
| |
| # There has to be a more "pandas" way of doing this. |
| |
| col_set = set(dfr[new_col_name]) |
| |
| ret_series = {} |
| for col in col_set: |
| try: |
| label = mapping_label[col] |
| except KeyError: |
| available_keys = ", ".join(mapping_label.keys()) |
| error_str = '"{}" not found, available keys: {}'.format(col, |
| available_keys) |
| raise KeyError(error_str) |
| data = dfr[dfr[new_col_name] == col][data_col_name] |
| |
| ret_series[label] = data |
| |
| return pd.DataFrame(ret_series).fillna(method="pad") |
| |
| def num_cpus_in_mask(mask): |
| """Return the number of cpus in a cpumask""" |
| |
| mask = mask.replace(",", "") |
| value = int(mask, 16) |
| |
| return bin(value).count("1") |
| |
| class CpuOutPower(Base): |
| """Process the cpufreq cooling power actor data in a ftrace dump""" |
| |
| unique_word = "thermal_power_cpu_limit" |
| """The unique word that will be matched in a trace line""" |
| |
| name = "cpu_out_power" |
| """The name of the :mod:`pandas.DataFrame` member that will be created in a |
| :mod:`trappy.ftrace.FTrace` object""" |
| |
| pivot = "cpus" |
| """The Pivot along which the data is orthogonal""" |
| |
| def get_all_freqs(self, mapping_label): |
| """Get a :mod:`pandas.DataFrame` with the maximum frequencies allowed by the governor |
| |
| :param mapping_label: A dictionary that maps cpumasks to name |
| of the cpu. |
| :type mapping_label: dict |
| |
| :return: freqs are in MHz |
| """ |
| |
| dfr = self.data_frame |
| |
| return pivot_with_labels(dfr, "freq", "cpus", mapping_label) / 1000 |
| |
| register_ftrace_parser(CpuOutPower, "thermal") |
| |
| class CpuInPower(Base): |
| """Process the cpufreq cooling power actor data in a ftrace dump |
| """ |
| |
| unique_word = "thermal_power_cpu_get" |
| """The unique word that will be matched in a trace line""" |
| |
| name = "cpu_in_power" |
| """The name of the :mod:`pandas.DataFrame` member that will be created in a |
| :mod:`trappy.ftrace.FTrace` object""" |
| |
| pivot = "cpus" |
| """The Pivot along which the data is orthogonal""" |
| |
| def _get_load_series(self): |
| """get a :mod:`pandas.Series` with the aggregated load""" |
| |
| dfr = self.data_frame |
| load_cols = [s for s in dfr.columns if s.startswith("load")] |
| |
| load_series = dfr[load_cols[0]].copy() |
| for col in load_cols[1:]: |
| load_series += dfr[col] |
| |
| return load_series |
| |
| def get_load_data(self, mapping_label): |
| """Return :mod:`pandas.DataFrame` suitable for plot_load() |
| |
| :param mapping_label: A Dictionary mapping cluster cpumasks to labels |
| :type mapping_label: dict |
| """ |
| |
| dfr = self.data_frame |
| load_series = self._get_load_series() |
| load_dfr = pd.DataFrame({"cpus": dfr["cpus"], "load": load_series}) |
| |
| return pivot_with_labels(load_dfr, "load", "cpus", mapping_label) |
| |
| def get_normalized_load_data(self, mapping_label): |
| """Return a :mod:`pandas.DataFrame` for plotting normalized load data |
| |
| :param mapping_label: should be a dictionary mapping cluster cpumasks |
| to labels |
| :type mapping_label: dict |
| """ |
| |
| dfr = self.data_frame |
| load_series = self._get_load_series() |
| |
| load_series *= dfr['freq'] |
| for cpumask in mapping_label: |
| num_cpus = num_cpus_in_mask(cpumask) |
| idx = dfr["cpus"] == cpumask |
| max_freq = max(dfr[idx]["freq"]) |
| load_series[idx] = load_series[idx] / (max_freq * num_cpus) |
| |
| load_dfr = pd.DataFrame({"cpus": dfr["cpus"], "load": load_series}) |
| |
| return pivot_with_labels(load_dfr, "load", "cpus", mapping_label) |
| |
| def get_all_freqs(self, mapping_label): |
| """get a :mod:`pandas.DataFrame` with the "in" frequencies as seen by the governor |
| |
| .. note:: |
| |
| Frequencies are in MHz |
| """ |
| |
| dfr = self.data_frame |
| |
| return pivot_with_labels(dfr, "freq", "cpus", mapping_label) / 1000 |
| |
| register_ftrace_parser(CpuInPower, "thermal") |