Home | History | Annotate | Download | only in utils
      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