From 570322710623399604278b66110ebc0bc3d15317 Mon Sep 17 00:00:00 2001 From: Morten Rasmussen Date: Tue, 13 Feb 2024 13:54:40 +0000 Subject: [PATCH] [Experimental] Interrupt handler trace analysis support FEATURE Interrupt handler trace analysis based on ftrace events 'irq_handler_entry' and 'irq_handler_exit'. Provide functions to extract interrupt handler, activity, and plot activity. The analysis is implemented using polars. --- lisa/analysis/irq.py | 163 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 lisa/analysis/irq.py diff --git a/lisa/analysis/irq.py b/lisa/analysis/irq.py new file mode 100644 index 000000000..dd28873cc --- /dev/null +++ b/lisa/analysis/irq.py @@ -0,0 +1,163 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (C) 2024, 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. +# + +import polars as pl +import holoviews as hv +from bokeh.models import HoverTool + +from lisa.analysis.base import TraceAnalysisBase +from lisa.trace import requires_events + +class IrqAnalysis(TraceAnalysisBase): + """ + Support for interrupt analysis. + + :param trace: input Trace object + :type trace: lisa.trace.Trace + """ + + name = 'irq' + + @requires_events('irq_handler_entry', 'irq_handler_exit') + def df_irq_handler(self, irq, handler_name=None): + """ + Returns dataframe tracking irq handler activity + + :param irq: intid + :type irq: int + + :param handler_name: Name of irq handler + :type handler_name: str + """ + trace = self.trace + + df_irq_entry = pl.from_pandas(trace.df_event('irq_handler_entry'), include_index=True) + df_irq_exit = pl.from_pandas(trace.df_event('irq_handler_exit'), include_index=True) + + if not handler_name: + handlers = self.irq_handlers(as_pl=True) + if handlers.group_by('irq').len().filter(pl.col('irq') == irq)[0,'len'] > 1: + raise ValueError(f'Multiple handlers for irq={irq}. Please specify handler_name.') + else: + handler_name = handlers.filter(pl.col('irq') == irq)[0, 'name'] + + df_entry = df_irq_entry.select( + pl.col(['Time', '__cpu', 'irq', 'name'])).filter( + pl.col('irq') == irq, + pl.col('name') == handler_name).with_columns( + pl.lit(1).alias('active')) + + df_exit = df_irq_exit.select(pl.col(['Time', '__cpu', 'irq'])).filter( + pl.col('irq') == irq).with_columns( + pl.lit("").alias('name'), + pl.lit(0).alias('active')) + + df = df_entry.merge_sorted(df_exit, key='Time').with_columns( + duration=pl.col('Time').diff(n=-1).abs()) + + df = df.with_columns(rle_id=pl.col('active').rle_id()) + + return df.with_columns(first=pl.col('rle_id').is_first_distinct()).filter( + pl.col('first') == True).select(pl.exclude(['rle_id', 'first'])) + + @requires_events('irq_handler_entry') + def irq_handlers(self, as_pl=False): + """ + Returns dict of interrupt handlers called in trace with intid as key. + Note that each intid may trigger multiple handlers. + + :param as_pl: Return a :class:`polars.DataFrame` instead for `dict` + :type as_pl: Boolean + """ + df_irq_entry = pl.from_pandas(self.trace.df_event('irq_handler_entry'), include_index=True) + + if as_pl: + return df_irq_entry.select(['irq', 'name']).unique().sort(by='irq') + return df_irq_entry.select(['irq', 'name']).unique().sort(by='irq').to_dict() + + def df_irq_handler_stats(self): + trace = self.trace + + handlers = self.irq_handlers(as_pl=True) + trace_duration = trace.time_range + + stats = [] + + for row in handlers.iter_rows(): + df = self.df_irq_handler(row[0], row[1]).filter((pl.col('active') == 1)) + + irq_stats = df.select( + pl.col('duration').count().alias('count'), + (pl.col('duration').count()/trace_duration).alias('rate'), + (pl.col('duration').mean()*1e6).alias('mean_us'), + (pl.col('duration').median()*1e6).alias('median_us'), + (pl.col('duration').std()*1e6).alias('std_us'), + (pl.col('duration').min()*1e6).alias('min_us'), + (pl.col('duration').max()*1e6).alias('max_us'), + (pl.col('duration').sum()*1e6).alias('sum_us'), + ) + + d_irq_stats = irq_stats.to_dict(as_series=False) + for k, v in d_irq_stats.items(): + d_irq_stats[k] = v[0] + + d_irq_stats['irq'] = row[0] + d_irq_stats['handler'] = row[1] + + stats.append(d_irq_stats) + + return stats + + def plot_irq_handler_entry(self): + + handlers = self.irq_handlers(as_pl=True) + + tooltips = [ + ('Time', '@Time'), + ('irq', '@irq'), + ('Handler', '@name'), + ('Duration', '@duration_us [us]'), + ] + hover = HoverTool(tooltips=tooltips) + + plot = hv.Overlay() + + def get_rect_list(df_irq): + return df_irq.select( + pl.col(['Time', '__cpu', 'irq', 'duration', 'name', 'active'])).filter( + pl.col('active') == 1).with_columns( + x0=pl.col('Time'), + y0=pl.col('__cpu')-0.5, + x1=pl.col('Time')+pl.col('duration'), + y1=pl.col('__cpu')+0.2, + duration_us=pl.col('duration')*1e6) + + for row in handlers.iter_rows(): + df_rect = get_rect_list(self.df_irq_handler(row[0], row[1])) + plot *=hv.Rectangles(df_rect.to_dict(), vdims=['Time', '__cpu', 'irq', 'name', 'duration_us']).opts(tools=[hover]) + + opts = { + 'Rectangles': + { + 'color': hv.Cycle('Category20'), + 'xlabel': 'Time', + 'ylabel': 'CPU', + 'yticks': [(c, f'CPU{c}') for c in range(self.trace.cpus_count)] + } + } + + return plot.opts(opts) -- GitLab