# SPDX-License-Identifier: Apache-2.0 # # Copyright (C) 2015, 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. # import glob import matplotlib.gridspec as gridspec import matplotlib.pyplot as plt import numpy as np import os import pandas as pd import pylab as pl import re import sys import trappy import logging # Regexp to match an rt-app generated logfile TASK_NAME_RE = re.compile('.*\/rt-app-(.+)-[0-9]+.log') class PerfAnalysis(object): def __init__(self, datadir, tasks=None): # Dataframe of all tasks performance data self.perf_data = {} # Folder containing all rt-app data self.datadir = None # Setup logging self._log = logging.getLogger('PerfAnalysis') # Load performance data generated by rt-app workloads self.__loadRTAData(datadir, tasks) # Keep track of the datadir from where data have been loaded if len(self.perf_data) == 0: raise ValueError('No performance data found on folder [{0:s}]'\ .format(datadir)) self.datadir = datadir def __taskNameFromLog(self, logfile): tname_match = re.search(TASK_NAME_RE, logfile) if tname_match is None: raise ValueError('The logfile [{0:s}] is not from rt-app'\ .format(logfile)) return tname_match.group(1) def __logfileFromTaskName(self, taskname): for logfile in glob.glob( '{0:s}/rt-app-{1:s}.log'.format(self.datadir, taskname)): return logfile raise ValueError('No rt-app logfile found for task [{0:s}]'\ .format(taskname)) def tasks(self): """ Return the list of tasks for which performance data have been loaded """ if self.datadir is None: raise ValueError("rt-app performance data not (yet) loaded") return self.perf_data.keys() def logfile(self, task): """ Return the logfile for the specified task """ if task not in self.perf_data: raise ValueError('No logfile loaded for task [{0:s}]'\ .format(task)) return self.perf_data[task]['logfile'] def df(self, task): """ Return the PANDAS dataframe with the performance data for the specified task """ if self.datadir is None: raise ValueError("rt-app performance data not (yet) loaded") if task not in self.perf_data: raise ValueError('No dataframe loaded for task [{0:s}]'\ .format(task)) return self.perf_data[task]['df'] def __loadRTAData(self, datadir, tasks): """ Load peformance data of an rt-app workload """ if tasks is None: # Lookup for all rt-app logfile into the specified datadir for logfile in glob.glob('{0:s}/rt-app-*.log'.format(datadir)): task_name = self.__taskNameFromLog(logfile) self.perf_data[task_name] = {} self.perf_data[task_name]['logfile'] = logfile self._log.debug('Found rt-app logfile for task [%s]', task_name) else: # Lookup for specified rt-app task logfile into specified datadir for task in tasks: logfile = self.__logfileFromTaskName(task) self.perf_data[task_name] = {} self.perf_data[task_name]['logfile'] = logfile self._log.debug('Found rt-app logfile for task [%s]', task_name) # Load all the found logfile into a dataset for task in self.perf_data.keys(): self._log.debug('Loading dataframe for task [%s]...', task) df = pd.read_table(self.logfile(task), sep='\s+', skiprows=1, header=0, usecols=[1,2,3,4,7,8,9,10], names=[ 'Cycles', 'Run' ,'Period', 'Timestamp', 'Slack', 'CRun', 'CPeriod', 'WKPLatency' ]) # Normalize time to [s] with origin on the first event start_time = df['Timestamp'][0]/1e6 df['Time'] = df['Timestamp']/1e6 - start_time df.set_index(['Time'], inplace=True) # Add performance metrics column, performance is defined as: # slack # perf = ------------- # period - run df['PerfIndex'] = df['Slack'] / (df['CPeriod'] - df['CRun']) # Keep track of the loaded dataframe self.perf_data[task]['df'] = df def plotPerf(self, task, title=None): """ Plot the Latency/Slack and Performance data for the specified task """ # Grid gs = gridspec.GridSpec(2, 2, height_ratios=[4,1], width_ratios=[3,1]); gs.update(wspace=0.1, hspace=0.1); # Figure plt.figure(figsize=(16, 2*6)); if title: plt.suptitle(title, y=.97, fontsize=16, horizontalalignment='center'); # Plot: Slack and Latency axes = plt.subplot(gs[0,0]); axes.set_title('Task [{0:s}] (start) Latency and (completion) Slack'\ .format(task)); data = self.df(task)[['Slack', 'WKPLatency']] data.plot(ax=axes, drawstyle='steps-post', style=['b', 'g']); # axes.set_xlim(x_min, x_max); axes.xaxis.set_visible(False); # Plot: Performance axes = plt.subplot(gs[1,0]); axes.set_title('Task [{0:s}] Performance Index'.format(task)); data = self.df(task)[['PerfIndex',]] data.plot(ax=axes, drawstyle='steps-post'); axes.set_ylim(0, 2); # axes.set_xlim(x_min, x_max); # Plot: Slack Histogram axes = plt.subplot(gs[0:2,1]); data = self.df(task)[['PerfIndex',]] data.hist(bins=30, ax=axes, alpha=0.4); # axes.set_xlim(x_min, x_max); pindex_avg = data.mean()[0]; pindex_std = data.std()[0]; self._log.info('PerfIndex, Task [%s] avg: %.2f, std: %.2f', task, pindex_avg, pindex_std) axes.axvline(pindex_avg, color='b', linestyle='--', linewidth=2); # Save generated plots into datadir figname = '{}/task_perf_{}.png'.format(self.datadir, task) pl.savefig(figname, bbox_inches='tight')