Home | History | Annotate | Download | only in analysis
      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