diff --git a/lisa/analysis/frequency.py b/lisa/analysis/frequency.py index 15e4f75450bb5fea7a9f70ef0c8dace007eb3c7b..1c80b79edac03a0cecc0bbeb214a2810703de893 100644 --- a/lisa/analysis/frequency.py +++ b/lisa/analysis/frequency.py @@ -198,7 +198,7 @@ class FrequencyAnalysis(TraceAnalysisBase): # We can't use the pandas average because it's not weighted by # time spent in each frequency, so we have to craft our own. df = self.trace.add_events_deltas(df, inplace=False) - timespan = df.index[-1] - df.index[0] + timespan = self.trace.end - self.trace.start return (df['frequency'] * df['delta']).sum() / timespan diff --git a/lisa/analysis/idle.py b/lisa/analysis/idle.py index f4e3598f0124a7b53da79546e8f0800a61bc8b26..220601f8892d2d48dc98c405518148a46387bdd2 100644 --- a/lisa/analysis/idle.py +++ b/lisa/analysis/idle.py @@ -126,7 +126,7 @@ class IdleAnalysis(TraceAnalysisBase): sr = pd.Series() for cpu in cpus: - cpu_sr = self.trace.get_cpu_active_signal(cpu) + cpu_sr = self.signal_cpu_active(cpu) cpu_sr = cpu_sr[cpu_sr == 1] cpu_sr = cpu_sr.replace(1, cpu) sr = sr.append(cpu_sr) diff --git a/lisa/tests/scheduler/misfit.py b/lisa/tests/scheduler/misfit.py index 808fc2ed43e4d9414069063845e8bbdaec1a3476..4330f202cae1921591ba363086227db4fa3c81bc 100644 --- a/lisa/tests/scheduler/misfit.py +++ b/lisa/tests/scheduler/misfit.py @@ -241,7 +241,9 @@ class StaggeredFinishes(MisfitMigrationBase): """ :returns: A dataframe that describes the idle status (on/off) of 'cpu' """ - active_df = pd.DataFrame(self.trace.get_cpu_active_signal(cpu), columns=['state']) + active_df = pd.DataFrame( + self.trace.analysis.idle.signal_cpu_active(cpu), columns=['state'] + ) self.trace.add_events_deltas(active_df) return active_df diff --git a/lisa/trace.py b/lisa/trace.py index 3675ab4a93b072eb1873afea9b07623a31883e05..24e541141cdb3e876ef74bf076cdca587b6e745e 100644 --- a/lisa/trace.py +++ b/lisa/trace.py @@ -41,7 +41,11 @@ from trappy.utils import listify, handle_duplicate_index NON_IDLE_STATE = -1 -class SubscriptableTrace(abc.ABC): +class TraceBase(abc.ABC): + """ + Base class for common functionalities between :class:`Trace` and :class:`TraceView` + """ + @abc.abstractmethod def get_view(self, window): """ @@ -58,8 +62,41 @@ class SubscriptableTrace(abc.ABC): return self.get_view((window.start, window.stop)) + def add_events_deltas(self, df, col_name='delta', inplace=True): + """ + Store the time between each event in a new dataframe column + + :param df: The DataFrame to operate one + :type df: pandas.DataFrame + + :param col_name: The name of the column to add + :type col_name: str + + :param inplace: Whether to operate on the passed DataFrame, or to use + a copy of it + :type inplace: bool + + This method only really makes sense for events tracking an on/off state + (e.g. overutilized, idle) + """ + if df.empty: + return df + + if col_name in df.columns: + raise RuntimeError("Column {} is already present in the dataframe". + format(col_name)) -class TraceView(Loggable, SubscriptableTrace): + if not inplace: + df = df.copy() + + df.loc[df.index[:-1], col_name] = df.index.values[1:] - df.index.values[:-1] + # Fix the last event, which will have a NaN duration + # Set duration to trace_end - last_event + df.loc[df.index[-1], col_name] = self.end - df.index[-1] + + return df + +class TraceView(Loggable, TraceBase): """ A view on a :class:`Trace` @@ -164,7 +201,7 @@ class TraceView(Loggable, SubscriptableTrace): return self.base_trace.get_view((start, end)) -class Trace(Loggable, SubscriptableTrace): +class Trace(Loggable, TraceBase): """ The Trace object is the LISA trace events parser. @@ -901,43 +938,6 @@ class Trace(Loggable, SubscriptableTrace): return len(self._functions_stats_df) > 0 - @memoized - def get_cpu_active_signal(self, cpu): - """ - Build a square wave representing the active (i.e. non-idle) CPU time, - i.e.: - - cpu_active[t] == 1 if the CPU is reported to be non-idle by cpuidle at - time t - cpu_active[t] == 0 otherwise - - :param cpu: CPU ID - :type cpu: int - - :returns: A :class:`pandas.Series` or ``None`` if the trace contains no - "cpu_idle" events - """ - if not self.has_events('cpu_idle'): - self.get_logger().warning('Events [cpu_idle] not found, ' - 'cannot compute CPU active signal!') - return None - - idle_df = self.df_events('cpu_idle') - cpu_df = idle_df[idle_df.cpu_id == cpu] - - cpu_active = cpu_df.state.apply( - lambda s: 1 if s == NON_IDLE_STATE else 0 - ) - - if cpu_active.empty: - cpu_active = pd.Series([0], index=[self.start]) - elif cpu_active.index[0] != self.start: - entry_0 = pd.Series(cpu_active.iloc[0] ^ 1, index=[self.start]) - cpu_active = pd.concat([entry_0, cpu_active]) - - # Fix sequences of wakeup/sleep events reported with the same index - return handle_duplicate_index(cpu_active) - @memoized def get_peripheral_clock_effective_rate(self, clk_name): logger = self.get_logger() @@ -973,40 +973,6 @@ class Trace(Loggable, SubscriptableTrace): np.where(freq['state'] == 1, freq['rate'], float('nan'))) return freq - def add_events_deltas(self, df, col_name='delta', inplace=True): - """ - Store the time between each event in a new dataframe column - - :param df: The DataFrame to operate one - :type df: pandas.DataFrame - - :param col_name: The name of the column to add - :type col_name: str - - :param inplace: Whether to operate on the passed DataFrame, or to use - a copy of it - :type inplace: bool - - This method only really makes sense for events tracking an on/off state - (e.g. overutilized, idle) - """ - if df.empty: - return df - - if col_name in df.columns: - raise RuntimeError("Column {} is already present in the dataframe". - format(col_name)) - - if not inplace: - df = df.copy() - - df.loc[df.index[:-1], col_name] = df.index.values[1:] - df.index.values[:-1] - # Fix the last event, which will have a NaN duration - # Set duration to trace_end - last_event - df.loc[df.index[-1], col_name] = self.end - df.index[-1] - - return df - @staticmethod def squash_df(df, start, end, column='delta'): """