From 5eb175cfd4faa29b41e243501b5334fa7b0baf0b Mon Sep 17 00:00:00 2001 From: Christophe Bedard Date: Mon, 29 Jul 2019 12:56:58 +0200 Subject: [PATCH] Add CPU time processor --- .../analysis/cpu_time_processor.py | 64 +++++++++++++++++++ .../analysis/data_model/cpu_time.py | 52 +++++++++++++++ .../tracetools_analysis/analysis/utils.py | 21 ++++++ .../tracetools_analysis/process.py | 4 ++ 4 files changed, 141 insertions(+) create mode 100644 tracetools_analysis/tracetools_analysis/analysis/cpu_time_processor.py create mode 100644 tracetools_analysis/tracetools_analysis/analysis/data_model/cpu_time.py diff --git a/tracetools_analysis/tracetools_analysis/analysis/cpu_time_processor.py b/tracetools_analysis/tracetools_analysis/analysis/cpu_time_processor.py new file mode 100644 index 0000000..a575078 --- /dev/null +++ b/tracetools_analysis/tracetools_analysis/analysis/cpu_time_processor.py @@ -0,0 +1,64 @@ +# Copyright 2019 Robert Bosch GmbH +# +# 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. + +"""Module for CPU time events processing.""" + +from typing import Dict + +from tracetools_read.utils import get_field + +from .data_model.cpu_time import CpuTimeDataModel +from .handler import EventHandler +from .lttng_models import EventMetadata + + +class CpuTimeProcessor(EventHandler): + """ + Processor that extracts data for CPU time. + + It extracts timestamps from sched_switch events to later compute CPU time per thread. + """ + + def __init__(self) -> None: + # Link event to handling method + handler_map = { + 'sched_switch': + self._handle_sched_switch, + } + super().__init__(handler_map) + + self._data = CpuTimeDataModel() + + # Temporary buffers + # cpu_id -> start timestamp of the running thread + self._cpu_start: Dict[int, int] = {} + + def get_data_model(self) -> CpuTimeDataModel: + return self._data + + def _handle_sched_switch( + self, event: Dict, metadata: EventMetadata + ) -> None: + timestamp = metadata.timestamp + cpu_id = metadata.cpu_id + # Process if there is a previous thread timestamp + # TODO instead of discarding it, use first ever timestamp + # of the trace (with TraceCollection.timestamp_begin) + prev_timestamp = self._cpu_start.get(cpu_id, None) + if prev_timestamp is not None: + prev_tid = get_field(event, 'prev_tid') + duration = timestamp - prev_timestamp + self._data.add_duration(prev_tid, prev_timestamp, duration, cpu_id) + # Set start timestamp of next thread + self._cpu_start[cpu_id] = timestamp diff --git a/tracetools_analysis/tracetools_analysis/analysis/data_model/cpu_time.py b/tracetools_analysis/tracetools_analysis/analysis/data_model/cpu_time.py new file mode 100644 index 0000000..b7a1eeb --- /dev/null +++ b/tracetools_analysis/tracetools_analysis/analysis/data_model/cpu_time.py @@ -0,0 +1,52 @@ +# Copyright 2019 Robert Bosch GmbH +# +# 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. + +"""Module for CPU time data model.""" + +import pandas as pd + + +class CpuTimeDataModel(): + """ + Container to model pre-processed CPU time data for analysis. + + Contains data for an analysis to use. It uses pandas DataFrames directly. + """ + + def __init__(self) -> None: + """Constructor.""" + self.times = pd.DataFrame(columns=[ + 'tid', + 'start_timestamp', + 'duration', + 'cpu_id', + ]) + + def add_duration( + self, tid: int, start_timestamp: int, duration: int, cpu_id: int + ) -> None: + data = { + 'tid': tid, + 'start_timestamp': start_timestamp, + 'duration': duration, + 'cpu_id': cpu_id, + } + self.times = self.times.append(data, ignore_index=True) + + def print_model(self) -> None: + """Debug method to print every contained df.""" + print('====================CPU TIME DATA MODEL====================') + tail = 20 + print(f'Times (tail={tail}):\n{self.times.tail(tail).to_string()}') + print('===========================================================') diff --git a/tracetools_analysis/tracetools_analysis/analysis/utils.py b/tracetools_analysis/tracetools_analysis/analysis/utils.py index 1c3b05f..43875a0 100644 --- a/tracetools_analysis/tracetools_analysis/analysis/utils.py +++ b/tracetools_analysis/tracetools_analysis/analysis/utils.py @@ -21,9 +21,30 @@ from typing import Union from pandas import DataFrame +from .data_model.cpu_time import CpuTimeDataModel from .data_model.ros import RosDataModel +class CpuTimeDataModelUtil(): + """ + CPU time data model utility class. + + Provides functions to get info on a CPU time data model. + """ + + def __init__(self, data_model: CpuTimeDataModel) -> None: + """ + Constructor. + + :param data_model: the data model object to use + """ + self._data = data_model + + def get_time_per_thread(self) -> DataFrame: + """Get a DataFrame of total duration for each thread.""" + return self._data.times.loc[:, ['tid', 'duration']].groupby(by='tid').sum() + + class RosDataModelUtil(): """ ROS data model utility class. diff --git a/tracetools_analysis/tracetools_analysis/process.py b/tracetools_analysis/tracetools_analysis/process.py index 7553b4e..f2d76ca 100644 --- a/tracetools_analysis/tracetools_analysis/process.py +++ b/tracetools_analysis/tracetools_analysis/process.py @@ -18,6 +18,7 @@ import argparse import time +from tracetools_analysis.analysis import cpu_time_processor from tracetools_analysis.analysis import load from tracetools_analysis.analysis import ros2_processor @@ -37,8 +38,11 @@ def main(): events = load.load_pickle(pickle_filename) processor = ros2_processor.ros2_process(events) + cpu_processor = cpu_time_processor.CpuTimeProcessor() + cpu_processor.handle_events(events) time_diff = time.time() - start_time print(f'processed {len(events)} events in {time_diff * 1000:.2f} ms') processor.get_data_model().print_model() + cpu_processor.get_data_model().print_model()