From 8199613621cf9c724bf4e9921b208e3b470187d6 Mon Sep 17 00:00:00 2001 From: Patrick Bellasi Date: Thu, 6 Jun 2019 10:07:41 +0100 Subject: [PATCH 1/5] analysis.rta: add start_time to get_df() When reporting the rtapp events for a task they are all referred to a 0 timebase. This does not allow to compare TraceView data with those events. Add a star_time optional parameter which allows to get a DF referenced to the given time base. Signed-off-by: Patrick Bellasi --- lisa/analysis/rta.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/lisa/analysis/rta.py b/lisa/analysis/rta.py index dc58c1e87..56a1d11e9 100644 --- a/lisa/analysis/rta.py +++ b/lisa/analysis/rta.py @@ -21,6 +21,8 @@ import glob import pandas as pd +from lisa.utils import memoized + from lisa.utils import Loggable from lisa.analysis.base import AnalysisHelpers @@ -160,15 +162,28 @@ class PerfAnalysis(AnalysisHelpers): """ return self.perf_data[task]['logfile'] - def get_df(self, task): + @memoized + def get_df(self, task, start_time=None): """ Return the pandas dataframe with the performance data for the - specified task + specified task. A start time can be specified as a reference for the + first event, for example to align events to a given (see :class:`TraceView`). :param task: Name of the task that we want the performance dataframe of. :type task: str + + :param start_time: The first event time in seconds + :type start_time: float """ - return self.perf_data[task]['df'] + task_df = self.perf_data[task]['df'] + if not start_time: + return task_df + + # Let's keep a copy so that we can still access the original one + task_df = task_df.reset_index() + task_df['Time'] = task_df['Time'] + start_time + + return task_df.set_index('Time') def save_plot(self, figure, filepath=None, img_format=None): # If all logfiles are located in the same folder, use that folder -- GitLab From a68eb4159adcead332dc6ab5d99e402d9fbf60a1 Mon Sep 17 00:00:00 2001 From: Patrick Bellasi Date: Thu, 6 Jun 2019 12:05:43 +0100 Subject: [PATCH 2/5] analysis.rta: add df getter and plotter for activation events This adds a new pair of dataframe getter and plotter to get information about the activations of a specified task. The returned dataframe includes slack and latency information for each activation. The plotter is compliant to other analysis plots and generate activation bands which can be over-imposed to other plots. For both methods, do allow to defined a star_time to align the generated data/plots with other events, for example from a trace. Signed-off-by: Patrick Bellasi --- lisa/analysis/rta.py | 58 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/lisa/analysis/rta.py b/lisa/analysis/rta.py index 56a1d11e9..3b5501fba 100644 --- a/lisa/analysis/rta.py +++ b/lisa/analysis/rta.py @@ -198,6 +198,64 @@ class PerfAnalysis(AnalysisHelpers): default_dir = dirnames.pop() return self._save_plot(figure, default_dir, filepath, img_format) + @memoized + def df_activations(self, task, start_time=None): + """ + Get activation events + + :param task: Name of the task that we want the performance dataframe of. + :type task: str + + :param start_time: The first activation time in seconds + :type start_time: float + + :returns: A :class:`pandas.DataFrame` with index representing the start + time of an activation and these columns: + + * ``Run``: the running time of the activation + * ``End``: the end time of the activation + * ``Slack``: the slack of the activation + * ``WKPLatency``: the wakeup latency of the activation + * ``PerfIndex``: the performance index of the activation + """ + activations_df = self.get_df(task, start_time)[[ + 'Run', 'Slack', 'WKPLatency', 'PerfIndex']].copy() + activations_df['End'] = activations_df.index + activations_df['Run'] / 1e6 + + # Reorder to keep "End" as the second column + return activations_df[['Run', 'End', 'Slack', 'WKPLatency', 'PerfIndex']] + + def plot_activations(self, task, start_time=None, filepath=None, axis=None): + """ + Draw the task's activations colored bands + + :param task: Name of the task that we want the performance dataframe of. + :type task: str + + :param start_time: The first event time in seconds + :type start_time: float + + .. seealso:: :meth:`lisa.analysis.base.AnalysisHelpers.do_plot` + """ + activations_df = self.df_activations(task, start_time) + + # Compute intervals in which the task was running + bands = [(t, activations_df['End'][t]) for t in activations_df.index] + + def plotter(axis, local_fig): + label = 'Activations' + for (start, end) in bands: + axis.axvspan(start, end, alpha=0.1, facecolor='r', label=label) + if label: + label = None + + axis.legend() + + if local_fig: + axis.set_title("Task [{}] activations".format(task)) + + return self.do_plot(plotter, filepath, axis) + def plot_perf(self, task, **kwargs): """ Plot the performance Index -- GitLab From 5a051e83444af258a354a970bb1895e40efe3a28 Mon Sep 17 00:00:00 2001 From: Patrick Bellasi Date: Fri, 7 Jun 2019 12:10:31 +0100 Subject: [PATCH 3/5] analysis.rta: add df getter and plotter for phases RTApp tasks run a sequence of phases each one composed of possibly multiple phases. Each task monitors and log the actual start and end time of each activation, along with the phase configuration parameters. Use the available log information to build a dataframe of phases start and duration time measured from the actual execution. Add a plotter which allows to add colored bands on top of other analysis plots. Signed-off-by: Patrick Bellasi --- lisa/analysis/rta.py | 71 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/lisa/analysis/rta.py b/lisa/analysis/rta.py index 3b5501fba..c22e47c7b 100644 --- a/lisa/analysis/rta.py +++ b/lisa/analysis/rta.py @@ -256,6 +256,77 @@ class PerfAnalysis(AnalysisHelpers): return self.do_plot(plotter, filepath, axis) + @memoized + def df_phases(self, task, start_time=None): + """ + Get phases actual start times and durations + + :param task: Name of the task that we want the phases dataframe of + :type task: def __str__(self): + + :param start_time: The first activation time in seconds + :type start_time: float + + :returns: A :class:`pandas.DataFrame` with index representing the + start time of a phase and these column: + + * ``Duration``: the measured phase duration. + * ``CPeriod``: the configured activation periods for the phase + * ``CRun``: the configured activation run time for the phase + + """ + activation_df = self.get_df(task, start_time) + phase_df = activation_df[['CRun', 'CPeriod']].copy() + + # Shift Run and Periods for phase changes detection + phase_df['PRun'] = phase_df['CRun'].shift(1) + phase_df['PPeriod'] = phase_df['CPeriod'].shift(1) + + # Keep only new phase start events + phase_df = phase_df[(phase_df.CRun != phase_df.PRun) | + (phase_df.CPeriod != phase_df.PPeriod)] + + # Compute duration of each phase, last one requires a "special" treatment + durations = list(phase_df.index[1:] - phase_df.index[:-1]) + last_phase_duration = activation_df.index[-1] - phase_df.index[-1] + \ + activation_df.iloc[-1].Period / 1e6 + + # Compute phase durations + phase_df.loc[:,'Duration'] = durations + [last_phase_duration] + + return phase_df[['Duration', 'CPeriod', 'CRun']] + + def plot_phases(self, task, start_time=None, filepath=None, axis=None): + """ + Draw the task's phases colored bands + + :param task: Name of the task that we want the performance dataframe of. + :type task: str + + :param start_time: The first event time in seconds + :type start_time: float + + .. seealso:: :meth:`lisa.analysis.base.AnalysisHelpers.do_plot` + """ + phases_df = self.df_phases(task, start_time) + + # Compute phases intervals + bands = [(t, t + phases_df['Duration'][t]) for t in phases_df.index] + + def plotter(axis, local_fig): + for idx, (start, end) in enumerate(bands): + color = self.get_next_color(axis) + label = 'Phase_{}:{}'.format(int(phases_df.iloc[idx].CRun /1e3), + int(phases_df.iloc[idx].CPeriod / 1e3)) + axis.axvspan(start, end, alpha=0.1, facecolor=color, label=label) + + axis.legend() + + if local_fig: + axis.set_title("Task [{}] phases".format(task)) + + return self.do_plot(plotter, filepath, axis) + def plot_perf(self, task, **kwargs): """ Plot the performance Index -- GitLab From bc9a0358e87e33a880530e6b67722b07abf50990 Mon Sep 17 00:00:00 2001 From: Patrick Bellasi Date: Fri, 7 Jun 2019 14:49:24 +0100 Subject: [PATCH 4/5] analysis.rta: add support to load logs from a bundle RTApp workloads are usually run from tests which generates a ResultBundle. A ResultBundle contains additional information about an rtapp workload, such has its configuration, the log files directory and an eventually collected trace. Make all these additional info available from the PerfAnalysis by introducing the support to create it from a ResultBundle object. Keep track of the bundle as an internal object to be used, when available, to refine certain default behaviours. Signed-off-by: Patrick Bellasi --- lisa/analysis/rta.py | 78 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 69 insertions(+), 9 deletions(-) diff --git a/lisa/analysis/rta.py b/lisa/analysis/rta.py index c22e47c7b..ae9dc06a2 100644 --- a/lisa/analysis/rta.py +++ b/lisa/analysis/rta.py @@ -40,7 +40,7 @@ class PerfAnalysis(AnalysisHelpers): RTA_LOG_PATTERN = 'rt-app-{task}.log' "Filename pattern matching RTApp log files" - def __init__(self, task_log_map): + def __init__(self, task_log_map, bundle=None): """ Load peformance data of an rt-app workload """ @@ -49,6 +49,15 @@ class PerfAnalysis(AnalysisHelpers): if not task_log_map: raise ValueError('No tasks in the task log mapping') + # When a bundle is provided, sanity check that all logfiles are in its res_dir + if bundle: + resdir = getattr(bundle, 'res_dir', None) + if not resdir: + return None + if not all([logfile.startswith(resdir) + for _, logfile in task_log_map.items()]): + return None + for task_name, logfile in task_log_map.items(): logger.debug('rt-app task [{}] logfile: {}'.format( task_name, logfile @@ -61,12 +70,28 @@ class PerfAnalysis(AnalysisHelpers): } for task_name, logfile in task_log_map.items() } + self._bundle = bundle + + @classmethod + def _log_dir_from_bundle(cls, bundle, default=None): + if bundle: + rtapp_profile = getattr(bundle, 'rtapp_profile', None) + if not rtapp_profile: + return None + log_dir = getattr(bundle, 'res_dir', None) + if log_dir: + return log_dir + return default @classmethod - def from_log_files(cls, rta_logs): + def from_log_files(cls, rta_logs, bundle=None): """ Build a :class:`PerfAnalysis` from a sequence of RTApp log files + If a :class:`ResultBundle` is provided as the `bundle` parameter, the + `bundle`'s `log_dir` is used to ensure the specified log files are from + that folder. + :param rta_logs: sequence of path to log files :type rta_logs: list(str) """ @@ -83,32 +108,54 @@ class PerfAnalysis(AnalysisHelpers): find_task_name(logfile): logfile for logfile in rta_logs } - return cls(task_log_map) + return cls(task_log_map, bundle) @classmethod - def from_dir(cls, log_dir): + def from_dir(cls, log_dir=None, bundle=None): """ - Build a :class:`PerfAnalysis` from a folder path + Build a :class:`PerfAnalysis` from a folder path or :class:ResultBundle + + One among the `log_dir` or the `bundle` parameter must be provided. + If a :class:`ResultBundle` is provided as the `bundle` parameter, the + `bundle`'s `log_dir` is used for log files search. :param log_dir: Folder containing RTApp log files :type log_dir: str + + :param bundle: A :class:ResultBundle containing information on the log folder + :type bundle: str """ + log_dir = cls._log_dir_from_bundle(bundle, log_dir) + if not log_dir: + return None + rta_logs = glob.glob(os.path.join( log_dir, cls.RTA_LOG_PATTERN.format(task='*'), )) - return cls.from_log_files(rta_logs) + return cls.from_log_files(rta_logs, bundle) @classmethod - def from_task_names(cls, task_names, log_dir): + def from_task_names(cls, task_names, log_dir=None, bundle=None): """ - Build a :class:`PerfAnalysis` from a list of task names + Build a :class:`PerfAnalysis` from a list of task names and a log_dir or :class:Result. + + One among the `log_dir` or the `bundle` parameter must be provided. + If a :class:`ResultBundle` is provided as the `bundle` parameter, the + `bundle`'s `log_dir` is used for log files search. :param task_names: List of task names to look for :type task_names: list(str) :param log_dir: Folder containing RTApp log files :type log_dir: str + + :param bundle: A :class:ResultBundle containing information on the log folder + :type bundle: str """ + log_dir = cls._log_dir_from_bundle(bundle, log_dir) + if not log_dir: + return None + def find_log_file(task_name, log_dir): log_file = os.path.join(log_dir, cls.RTA_LOG_PATTERN.format(task_name)) if not os.path.isfile(log_file): @@ -121,7 +168,20 @@ class PerfAnalysis(AnalysisHelpers): task_name: find_log_file(task, log_dir) for task_name in tasks } - return cls(task_log_map) + return cls(task_log_map, bundle) + + @classmethod + def from_bundle(cls, bundle): + """ + Build a :class:`PerfAnalysis` from a :class:`ResultBundle` + + A :class:`ResultBundle` obtained from the execution of an + :class:`RTApp` workload can be used to get access to the rtapp + generated logfile information. + + + """ + return cls.from_dir(bundle=bundle) @staticmethod def _parse_df(logfile): -- GitLab From d344ae4585a763af20bd822f0862dde6b916804e Mon Sep 17 00:00:00 2001 From: Patrick Bellasi Date: Fri, 7 Jun 2019 15:12:13 +0100 Subject: [PATCH 5/5] analysis.rta: use bundle start_time by default When a ResultBundle is known, and not otherwise specified, use the bundle's trace start time for dataframe getters and plotters. Signed-off-by: Patrick Bellasi --- lisa/analysis/rta.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lisa/analysis/rta.py b/lisa/analysis/rta.py index ae9dc06a2..ae26702ab 100644 --- a/lisa/analysis/rta.py +++ b/lisa/analysis/rta.py @@ -222,6 +222,15 @@ class PerfAnalysis(AnalysisHelpers): """ return self.perf_data[task]['logfile'] + @memoized + def _start_time_from_bundle(self): + if not self._bundle: + return None + trace = getattr(self._bundle, 'trace', None) + if not trace: + return None + return trace.start + @memoized def get_df(self, task, start_time=None): """ @@ -236,6 +245,9 @@ class PerfAnalysis(AnalysisHelpers): :type start_time: float """ task_df = self.perf_data[task]['df'] + + if start_time is None: + start_time = self._start_time_from_bundle() if not start_time: return task_df -- GitLab