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 import glob 19 import matplotlib.gridspec as gridspec 20 import matplotlib.pyplot as plt 21 import numpy as np 22 import os 23 import pandas as pd 24 import pylab as pl 25 import re 26 import sys 27 import trappy 28 import logging 29 30 # Regexp to match an rt-app generated logfile 31 TASK_NAME_RE = re.compile('.*\/rt-app-(.+)-[0-9]+.log') 32 33 class PerfAnalysis(object): 34 35 def __init__(self, datadir, tasks=None): 36 37 # Dataframe of all tasks performance data 38 self.perf_data = {} 39 40 # Folder containing all rt-app data 41 self.datadir = None 42 43 # Setup logging 44 self._log = logging.getLogger('PerfAnalysis') 45 46 # Load performance data generated by rt-app workloads 47 self.__loadRTAData(datadir, tasks) 48 49 # Keep track of the datadir from where data have been loaded 50 if len(self.perf_data) == 0: 51 raise ValueError('No performance data found on folder [{0:s}]'\ 52 .format(datadir)) 53 54 self.datadir = datadir 55 56 def __taskNameFromLog(self, logfile): 57 tname_match = re.search(TASK_NAME_RE, logfile) 58 if tname_match is None: 59 raise ValueError('The logfile [{0:s}] is not from rt-app'\ 60 .format(logfile)) 61 return tname_match.group(1) 62 63 def __logfileFromTaskName(self, taskname): 64 for logfile in glob.glob( 65 '{0:s}/rt-app-{1:s}.log'.format(self.datadir, taskname)): 66 return logfile 67 raise ValueError('No rt-app logfile found for task [{0:s}]'\ 68 .format(taskname)) 69 70 def tasks(self): 71 """ 72 Return the list of tasks for which performance data have been loaded 73 """ 74 if self.datadir is None: 75 raise ValueError("rt-app performance data not (yet) loaded") 76 return self.perf_data.keys() 77 78 def logfile(self, task): 79 """ 80 Return the logfile for the specified task 81 """ 82 if task not in self.perf_data: 83 raise ValueError('No logfile loaded for task [{0:s}]'\ 84 .format(task)) 85 return self.perf_data[task]['logfile'] 86 87 def df(self, task): 88 """ 89 Return the PANDAS dataframe with the performance data for the 90 specified task 91 """ 92 if self.datadir is None: 93 raise ValueError("rt-app performance data not (yet) loaded") 94 if task not in self.perf_data: 95 raise ValueError('No dataframe loaded for task [{0:s}]'\ 96 .format(task)) 97 return self.perf_data[task]['df'] 98 99 def __loadRTAData(self, datadir, tasks): 100 """ 101 Load peformance data of an rt-app workload 102 """ 103 104 if tasks is None: 105 # Lookup for all rt-app logfile into the specified datadir 106 for logfile in glob.glob('{0:s}/rt-app-*.log'.format(datadir)): 107 task_name = self.__taskNameFromLog(logfile) 108 self.perf_data[task_name] = {} 109 self.perf_data[task_name]['logfile'] = logfile 110 self._log.debug('Found rt-app logfile for task [%s]', task_name) 111 else: 112 # Lookup for specified rt-app task logfile into specified datadir 113 for task in tasks: 114 logfile = self.__logfileFromTaskName(task) 115 self.perf_data[task_name] = {} 116 self.perf_data[task_name]['logfile'] = logfile 117 self._log.debug('Found rt-app logfile for task [%s]', task_name) 118 119 # Load all the found logfile into a dataset 120 for task in self.perf_data.keys(): 121 self._log.debug('Loading dataframe for task [%s]...', task) 122 df = pd.read_table(self.logfile(task), 123 sep='\s+', 124 skiprows=1, 125 header=0, 126 usecols=[1,2,3,4,7,8,9,10], 127 names=[ 128 'Cycles', 'Run' ,'Period', 'Timestamp', 129 'Slack', 'CRun', 'CPeriod', 'WKPLatency' 130 ]) 131 # Normalize time to [s] with origin on the first event 132 start_time = df['Timestamp'][0]/1e6 133 df['Time'] = df['Timestamp']/1e6 - start_time 134 df.set_index(['Time'], inplace=True) 135 # Add performance metrics column, performance is defined as: 136 # slack 137 # perf = ------------- 138 # period - run 139 df['PerfIndex'] = df['Slack'] / (df['CPeriod'] - df['CRun']) 140 141 # Keep track of the loaded dataframe 142 self.perf_data[task]['df'] = df 143 144 def plotPerf(self, task, title=None): 145 """ 146 Plot the Latency/Slack and Performance data for the specified task 147 """ 148 # Grid 149 gs = gridspec.GridSpec(2, 2, height_ratios=[4,1], width_ratios=[3,1]); 150 gs.update(wspace=0.1, hspace=0.1); 151 # Figure 152 plt.figure(figsize=(16, 2*6)); 153 if title: 154 plt.suptitle(title, y=.97, fontsize=16, 155 horizontalalignment='center'); 156 # Plot: Slack and Latency 157 axes = plt.subplot(gs[0,0]); 158 axes.set_title('Task [{0:s}] (start) Latency and (completion) Slack'\ 159 .format(task)); 160 data = self.df(task)[['Slack', 'WKPLatency']] 161 data.plot(ax=axes, drawstyle='steps-post', style=['b', 'g']); 162 # axes.set_xlim(x_min, x_max); 163 axes.xaxis.set_visible(False); 164 # Plot: Performance 165 axes = plt.subplot(gs[1,0]); 166 axes.set_title('Task [{0:s}] Performance Index'.format(task)); 167 data = self.df(task)[['PerfIndex',]] 168 data.plot(ax=axes, drawstyle='steps-post'); 169 axes.set_ylim(0, 2); 170 # axes.set_xlim(x_min, x_max); 171 # Plot: Slack Histogram 172 axes = plt.subplot(gs[0:2,1]); 173 data = self.df(task)[['PerfIndex',]] 174 data.hist(bins=30, ax=axes, alpha=0.4); 175 # axes.set_xlim(x_min, x_max); 176 pindex_avg = data.mean()[0]; 177 pindex_std = data.std()[0]; 178 self._log.info('PerfIndex, Task [%s] avg: %.2f, std: %.2f', 179 task, pindex_avg, pindex_std) 180 axes.axvline(pindex_avg, color='b', linestyle='--', linewidth=2); 181 182 183 # Save generated plots into datadir 184 figname = '{}/task_perf_{}.png'.format(self.datadir, task) 185 pl.savefig(figname, bbox_inches='tight') 186 187