From 3f6f9151164e8358ff0278473d12271178fd5ee9 Mon Sep 17 00:00:00 2001 From: Douglas RAILLARD Date: Thu, 11 Jul 2019 17:55:45 +0100 Subject: [PATCH 1/2] lisa.datautils: Add df_filter() Simple filter helper that allows selecting rows matching specific values in columns. --- lisa/datautils.py | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/lisa/datautils.py b/lisa/datautils.py index 38eb070ab..a7a4a97a3 100644 --- a/lisa/datautils.py +++ b/lisa/datautils.py @@ -173,6 +173,27 @@ def df_squash(df, start, end, column='delta'): return res_df +def df_filter(df, filter_columns): + """ + Filter the content of a dataframe. + + :param df: DataFrame to filter + :type df: pandas.DataFrame + + :param filter_columns: Dict of `{"column": value)` that rows has to match + to be selected. + :type filter_columns: dict(str, object) + """ + key = functools.reduce( + operator.and_, + ( + df[col] == val + for col, val in filter_columns.items() + ) + ) + + return df[key] + def df_merge(df_list, drop_columns=None, drop_inplace=False, filter_columns=None): """ Merge a list of :class:`pandas.DataFrame`, keeping the index sorted. @@ -195,18 +216,8 @@ def df_merge(df_list, drop_columns=None, drop_inplace=False, filter_columns=None drop_columns = drop_columns if drop_columns else [] if filter_columns: - def filter_df(df): - key = functools.reduce( - operator.and_, - ( - df[col] == val - for col, val in filter_columns.items() - ) - ) - return df[key] - df_list = [ - filter_df(df) + df_filter(df, filter_columns) for df in df_list ] -- GitLab From 1e2af33e73705488a82e3d22976d35274a66caff Mon Sep 17 00:00:00 2001 From: Douglas RAILLARD Date: Tue, 11 Jun 2019 21:26:44 +0100 Subject: [PATCH 2/2] lisa.analysis: Add notebook analysis Allow looking up attributes in the __main__ module, and give a simple way of creating plot methods. Also has a plot_event_field() method that allows straightforward plotting of fields of ftrace events. --- lisa/analysis/notebook.py | 115 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 lisa/analysis/notebook.py diff --git a/lisa/analysis/notebook.py b/lisa/analysis/notebook.py new file mode 100644 index 000000000..848b67f09 --- /dev/null +++ b/lisa/analysis/notebook.py @@ -0,0 +1,115 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (C) 2019, 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. +# + +""" Notebook Analysis Module """ + +import sys +import pandas as pd +import functools +import operator + +import __main__ as main + +from lisa.utils import memoized +from lisa.analysis.base import TraceAnalysisBase +from lisa.trace import requires_events +from lisa.datautils import df_refit_index, df_filter + + +class NotebookAnalysis(TraceAnalysisBase): + """ + Support for custom Notebook-defined plots + + Attribute lookup will be resolved in ``__main__`` module, which contains + all names created in cells of Jupyter notebooks. + + Functions named ``plot_*`` have a special behavior: they are expected to + take a :class:`lisa.trace.Trace` as first parameter and a named parameter + :class:`matplotlib.axes.Axes` ``axis`` parameter to plot on. + + example:: + + from lisa.trace import Trace + trace = Trace('trace.dat', events=['sched_switch']) + + # Define a plot method in any cell + def plot_foo(trace, y, axis): + print('Plotting horizontal line at level: {}'.format(y)) + axis.axhline(y=y) + + # Just lookup the plot function + trace.analysis.notebook.plot_foo(3) + + """ + + name = 'notebook' + + def __getattr__(self, attr): + val = getattr(main, attr) + + if attr.startswith('plot_'): + f = val + # swallow "local_fig" as it is usually not needed for the notebook + # usage and pass the trace directly instead of the analysis + @TraceAnalysisBase.plot_method(return_axis=False) + @functools.wraps(f) + def wrapper(self, *args, local_fig, **kwargs): + return f(self.trace, *args, **kwargs) + + val = wrapper + + if callable(val): + # bind the function to the analysis instance to give a bound method + return val.__get__(self, type(self)) + else: + return val + + + @TraceAnalysisBase.plot_method(return_axis=False) + def plot_event_field(self, event, field, axis, local_fig, filter_columns=None, filter_f=None): + """ + Plot a signal represented by the filtered values of a field of an event. + + :param event: FTrace event name of interest. + :type event: str + + :param field: Name of the field of ``event``. + :type field: str + + :param filter_columns: Pre-filter the dataframe using + :func:`lisa.datautils.df_filter` + :type filter_columns: dict or None + + :param filter_f: Function used to filter the dataframe of the event. + The function must take a dataframe as only parameter and return + a filtered dataframe. It is applied after ``field_values`` filter. + :type filter_f: collections.abc.Callable + """ + trace = self.trace + df = trace.df_events(event) + + if filter_columns: + df = df_filter(df, filter_columns) + + if filter_f: + df = filter_f(df) + + df = df_refit_index(df, trace.start, trace.end) + df[[field]].plot(ax=axis, drawstyle='steps-post') + + +# vim :set tabstop=4 shiftwidth=4 expandtab textwidth=80 -- GitLab