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