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
20import matplotlib.gridspec as gridspec
21import matplotlib.pyplot as plt
22from matplotlib import font_manager as fm
23import pandas as pd
24import pylab as pl
25import operator
26from trappy.utils import listify
27from devlib.utils.misc import memoized
28import numpy as np
29import logging
30import trappy
31from analysis_module import AnalysisModule
32from trace import ResidencyTime, ResidencyData
33from bart.common.Utils import area_under_curve
34
35class 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
52def process_pivot(pivot_list, pivot):
53    if not pivot_list:
54        return True
55    return pivot in pivot_list
56
57def 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
115class 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