• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# SPDX-License-Identifier: Apache-2.0
2#
3# Copyright (C) 2015, ARM Limited and contributors.
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18""" CPUs Analysis Module """
19
20import matplotlib.pyplot as plt
21import pylab as pl
22import pandas as pd
23
24from analysis_module import AnalysisModule
25
26
27class CpusAnalysis(AnalysisModule):
28    """
29    Support for CPUs Signals Analysis
30
31    :param trace: input Trace object
32    :type trace: :mod:`libs.utils.Trace`
33    """
34
35    def __init__(self, trace):
36        super(CpusAnalysis, self).__init__(trace)
37
38
39###############################################################################
40# DataFrame Getter Methods
41###############################################################################
42
43    def _dfg_context_switches(self):
44        """
45        Compute number of context switches on each CPU.
46
47        :returns: :mod:`pandas.DataFrame`
48        """
49        if not self._trace.hasEvents('sched_switch'):
50            self._log.warning('Events [sched_switch] not found, context switch '
51                              'computation not possible!')
52            return None
53
54        sched_df = self._dfg_trace_event('sched_switch')
55        cpus = range(self._platform['cpus_count'])
56        ctx_sw_df = pd.DataFrame(
57            [len(sched_df[sched_df['__cpu'] == cpu]) for cpu in cpus],
58            index=cpus,
59            columns=['context_switch_cnt']
60        )
61        ctx_sw_df.index.name = 'cpu'
62        return ctx_sw_df
63
64    def _dfg_cpu_wakeups(self, cpus=None):
65        """"
66        Get a DataFrame showing when a CPU was woken from idle
67
68        :param cpus: List of CPUs to find wakeups for. If None, all CPUs.
69        :type cpus: list(int) or None
70
71        :returns: :mod:`pandas.DataFrame` with one column ``cpu``, where each
72                  row shows a time when the given ``cpu`` was woken up from
73                  idle.
74        """
75        if not self._trace.hasEvents('cpu_idle'):
76            self._log.warning('Events [cpu_idle] not found, cannot '
77                              'get CPU wakeup events.')
78            return None
79
80        cpus = cpus or range(self._trace.platform['cpus_count'])
81
82        sr = pd.Series()
83        for cpu in cpus:
84            cpu_sr = self._trace.getCPUActiveSignal(cpu)
85            cpu_sr = cpu_sr[cpu_sr == 1]
86            cpu_sr = cpu_sr.replace(1, cpu)
87            sr = sr.append(cpu_sr)
88
89        return pd.DataFrame({'cpu': sr}).sort_index()
90
91###############################################################################
92# Plotting Methods
93###############################################################################
94
95    def plotCPU(self, cpus=None):
96        """
97        Plot CPU-related signals for both big and LITTLE clusters.
98
99        :param cpus: list of CPUs to be plotted
100        :type cpus: list(int)
101        """
102        if not self._trace.hasEvents('sched_load_avg_cpu'):
103            self._log.warning('Events [sched_load_avg_cpu] not found, '
104                              'plot DISABLED!')
105            return
106
107        # Filter on specified cpus
108        if cpus is None:
109            cpus = sorted(self._big_cpus + self._little_cpus)
110
111        # Plot: big CPUs
112        bcpus = set(cpus).intersection(self._big_cpus)
113        if bcpus:
114            self._plotCPU(bcpus, "big")
115
116        # Plot: LITTLE CPUs
117        lcpus = set(cpus).intersection(self._little_cpus)
118        if lcpus:
119            self._plotCPU(lcpus, "LITTLE")
120
121
122###############################################################################
123# Utility Methods
124###############################################################################
125
126    def _plotCPU(self, cpus, label=''):
127        """
128        Internal method that generates plots for all input CPUs.
129
130        :param cpus: list of CPUs to be plotted
131        :type cpus: list(int)
132        """
133        if label != '':
134            label1 = '{} '.format(label)
135            label2 = '_{}s'.format(label.lower())
136
137        # Plot required CPUs
138        _, pltaxes = plt.subplots(len(cpus), 1, figsize=(16, 3*(len(cpus))))
139
140        idx = 0
141        for cpu in cpus:
142
143            # Reference axes to be used
144            axes = pltaxes
145            if len(cpus) > 1:
146                axes = pltaxes[idx]
147
148            # Add CPU utilization
149            axes.set_title('{0:s}CPU [{1:d}]'.format(label1, cpu))
150            df = self._dfg_trace_event('sched_load_avg_cpu')
151            df = df[df.cpu == cpu]
152            if len(df):
153                df[['util_avg']].plot(ax=axes, drawstyle='steps-post',
154                                      alpha=0.4)
155
156            # if self._trace.hasEvents('sched_boost_cpu'):
157            #     df = self._dfg_trace_event('sched_boost_cpu')
158            #     df = df[df.cpu == cpu]
159            #     if len(df):
160            #         df[['usage', 'boosted_usage']].plot(
161            #             ax=axes,
162            #             style=['m-', 'r-'],
163            #             drawstyle='steps-post');
164
165            # Add Capacities data if avilable
166            if self._trace.hasEvents('cpu_capacity'):
167                df = self._dfg_trace_event('cpu_capacity')
168                df = df[df.cpu == cpu]
169                if len(df):
170                    # data = df[['capacity', 'tip_capacity', 'max_capacity']]
171                    # data.plot(ax=axes, style=['m', 'y', 'r'],
172                    data = df[['capacity', 'tip_capacity']]
173                    data.plot(ax=axes, style=['m', '--y'],
174                              drawstyle='steps-post')
175
176            # Add overutilized signal to the plot
177            self._trace.analysis.status.plotOverutilized(axes)
178
179            axes.set_ylim(0, 1100)
180            axes.set_xlim(self._trace.x_min, self._trace.x_max)
181
182            if idx == 0:
183                axes.annotate("{}CPUs Signals".format(label1),
184                              xy=(0, axes.get_ylim()[1]),
185                              xytext=(-50, 25),
186                              textcoords='offset points', fontsize=16)
187            # Disable x-axis timestamp for top-most cpus
188            if len(cpus) > 1 and idx < len(cpus)-1:
189                axes.set_xticklabels([])
190                axes.set_xlabel('')
191            axes.grid(True)
192
193            idx += 1
194
195        # Save generated plots into datadir
196        figname = '{}/{}cpus{}.png'.format(self._trace.plots_dir,
197                                           self._trace.plots_prefix, label2)
198        pl.savefig(figname, bbox_inches='tight')
199
200    def plotContextSwitch(self):
201        """
202        Plot histogram of context switches on each CPU.
203        """
204        if not self._trace.hasEvents('sched_switch'):
205            self._log.warning('Events [sched_switch] not found, plot DISABLED!')
206            return
207
208        ctx_sw_df = self._dfg_context_switches()
209        ax = ctx_sw_df.plot.bar(title="Per-CPU Task Context Switches",
210                                legend=False,
211                                figsize=(16, 8))
212        ax.grid()
213
214# vim :set tabstop=4 shiftwidth=4 expandtab
215