From 92cbd643886c30ddfe8ecb80dc28e33f23d1b6d9 Mon Sep 17 00:00:00 2001 From: Michele Di Giorgio Date: Wed, 14 Sep 2016 15:11:34 +0100 Subject: [PATCH 1/4] libs/utils/trace: get time range from TRAPpy Computation of the time range is already done in TRAPpy by the get_duration() method. Avoid perfoming the same computation in LISA by simply calling that method from the FTrace object. Signed-off-by: Michele Di Giorgio --- libs/utils/trace.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/libs/utils/trace.py b/libs/utils/trace.py index 80b6133d7..910573374 100644 --- a/libs/utils/trace.py +++ b/libs/utils/trace.py @@ -319,18 +319,7 @@ class Trace(object): """ Compute time axis range, considering all the parsed events. """ - ts = sys.maxint - te = 0 - - for events in self.available_events: - df = self._dfg_trace_event(events) - if len(df) == 0: - continue - if (df.index[0]) < ts: - ts = df.index[0] - if (df.index[-1]) > te: - te = df.index[-1] - self.time_range = te - ts + self.time_range = self.ftrace.get_duration() self._log.debug('Collected events spans a %.3f [s] time interval', self.time_range) -- GitLab From 68afb3a0d9742587b2d63caa571de058d6a86ade Mon Sep 17 00:00:00 2001 From: Michele Di Giorgio Date: Wed, 14 Sep 2016 16:10:37 +0100 Subject: [PATCH 2/4] libs/utils/trace: refactor computation of the plot window Compute the plot window values in a separate private method and set the window attribute of the trace object accordingly. In case the user does specify any window, the upper bound is set to the maximum duration of the trace, so that we do not have to deal with a NoneType value later in methods that take the plot window into account. Signed-off-by: Michele Di Giorgio --- libs/utils/trace.py | 48 ++++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/libs/utils/trace.py b/libs/utils/trace.py index 910573374..e56dc4c92 100644 --- a/libs/utils/trace.py +++ b/libs/utils/trace.py @@ -85,9 +85,6 @@ class Trace(object): # Trace format self.trace_format = trace_format - # The time window used to limit trace parsing to - self.window = window - # Dynamically registered TRAPpy events self.trappy_cls = {} @@ -130,14 +127,13 @@ class Trace(object): trace_format) self.__computeTimeSpan() - # Minimum and Maximum x_time to use for all plots - self.x_min = 0 - self.x_max = self.time_range + # The time window used to limit trace parsing to a user-defined plot + # window + self.window = self.__computePlotWindow(window, normalize_time) - # Reset x axis time range to full scale - t_min = self.window[0] - t_max = self.window[1] - self.setXTimeRange(t_min, t_max) + # Reset x axis time range to plot window scale + self.x_min = self.window[0] + self.x_max = self.window[1] self.data_frame = TraceData() self._registerDataFrameGetters(self) @@ -260,16 +256,6 @@ class Trace(object): self._sanitize_SchedOverutilized() self._sanitize_CpuFrequency() - # Compute plot window - if not normalize_time: - start = self.window[0] - if self.window[1]: - duration = min(self.ftrace.get_duration(), self.window[1]) - else: - duration = self.ftrace.get_duration() - self.window = (self.ftrace.basetime + start, - self.ftrace.basetime + duration) - def __checkAvailableEvents(self, key=""): """ Internal method used to build a list of available events. @@ -333,6 +319,28 @@ class Trace(object): self._log.debug('Overutilized time: %.6f [s] (%.3f%% of trace time)', self.overutilized_time, self.overutilized_prc) + def __computePlotWindow(self, window, normalize_time): + """ + Compute boundaries for the plot window. + + :param window: time window considered when trace was parsed + :type window: tuple(int or float, int or float) + + :param normalize_time: normalize trace time stamps + :type normalize_time: bool + """ + start = window[0] + if window[1]: + end = min(self.time_range, window[1]) + else: + end = self.time_range + + if not normalize_time: + start += self.ftrace.basetime + end += self.ftrace.basetime + + return (start, end) + def _scanTasks(self, df, name_key='comm', pid_key='pid'): """ Extract tasks names and PIDs from the input data frame. The data frame -- GitLab From 7c0b038ea9378fde42f3462e78796dd714f4b590 Mon Sep 17 00:00:00 2001 From: Michele Di Giorgio Date: Mon, 27 Jun 2016 14:26:17 +0100 Subject: [PATCH 3/4] libs/utils/trace: refactor setXTimeRange() When setting x axis time range it is necessary to take into account non normalized traces as well by setting the boundaries to the plot window. Signed-off-by: Michele Di Giorgio --- libs/utils/trace.py | 33 +++++++++++++++++++++++++-------- tests/lisa/test_trace.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 8 deletions(-) diff --git a/libs/utils/trace.py b/libs/utils/trace.py index e56dc4c92..a42778726 100644 --- a/libs/utils/trace.py +++ b/libs/utils/trace.py @@ -167,14 +167,31 @@ class Trace(object): :param t_max: upper bound :type t_max: int or float """ - if t_min is None: - self.x_min = 0 - else: - self.x_min = t_min - if t_max is None: - self.x_max = self.time_range - else: - self.x_max = t_max + self.x_min = self.window[0] + self.x_max = self.window[1] + + if t_min is not None: + if t_min < self.x_min: + self._log.warning('t_min out of range: ' + 'capping to trace minimum %.6f [s]', + self.x_min) + elif t_min > self.x_max: + raise ValueError('t_min out of range: ' + 'trace boundaries are ({:.6f}, {:.6f}) [s]'\ + .format(self.x_min, self.x_max)) + else: + self.x_min = t_min + if t_max is not None: + if t_max > self.x_max: + self._log.warning('t_max out of range: ' + 'capping to trace maximum %.6f [s]', + self.x_max) + elif t_max < self.x_min: + raise ValueError('t_max out of range: ' + 'trace boundaries are ({:.6f}, {:.6f}) [s]'\ + .format(self.window[0], self.x_max)) + else: + self.x_max = t_max self._log.debug('Set plots time range to (%.6f, %.6f)[s]', self.x_min, self.x_max) diff --git a/tests/lisa/test_trace.py b/tests/lisa/test_trace.py index 30885e91c..b9be7f6ee 100644 --- a/tests/lisa/test_trace.py +++ b/tests/lisa/test_trace.py @@ -78,3 +78,38 @@ class TestTrace(TestCase): self.assertEqual(trace.getTaskByName('father'), [1234]) os.remove(self.test_trace) + + def test_setXTimeRange(self): + """ + TestTrace: setXTimeRange() properly update user-specified trace time + boundaries. + """ + # Test min and max values out of range + t_min = self.trace.window[1] + 10 + with self.assertRaises(ValueError): + self.trace.setXTimeRange(t_min=t_min) + + t_max = self.trace.window[0] - 10 + with self.assertRaises(ValueError): + self.trace.setXTimeRange(t_max=t_max) + + # Test min/max set to valid values in the range + # [trace.window[0], trace.window[1]] + t_min = self.trace.window[0] + 1 + t_max = self.trace.window[1] - 1 + self.trace.setXTimeRange(t_min, t_max) + self.assertEqual(self.trace.x_min, t_min) + self.assertEqual(self.trace.x_max, t_max) + + # Test min/max reset to trace.window[0]/trace.window[1] + self.trace.setXTimeRange() + self.assertEqual(self.trace.x_min, self.trace.window[0]) + self.assertEqual(self.trace.x_max, self.trace.window[1]) + + # Test min value and max capped to trace minimum and maximum + # respectively + t_min = self.trace.window[0] - 10 + t_max = self.trace.window[1] + 10 + self.trace.setXTimeRange(t_min, t_max) + self.assertEqual(self.trace.x_min, self.trace.window[0]) + self.assertEqual(self.trace.x_max, self.trace.window[1]) -- GitLab From 348540c171326362e2c4b623daa76ecfa98d5864 Mon Sep 17 00:00:00 2001 From: Michele Di Giorgio Date: Mon, 27 Jun 2016 14:28:34 +0100 Subject: [PATCH 4/4] libs/utils/analysis: crop data used for analysis according to X range The user can specify a time range to limit the scope of the trace analysis by means of the function setXTimeRange(). This patch provides a private method to crop a pandas Series using the specified range (which by default corresponds to entire duration of the trace). In order to allow this, the memoized decorator must be removed from getClusterFrequencyResidency(). Signed-off-by: Michele Di Giorgio --- libs/utils/analysis/frequency_analysis.py | 7 +++-- libs/utils/trace.py | 37 +++++++++++++++++++++++ tests/lisa/test_trace.py | 15 +++++++++ 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/libs/utils/analysis/frequency_analysis.py b/libs/utils/analysis/frequency_analysis.py index e9fce893f..a48b9a315 100644 --- a/libs/utils/analysis/frequency_analysis.py +++ b/libs/utils/analysis/frequency_analysis.py @@ -446,7 +446,6 @@ class FrequencyAnalysis(AnalysisModule): # Utility Methods ############################################################################### - @memoized def _getFrequencyResidency(self, cluster): """ Get a DataFrame with per cluster frequency residency, i.e. amount of @@ -479,7 +478,8 @@ class FrequencyAnalysis(AnalysisModule): self._log.warning('Cluster frequency is NOT coherent,' 'cannot compute residency!') return None - cluster_freqs = freq_df[freq_df.cpu == _cluster[0]] + cluster_freqs = freq_df[freq_df.cpu == _cluster[0]].frequency + cluster_freqs = self._trace._cropToXTimeRange(cluster_freqs) # Compute TOTAL Time time_intervals = cluster_freqs.index[1:] - cluster_freqs.index[:-1] @@ -490,7 +490,8 @@ class FrequencyAnalysis(AnalysisModule): total_time = total_time.groupby(['frequency']).sum() # Compute ACTIVE Time - cluster_active = self._trace.getClusterActiveSignal(_cluster) + cluster_active = self._getClusterActiveSignal(_cluster) + cluster_active = self._trace._cropToXTimeRange(cluster_active) # In order to compute the active time spent at each frequency we # multiply 2 square waves: diff --git a/libs/utils/trace.py b/libs/utils/trace.py index a42778726..0a2110b2c 100644 --- a/libs/utils/trace.py +++ b/libs/utils/trace.py @@ -871,6 +871,43 @@ class Trace(object): return cluster_active + def _cropToXTimeRange(self, data): + """ + Crop a series of FTrace events to the X time range specified by the + user through setXTimeRange(). + + When cropping a series it might be necessary to include a first and + last element with timestamps equal to the boundaries of the X time + range. + + :param data: series to be cropped + :type data: :mod:`pandas.Series` + """ + if not isinstance(data, pd.Series): + msg = 'Cropping supported only for pandas.Series objects!' + raise ValueError(msg) + + first = None + # Avoid duplicate indexes in case the user specifies an x_min that + # corresponds to the time stamp of an existing event. + if self.x_min not in data.index: + # data[data.index < self.x_min] may return an empty series if x_min + # is lower than the lowest index of the data series. This will + # raise an exception that can be caught and leave first to None, as + # in this case a first element is not needed + try: + first = pd.Series([data[data.index < self.x_min].iloc[-1]], + index=[self.x_min]) + except: pass + last = None + if self.x_max not in data.index: + try: + last = pd.Series([data[data.index <= self.x_max].iloc[-1]], + index=[self.x_max]) + except: pass + data = data.loc[self.x_min:self.x_max] + return pd.concat([first, data, last]) + class TraceData: """ A DataFrame collector exposed to Trace's clients """ diff --git a/tests/lisa/test_trace.py b/tests/lisa/test_trace.py index b9be7f6ee..174e3a87a 100644 --- a/tests/lisa/test_trace.py +++ b/tests/lisa/test_trace.py @@ -113,3 +113,18 @@ class TestTrace(TestCase): self.trace.setXTimeRange(t_min, t_max) self.assertEqual(self.trace.x_min, self.trace.window[0]) self.assertEqual(self.trace.x_max, self.trace.window[1]) + + def test_cropToXTimeRange(self): + """ + TestTrace: cropToXTimeRange() properly crops the data frame to the X + time range specified by the user through setXTimeRange(). + """ + series = self.trace.data_frame.trace_event('sched_switch').prev_state + + t_min = self.trace.window[0] + 2 + t_max = self.trace.window[1] - 2 + self.trace.setXTimeRange(t_min, t_max) + + cropped_series = self.trace._cropToXTimeRange(series) + self.assertTrue(cropped_series.index[0] >= t_min) + self.assertTrue(cropped_series.index[-1] <= t_max) -- GitLab