aboutsummaryrefslogtreecommitdiff
path: root/libs/utils/analysis/residency_analysis.py
diff options
context:
space:
mode:
Diffstat (limited to 'libs/utils/analysis/residency_analysis.py')
-rw-r--r--libs/utils/analysis/residency_analysis.py271
1 files changed, 271 insertions, 0 deletions
diff --git a/libs/utils/analysis/residency_analysis.py b/libs/utils/analysis/residency_analysis.py
new file mode 100644
index 0000000..797e2f4
--- /dev/null
+++ b/libs/utils/analysis/residency_analysis.py
@@ -0,0 +1,271 @@
+# SPDX-License-Identifier: Apache-2.0
+#
+# Copyright (C) 2015, Google, ARM Limited and contributors.
+#
+# 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.
+#
+
+""" Residency Analysis Module """
+
+import matplotlib.gridspec as gridspec
+import matplotlib.pyplot as plt
+from matplotlib import font_manager as fm
+import pandas as pd
+import pylab as pl
+import operator
+from trappy.utils import listify
+from devlib.utils.misc import memoized
+import numpy as np
+import logging
+import trappy
+from analysis_module import AnalysisModule
+from trace import ResidencyTime, ResidencyData
+from bart.common.Utils import area_under_curve
+
+class Residency(object):
+ def __init__(self, pivot, time):
+ self.last_start_time = time
+ self.total_time = np.float64(0.0)
+ # Keep track of last seen start times
+ self.start_time = -1
+ # Keep track of maximum runtime seen
+ self.end_time = -1
+ self.max_runtime = -1
+ # When Residency is created for the first time,
+ # its running (switch in)
+ self.running = 1
+
+################################################################
+# Callback and state machinery #
+################################################################
+
+def process_pivot(pivot_list, pivot):
+ if not pivot_list:
+ return True
+ return pivot in pivot_list
+
+def pivot_process_cb(data, args):
+
+ pivot = args[0]['pivot']
+ if args[0].has_key('pivot_list'):
+ pivot_list = args[0]['pivot_list']
+ else:
+ pivot_list = []
+ res_analysis_obj = args[0]['res_analysis_obj']
+
+ debugg = False if pivot == 'schedtune' else False
+
+ log = res_analysis_obj._log
+ prev_pivot = data['prev_' + pivot]
+ next_pivot = data['next_' + pivot]
+ time = data['Time']
+ cpu = data['__cpu']
+ pivot_res = res_analysis_obj.residency[pivot][int(cpu)]
+
+ if debugg:
+ print "{}: {} {} -> {} {}".format(time, prev_pivot, data['prev_comm'], \
+ next_pivot, data['next_comm'])
+
+ # prev pivot processing (switch out)
+ if pivot_res.has_key(prev_pivot) and process_pivot(pivot_list, prev_pivot):
+ pr = pivot_res[prev_pivot]
+ if pr.running == 1:
+ pr.running = 0
+ runtime = time - pr.last_start_time
+ if runtime > pr.max_runtime:
+ pr.max_runtime = runtime
+ pr.start_time = pr.last_start_time
+ pr.end_time = time
+ pr.total_time += runtime
+ if debugg: log.info('adding to total time {}, new total {}'.format(runtime, pr.total_time))
+
+ else:
+ log.info('switch out seen while no switch in {}'.format(prev_pivot))
+ elif process_pivot(pivot_list, prev_pivot):
+ log.info('switch out seen while no switch in {}'.format(prev_pivot))
+
+ # Filter the next pivot
+ if not process_pivot(pivot_list, next_pivot):
+ return
+
+ # next_pivot processing for new pivot switch in
+ if not pivot_res.has_key(next_pivot):
+ pr = Residency(next_pivot, time)
+ pivot_res[next_pivot] = pr
+ return
+
+ # next_pivot processing for previously discovered pid (switch in)
+ pr = pivot_res[next_pivot]
+ if pr.running == 1:
+ log.info('switch in seen for already running task {}'.format(next_pivot))
+ return
+ pr.running = 1
+ pr.last_start_time = time
+
+class ResidencyAnalysis(AnalysisModule):
+ """
+ Support for calculating residencies
+
+ :param trace: input Trace object
+ :type trace: :mod:`libs.utils.Trace`
+ """
+
+ def __init__(self, trace):
+ self.pid_list = []
+ self.pid_tgid = {}
+ # Hastable of pivot -> array of entities (cores) mapping
+ # Each element of the array represents a single entity (core) to calculate on
+ # Each array entry is a hashtable, for ex: residency['pid'][0][123]
+ # is the residency of PID 123 on core 0
+ self.residency = { }
+ super(ResidencyAnalysis, self).__init__(trace)
+
+ def generate_residency_data(self, pivot_type, pivot_ids):
+ logging.info("Generating residency for {} {}s!".format(len(pivot_ids), pivot_type))
+ for pivot in pivot_ids:
+ dict_ret = {}
+ total = 0
+ # dict_ret['name'] = self._trace.getTaskByPid(pid)[0] if self._trace.getTaskByPid(pid) else 'UNKNOWN'
+ # dict_ret['tgid'] = -1 if not self.pid_tgid.has_key(pid) else self.pid_tgid[pid]
+ for cpunr in range(0, self.ncpus):
+ cpu_key = 'cpu_{}'.format(cpunr)
+ try:
+ dict_ret[cpu_key] = self.residency[pivot_type][int(cpunr)][pivot].total_time
+ except:
+ dict_ret[cpu_key] = 0
+ total += dict_ret[cpu_key]
+
+ dict_ret['total'] = total
+ yield dict_ret
+
+ @memoized
+ def _dfg_cpu_residencies(self, pivot, pivot_list=[], event_name='sched_switch'):
+ # Build a list of pids
+ df = self._dfg_trace_event('sched_switch')
+ df = df[['__pid']].drop_duplicates(keep='first')
+ for s in df.iterrows():
+ self.pid_list.append(s[1]['__pid'])
+
+ # Build the pid_tgid map (skip pids without tgid)
+ df = self._dfg_trace_event('sched_switch')
+ df = df[['__pid', '__tgid']].drop_duplicates(keep='first')
+ df_with_tgids = df[df['__tgid'] != -1]
+ for s in df_with_tgids.iterrows():
+ self.pid_tgid[s[1]['__pid']] = s[1]['__tgid']
+
+ self.pid_tgid[0] = 0 # Record the idle thread as well (pid = tgid = 0)
+
+ self.npids = len(df.index) # How many pids in total
+ self.npids_tgid = len(self.pid_tgid.keys()) # How many pids with tgid
+ self.ncpus = self._trace.ftrace._cpus # How many total cpus
+
+ logging.info("TOTAL number of CPUs: {}".format(self.ncpus))
+ logging.info("TOTAL number of PIDs: {}".format(self.npids))
+ logging.info("TOTAL number of TGIDs: {}".format(self.npids_tgid))
+
+ # Create empty hash tables, 1 per CPU for each each residency
+ self.residency[pivot] = []
+ for cpunr in range(0, self.ncpus):
+ self.residency[pivot].append({})
+
+ # Calculate residencies
+ if hasattr(self._trace.data_frame, event_name):
+ df = getattr(self._trace.data_frame, event_name)()
+ else:
+ df = self._dfg_trace_event(event_name)
+
+ kwargs = { 'pivot': pivot, 'res_analysis_obj': self, 'pivot_list': pivot_list }
+ trappy.utils.apply_callback(df, pivot_process_cb, kwargs)
+
+ # Build the pivot id list
+ pivot_ids = []
+ for cpunr in range(0, len(self.residency[pivot])):
+ res_ht = self.residency[pivot][cpunr]
+ # print res_ht.keys()
+ pivot_ids = pivot_ids + res_ht.keys()
+
+ # Make unique
+ pivot_ids = list(set(pivot_ids))
+
+ # Now build the final DF!
+ pid_idx = pd.Index(pivot_ids, name=pivot)
+ df = pd.DataFrame(self.generate_residency_data(pivot, pivot_ids), index=pid_idx)
+ df.sort_index(inplace=True)
+
+ logging.info("total time spent by all pids across all cpus: {}".format(df['total'].sum()))
+ logging.info("total real time range of events: {}".format(self._trace.time_range))
+ return df
+
+ def _dfg_cpu_residencies_cgroup(self, controller, cgroups=[]):
+ return self._dfg_cpu_residencies(controller, pivot_list=cgroups, event_name='sched_switch_cgroup')
+
+ def plot_cgroup(self, controller, cgroup='all', idle=False):
+ """
+ controller: name of the controller
+ idle: Consider idle time?
+ """
+ df = self._dfg_cpu_residencies_cgroup(controller)
+ # Plot per-CPU break down for a single CGroup (Single pie plot)
+ if cgroup != 'all':
+ df = df[df.index == cgroup]
+ df = df.drop('total', 1)
+ df = df.apply(lambda x: x*10)
+
+ plt.style.use('ggplot')
+ colors = plt.rcParams['axes.color_cycle']
+ fig, axes = plt.subplots(nrows=1, ncols=1, figsize=(8,8))
+ patches, texts, autotexts = axes.pie(df.loc[cgroup], labels=df.columns, autopct='%.2f', colors=colors)
+ axes.set(ylabel='', title=cgroup + ' per CPU percentage breakdown', aspect='equal')
+
+ axes.legend(bbox_to_anchor=(0, 0.5))
+ proptease = fm.FontProperties()
+ proptease.set_size('x-large')
+ plt.setp(autotexts, fontproperties=proptease)
+ plt.setp(texts, fontproperties=proptease)
+
+ plt.show()
+ return
+
+ # Otherwise, Plot per-CGroup of a Controller down for each CPU
+ if not idle:
+ df = df[pd.isnull(df.index) != True]
+ # Bug in matplot lib causes plotting issues when residency is < 1
+ df = df.apply(lambda x: x*10)
+ plt.style.use('ggplot')
+ colors = plt.rcParams['axes.color_cycle']
+ fig, axes = plt.subplots(nrows=5, ncols=2, figsize=(12,30))
+
+ for ax, col in zip(axes.flat, df.columns):
+ ax.pie(df[col], labels=df.index, autopct='%.2f', colors=colors)
+ ax.set(ylabel='', title=col, aspect='equal')
+
+ axes[0, 0].legend(bbox_to_anchor=(0, 0.5))
+ plt.show()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+# vim :set tabstop=4 shiftwidth=4 expandtab