# 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')