diff --git a/tracetools_launch/tracetools_launch/action.py b/tracetools_launch/tracetools_launch/action.py index 3a1205b..d7e69f2 100644 --- a/tracetools_launch/tracetools_launch/action.py +++ b/tracetools_launch/tracetools_launch/action.py @@ -14,10 +14,14 @@ """Module for the Trace action.""" +import re +import subprocess from typing import List from typing import Optional +from typing import Union from launch.action import Action +from launch.actions import SetEnvironmentVariable from launch.event import Event from launch.event_handlers import OnShutdown from launch.launch_context import LaunchContext @@ -33,6 +37,10 @@ class Trace(Action): Sets up and enables tracing through a launch file description. """ + PROFILE_LIB_NORMAL = 'liblttng-ust-cyg-profile.so' + PROFILE_LIB_FAST = 'liblttng-ust-cyg-profile-fast.so' + PROFILE_PATTERN = '^lttng_ust_cyg_profile.*:func_.*' + def __init__( self, *, @@ -41,6 +49,7 @@ class Trace(Action): base_path: str = path.DEFAULT_BASE_PATH, events_ust: List[str] = names.DEFAULT_EVENTS_ROS, events_kernel: List[str] = names.DEFAULT_EVENTS_KERNEL, + profile_fast: bool = True, **kwargs, ) -> None: """ @@ -51,6 +60,7 @@ class Trace(Action): :param base_path: the path to the base directory in which to create the session directory :param events_ust: the list of ROS UST events to enable :param events_kernel: the list of kernel events to enable + :param profile_fast: `True` to use fast profiling, `False` for normal (only if necessary) """ super().__init__(**kwargs) if append_timestamp: @@ -59,8 +69,41 @@ class Trace(Action): self.__base_path = base_path self.__events_ust = events_ust self.__events_kernel = events_kernel + self.__ld_preload_action = None + if self.has_profiling_events(events_ust): + profile_lib_name = self.PROFILE_LIB_FAST if profile_fast else self.PROFILE_LIB_NORMAL + self.__ld_preload_action = SetEnvironmentVariable( + 'LD_PRELOAD', + self.get_shared_lib_path(profile_lib_name), + ) + + @classmethod + def has_profiling_events(cls, events_ust: List[str]) -> bool: + matches = [re.match(cls.PROFILE_PATTERN, event_name) for event_name in events_ust] + return any(matches) + + @staticmethod + def get_shared_lib_path(lib_name: str) -> Union[str, None]: + """ + Get the full path to a given shared lib, if possible. + + :param lib_name: the name of the shared library + :return: the full path if found, `None` otherwise + """ + (exit_code, output) = subprocess.getstatusoutput(f'whereis -b {lib_name}') + if exit_code != 0: + return None + # Output of whereis is + # : + # Filter out empty strings, in case lib is not found + output_split = [split_part for split_part in output.split(':') if len(split_part) > 0] + if len(output_split) != 2: + return None + return output_split[1].strip() def execute(self, context: LaunchContext) -> Optional[List[Action]]: + if self.__ld_preload_action is not None: + context.add_action(self.__ld_preload_action) # TODO make sure this is done as late as possible context.register_event_handler(OnShutdown(on_shutdown=self._destroy)) # TODO make sure this is done as early as possible