1 # SPDX-License-Identifier: Apache-2.0 2 # 3 # Copyright (C) 2015, Google, 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 """ Residency Analysis Module """ 19 20 import matplotlib.gridspec as gridspec 21 import matplotlib.pyplot as plt 22 from matplotlib import font_manager as fm 23 import pandas as pd 24 import pylab as pl 25 import operator 26 from trappy.utils import listify 27 from devlib.utils.misc import memoized 28 import numpy as np 29 import logging 30 import trappy 31 from analysis_module import AnalysisModule 32 from trace import ResidencyTime, ResidencyData 33 from bart.common.Utils import area_under_curve 34 35 class Residency(object): 36 def __init__(self, pivot, time): 37 self.last_start_time = time 38 self.total_time = np.float64(0.0) 39 # Keep track of last seen start times 40 self.start_time = -1 41 # Keep track of maximum runtime seen 42 self.end_time = -1 43 self.max_runtime = -1 44 # When Residency is created for the first time, 45 # its running (switch in) 46 self.running = 1 47 48 ################################################################ 49 # Callback and state machinery # 50 ################################################################ 51 52 def process_pivot(pivot_list, pivot): 53 if not pivot_list: 54 return True 55 return pivot in pivot_list 56 57 def pivot_process_cb(data, args): 58 59 pivot = args[0]['pivot'] 60 if args[0].has_key('pivot_list'): 61 pivot_list = args[0]['pivot_list'] 62 else: 63 pivot_list = [] 64 res_analysis_obj = args[0]['res_analysis_obj'] 65 66 debugg = False if pivot == 'schedtune' else False 67 68 log = res_analysis_obj._log 69 prev_pivot = data['prev_' + pivot] 70 next_pivot = data['next_' + pivot] 71 time = data['Time'] 72 cpu = data['__cpu'] 73 pivot_res = res_analysis_obj.residency[pivot][int(cpu)] 74 75 if debugg: 76 print "{}: {} {} -> {} {}".format(time, prev_pivot, data['prev_comm'], \ 77 next_pivot, data['next_comm']) 78 79 # prev pivot processing (switch out) 80 if pivot_res.has_key(prev_pivot) and process_pivot(pivot_list, prev_pivot): 81 pr = pivot_res[prev_pivot] 82 if pr.running == 1: 83 pr.running = 0 84 runtime = time - pr.last_start_time 85 if runtime > pr.max_runtime: 86 pr.max_runtime = runtime 87 pr.start_time = pr.last_start_time 88 pr.end_time = time 89 pr.total_time += runtime 90 if debugg: log.info('adding to total time {}, new total {}'.format(runtime, pr.total_time)) 91 92 else: 93 log.info('switch out seen while no switch in {}'.format(prev_pivot)) 94 elif process_pivot(pivot_list, prev_pivot): 95 log.info('switch out seen while no switch in {}'.format(prev_pivot)) 96 97 # Filter the next pivot 98 if not process_pivot(pivot_list, next_pivot): 99 return 100 101 # next_pivot processing for new pivot switch in 102 if not pivot_res.has_key(next_pivot): 103 pr = Residency(next_pivot, time) 104 pivot_res[next_pivot] = pr 105 return 106 107 # next_pivot processing for previously discovered pid (switch in) 108 pr = pivot_res[next_pivot] 109 if pr.running == 1: 110 log.info('switch in seen for already running task {}'.format(next_pivot)) 111 return 112 pr.running = 1 113 pr.last_start_time = time 114 115 class ResidencyAnalysis(AnalysisModule): 116 """ 117 Support for calculating residencies 118 119 :param trace: input Trace object 120 :type trace: :mod:`libs.utils.Trace` 121 """ 122 123 def __init__(self, trace): 124 self.pid_list = [] 125 self.pid_tgid = {} 126 # Hastable of pivot -> array of entities (cores) mapping 127 # Each element of the array represents a single entity (core) to calculate on 128 # Each array entry is a hashtable, for ex: residency['pid'][0][123] 129 # is the residency of PID 123 on core 0 130 self.residency = { } 131 super(ResidencyAnalysis, self).__init__(trace) 132 133 def generate_residency_data(self, pivot_type, pivot_ids): 134 logging.info("Generating residency for {} {}s!".format(len(pivot_ids), pivot_type)) 135 for pivot in pivot_ids: 136 dict_ret = {} 137 total = 0 138 # dict_ret['name'] = self._trace.getTaskByPid(pid)[0] if self._trace.getTaskByPid(pid) else 'UNKNOWN' 139 # dict_ret['tgid'] = -1 if not self.pid_tgid.has_key(pid) else self.pid_tgid[pid] 140 for cpunr in range(0, self.ncpus): 141 cpu_key = 'cpu_{}'.format(cpunr) 142 try: 143 dict_ret[cpu_key] = self.residency[pivot_type][int(cpunr)][pivot].total_time 144 except: 145 dict_ret[cpu_key] = 0 146 total += dict_ret[cpu_key] 147 148 dict_ret['total'] = total 149 yield dict_ret 150 151 @memoized 152 def _dfg_cpu_residencies(self, pivot, pivot_list=[], event_name='sched_switch'): 153 # Build a list of pids 154 df = self._dfg_trace_event('sched_switch') 155 df = df[['__pid']].drop_duplicates(keep='first') 156 for s in df.iterrows(): 157 self.pid_list.append(s[1]['__pid']) 158 159 # Build the pid_tgid map (skip pids without tgid) 160 df = self._dfg_trace_event('sched_switch') 161 df = df[['__pid', '__tgid']].drop_duplicates(keep='first') 162 df_with_tgids = df[df['__tgid'] != -1] 163 for s in df_with_tgids.iterrows(): 164 self.pid_tgid[s[1]['__pid']] = s[1]['__tgid'] 165 166 self.pid_tgid[0] = 0 # Record the idle thread as well (pid = tgid = 0) 167 168 self.npids = len(df.index) # How many pids in total 169 self.npids_tgid = len(self.pid_tgid.keys()) # How many pids with tgid 170 self.ncpus = self._trace.ftrace._cpus # How many total cpus 171 172 logging.info("TOTAL number of CPUs: {}".format(self.ncpus)) 173 logging.info("TOTAL number of PIDs: {}".format(self.npids)) 174 logging.info("TOTAL number of TGIDs: {}".format(self.npids_tgid)) 175 176 # Create empty hash tables, 1 per CPU for each each residency 177 self.residency[pivot] = [] 178 for cpunr in range(0, self.ncpus): 179 self.residency[pivot].append({}) 180 181 # Calculate residencies 182 if hasattr(self._trace.data_frame, event_name): 183 df = getattr(self._trace.data_frame, event_name)() 184 else: 185 df = self._dfg_trace_event(event_name) 186 187 kwargs = { 'pivot': pivot, 'res_analysis_obj': self, 'pivot_list': pivot_list } 188 trappy.utils.apply_callback(df, pivot_process_cb, kwargs) 189 190 # Build the pivot id list 191 pivot_ids = [] 192 for cpunr in range(0, len(self.residency[pivot])): 193 res_ht = self.residency[pivot][cpunr] 194 # print res_ht.keys() 195 pivot_ids = pivot_ids + res_ht.keys() 196 197 # Make unique 198 pivot_ids = list(set(pivot_ids)) 199 200 # Now build the final DF! 201 pid_idx = pd.Index(pivot_ids, name=pivot) 202 df = pd.DataFrame(self.generate_residency_data(pivot, pivot_ids), index=pid_idx) 203 df.sort_index(inplace=True) 204 205 logging.info("total time spent by all pids across all cpus: {}".format(df['total'].sum())) 206 logging.info("total real time range of events: {}".format(self._trace.time_range)) 207 return df 208 209 def _dfg_cpu_residencies_cgroup(self, controller, cgroups=[]): 210 return self._dfg_cpu_residencies(controller, pivot_list=cgroups, event_name='sched_switch_cgroup') 211 212 def plot_cgroup(self, controller, cgroup='all', idle=False): 213 """ 214 controller: name of the controller 215 idle: Consider idle time? 216 """ 217 df = self._dfg_cpu_residencies_cgroup(controller) 218 # Plot per-CPU break down for a single CGroup (Single pie plot) 219 if cgroup != 'all': 220 df = df[df.index == cgroup] 221 df = df.drop('total', 1) 222 df = df.apply(lambda x: x*10) 223 224 plt.style.use('ggplot') 225 colors = plt.rcParams['axes.color_cycle'] 226 fig, axes = plt.subplots(nrows=1, ncols=1, figsize=(8,8)) 227 patches, texts, autotexts = axes.pie(df.loc[cgroup], labels=df.columns, autopct='%.2f', colors=colors) 228 axes.set(ylabel='', title=cgroup + ' per CPU percentage breakdown', aspect='equal') 229 230 axes.legend(bbox_to_anchor=(0, 0.5)) 231 proptease = fm.FontProperties() 232 proptease.set_size('x-large') 233 plt.setp(autotexts, fontproperties=proptease) 234 plt.setp(texts, fontproperties=proptease) 235 236 plt.show() 237 return 238 239 # Otherwise, Plot per-CGroup of a Controller down for each CPU 240 if not idle: 241 df = df[pd.isnull(df.index) != True] 242 # Bug in matplot lib causes plotting issues when residency is < 1 243 df = df.apply(lambda x: x*10) 244 plt.style.use('ggplot') 245 colors = plt.rcParams['axes.color_cycle'] 246 fig, axes = plt.subplots(nrows=5, ncols=2, figsize=(12,30)) 247 248 for ax, col in zip(axes.flat, df.columns): 249 ax.pie(df[col], labels=df.index, autopct='%.2f', colors=colors) 250 ax.set(ylabel='', title=col, aspect='equal') 251 252 axes[0, 0].legend(bbox_to_anchor=(0, 0.5)) 253 plt.show() 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 # vim :set tabstop=4 shiftwidth=4 expandtab 272