def __init__( self, target_action, log_file, timeout=None, **kwargs ) -> None: """ Construct a SystemMetricCollector action. Parameters ---------- target_action : ExecuteProcess ExecuteProcess (or Node) instance to collect metrics on log_file : str or LaunchSubstitutionsType Path to where the collected metrics should be written to timeout : int or LaunchSubstitutionsType Maximum time to run the metrics collector if the target process does not exit """ # These Node/ExecuteProcess arguments are invalid in this context # because we implicitly set them right here. assert 'arguments' not in kwargs assert 'package' not in kwargs assert 'executable' not in kwargs assert 'executable' not in kwargs self.__pid_var_name = '__PROCESS_ID_%d' % id(self) kwargs['package'] = 'buildfarm_perf_tests' kwargs['executable'] = 'system_metric_collector' kwargs['arguments'] = [ '--log', log_file, '--process_pid', LocalSubstitution(self.__pid_var_name)] if timeout is not None: kwargs['arguments'] += [ '--timeout', str(timeout) if isinstance(timeout, int) else timeout ] super().__init__(**kwargs) self.__target_start_handler = OnProcessStart( target_action=target_action, on_start=self.__on_target_start) self.__target_exit_handler = OnProcessExit( target_action=target_action, on_exit=EmitEvent( event=ShutdownProcess( process_matcher=matches_action(self))))
def __init__(self, *, package: SomeSubstitutionsType, node_executable: SomeSubstitutionsType, node_name: Optional[SomeSubstitutionsType] = None, node_namespace: SomeSubstitutionsType = '', parameters: Optional[SomeParameters] = None, remappings: Optional[SomeRemapRules] = None, arguments: Optional[Iterable[SomeSubstitutionsType]] = None, **kwargs) -> None: """ Construct an Node action. Many arguments are passed eventually to :class:`launch.actions.ExecuteProcess`, so see the documentation of that class for additional details. However, the `cmd` is not meant to be used, instead use the `node_executable` and `arguments` keyword arguments to this function. This action, once executed, delegates most work to the :class:`launch.actions.ExecuteProcess`, but it also converts some ROS specific arguments into generic command line arguments. The launch_ros.substitutions.ExecutableInPackage substitution is used to find the executable at runtime, so this Action also raise the exceptions that substituion can raise when the package or executable are not found. If the node_name is not given (or is None) then no name is passed to the node on creation and instead the default name specified within the code of the node is used instead. The node_namespace can either be absolute (i.e. starts with /) or relative. If absolute, then nothing else is considered and this is passed directly to the node to set the namespace. If relative, the namespace in the 'ros_namespace' LaunchConfiguration will be prepended to the given relative node namespace. If no node_namespace is given, then the default namespace `/` is assumed. The parameters are passed as a list, with each element either a yaml file that contains parameter rules (string or pathlib.Path to the full path of the file), or a dictionary that specifies parameter rules. Keys of the dictionary can be strings or an iterable of Substitutions that will be expanded to a string. Values in the dictionary can be strings, integers, floats, or tuples of Substitutions that will be expanded to a string. Additionally, values in the dictionary can be lists of the aforementioned types, or another dictionary with the same properties. A yaml file with the resulting parameters from the dictionary will be written to a temporary file, the path to which will be passed to the node. Multiple dictionaries/files can be passed: each file path will be passed in in order to the node (where the last definition of a parameter takes effect). :param: package the package in which the node executable can be found :param: node_executable the name of the executable to find :param: node_name the name of the node :param: node_namespace the ros namespace for this Node :param: parameters list of names of yaml files with parameter rules, or dictionaries of parameters. :param: remappings ordered list of 'to' and 'from' string pairs to be passed to the node as ROS remapping rules :param: arguments list of extra arguments for the node """ cmd = [ ExecutableInPackage(package=package, executable=node_executable) ] cmd += [] if arguments is None else arguments # Reserve space for ros specific arguments. # The substitutions will get expanded when the action is executed. cmd += ['--ros-args' ] # Prepend ros specific arguments with --ros-args flag if node_name is not None: cmd += [ '-r', LocalSubstitution("ros_specific_arguments['name']", description='node name') ] if parameters is not None: ensure_argument_type(parameters, (list), 'parameters', 'Node') # All elements in the list are paths to files with parameters (or substitutions that # evaluate to paths), or dictionaries of parameters (fields can be substitutions). i = 0 for param in parameters: cmd += [ LocalSubstitution( "ros_specific_arguments['params'][{}]".format(i), description='parameter {}'.format(i)) ] i += 1 normalized_params = normalize_parameters(parameters) if remappings is not None: i = 0 for remapping in normalize_remap_rules(remappings): k, v = remapping cmd += [ '-r', LocalSubstitution( "ros_specific_arguments['remaps'][{}]".format(i), description='remapping {}'.format(i)) ] i += 1 super().__init__(cmd=cmd, **kwargs) self.__package = package self.__node_executable = node_executable self.__node_name = node_name self.__node_namespace = node_namespace self.__parameters = [] if parameters is None else normalized_params self.__remappings = [] if remappings is None else remappings self.__arguments = arguments self.__expanded_node_name = '<node_name_unspecified>' self.__expanded_node_namespace = '' self.__final_node_name = None # type: Optional[Text] self.__expanded_parameter_files = None # type: Optional[List[Text]] self.__expanded_remappings = None # type: Optional[List[Tuple[Text, Text]]] self.__substitutions_performed = False self.__logger = launch.logging.get_logger(__name__)
def _perform_substitutions(self, context: LaunchContext) -> None: try: if self.__substitutions_performed: # This function may have already been called by a subclass' `execute`, for example. return self.__substitutions_performed = True if self.__node_name is not None: self.__expanded_node_name = perform_substitutions( context, normalize_to_list_of_substitutions(self.__node_name)) validate_node_name(self.__expanded_node_name) self.__expanded_node_name.lstrip('/') self.__expanded_node_namespace = perform_substitutions( context, normalize_to_list_of_substitutions(self.__node_namespace)) if not self.__expanded_node_namespace.startswith('/'): base_ns = context.launch_configurations.get( 'ros_namespace', '') self.__expanded_node_namespace = ( base_ns + '/' + self.__expanded_node_namespace).rstrip('/') if (self.__expanded_node_namespace != '' and not self.__expanded_node_namespace.startswith('/')): self.__expanded_node_namespace = '/' + self.__expanded_node_namespace if self.__expanded_node_namespace != '': cmd_extension = [ '-r', LocalSubstitution("ros_specific_arguments['ns']") ] self.cmd.extend([ normalize_to_list_of_substitutions(x) for x in cmd_extension ]) validate_namespace(self.__expanded_node_namespace) except Exception: self.__logger.error( "Error while expanding or validating node name or namespace for '{}':" .format( 'package={}, node_executable={}, name={}, namespace={}'. format( self.__package, self.__node_executable, self.__node_name, self.__node_namespace, ))) raise self.__final_node_name = '' if self.__expanded_node_namespace not in ['', '/']: self.__final_node_name += self.__expanded_node_namespace self.__final_node_name += '/' + self.__expanded_node_name # expand parameters too if self.__parameters is not None: self.__expanded_parameter_files = [] evaluated_parameters = evaluate_parameters(context, self.__parameters) for params in evaluated_parameters: if isinstance(params, dict): param_file_path = self._create_params_file_from_dict( params) elif isinstance(params, pathlib.Path): param_file_path = str(params) else: raise RuntimeError( 'invalid normalized parameters {}'.format( repr(params))) if not os.path.isfile(param_file_path): self.__logger.warning( 'Parameter file path is not a file: {}'.format( param_file_path), ) # Don't skip adding the file to the parameter list since space has been # reserved for it in the ros_specific_arguments. self.__expanded_parameter_files.append(param_file_path) # expand remappings too if self.__remappings is not None: self.__expanded_remappings = [] for k, v in self.__remappings: key = perform_substitutions( context, normalize_to_list_of_substitutions(k)) value = perform_substitutions( context, normalize_to_list_of_substitutions(v)) self.__expanded_remappings.append((key, value))
def _perform_substitutions(self, context: LaunchContext) -> None: # Here to avoid cyclic import from ..descriptions import Parameter try: if self.__substitutions_performed: # This function may have already been called by a subclass' `execute`, for example. return self.__substitutions_performed = True if self.__node_name is not None: self.__expanded_node_name = perform_substitutions( context, normalize_to_list_of_substitutions(self.__node_name)) validate_node_name(self.__expanded_node_name) self.__expanded_node_name.lstrip('/') expanded_node_namespace: Optional[Text] = None if self.__node_namespace is not None: expanded_node_namespace = perform_substitutions( context, normalize_to_list_of_substitutions(self.__node_namespace)) base_ns = context.launch_configurations.get('ros_namespace', None) expanded_node_namespace = make_namespace_absolute( prefix_namespace(base_ns, expanded_node_namespace)) if expanded_node_namespace is not None: self.__expanded_node_namespace = expanded_node_namespace cmd_extension = ['-r', LocalSubstitution("ros_specific_arguments['ns']")] self.cmd.extend([normalize_to_list_of_substitutions(x) for x in cmd_extension]) validate_namespace(self.__expanded_node_namespace) except Exception: self.__logger.error( "Error while expanding or validating node name or namespace for '{}':" .format('package={}, executable={}, name={}, namespace={}'.format( self.__package, self.__node_executable, self.__node_name, self.__node_namespace, )) ) raise self.__final_node_name = prefix_namespace( self.__expanded_node_namespace, self.__expanded_node_name) # expand global parameters first, # so they can be overriden with specific parameters of this Node global_params = context.launch_configurations.get('ros_params', None) if global_params is not None or self.__parameters is not None: self.__expanded_parameter_arguments = [] if global_params is not None: param_file_path = self._create_params_file_from_dict(global_params) self.__expanded_parameter_arguments.append((param_file_path, True)) cmd_extension = ['--params-file', f'{param_file_path}'] self.cmd.extend([normalize_to_list_of_substitutions(x) for x in cmd_extension]) assert os.path.isfile(param_file_path) # expand parameters too if self.__parameters is not None: evaluated_parameters = evaluate_parameters(context, self.__parameters) for params in evaluated_parameters: is_file = False if isinstance(params, dict): param_argument = self._create_params_file_from_dict(params) is_file = True assert os.path.isfile(param_argument) elif isinstance(params, pathlib.Path): param_argument = str(params) is_file = True elif isinstance(params, Parameter): param_argument = self._get_parameter_rule(params, context) else: raise RuntimeError('invalid normalized parameters {}'.format(repr(params))) if is_file and not os.path.isfile(param_argument): self.__logger.warning( 'Parameter file path is not a file: {}'.format(param_argument), ) continue self.__expanded_parameter_arguments.append((param_argument, is_file)) cmd_extension = ['--params-file' if is_file else '-p', f'{param_argument}'] self.cmd.extend([normalize_to_list_of_substitutions(x) for x in cmd_extension]) # expand remappings too global_remaps = context.launch_configurations.get('ros_remaps', None) if global_remaps or self.__remappings: self.__expanded_remappings = [] if global_remaps: self.__expanded_remappings.extend(global_remaps) if self.__remappings: self.__expanded_remappings.extend([ (perform_substitutions(context, src), perform_substitutions(context, dst)) for src, dst in self.__remappings ]) if self.__expanded_remappings: cmd_extension = [] for src, dst in self.__expanded_remappings: cmd_extension.extend(['-r', f'{src}:={dst}']) self.cmd.extend([normalize_to_list_of_substitutions(x) for x in cmd_extension])
def __init__(self, *, executable: Optional[SomeSubstitutionsType] = None, node_executable: Optional[SomeSubstitutionsType] = None, package: Optional[SomeSubstitutionsType] = None, name: Optional[SomeSubstitutionsType] = None, namespace: Optional[SomeSubstitutionsType] = None, node_name: Optional[SomeSubstitutionsType] = None, node_namespace: SomeSubstitutionsType = None, exec_name: Optional[SomeSubstitutionsType] = None, parameters: Optional[SomeParameters] = None, remappings: Optional[SomeRemapRules] = None, arguments: Optional[Iterable[SomeSubstitutionsType]] = None, **kwargs) -> None: """ Construct an Node action. Many arguments are passed eventually to :class:`launch.actions.ExecuteProcess`, so see the documentation of that class for additional details. However, the `cmd` is not meant to be used, instead use the `executable` and `arguments` keyword arguments to this function. This action, once executed, delegates most work to the :class:`launch.actions.ExecuteProcess`, but it also converts some ROS specific arguments into generic command line arguments. The launch_ros.substitutions.ExecutableInPackage substitution is used to find the executable at runtime, so this Action also raise the exceptions that substituion can raise when the package or executable are not found. If the name is not given (or is None) then no name is passed to the node on creation and instead the default name specified within the code of the node is used instead. The namespace can either be absolute (i.e. starts with /) or relative. If absolute, then nothing else is considered and this is passed directly to the node to set the namespace. If relative, the namespace in the 'ros_namespace' LaunchConfiguration will be prepended to the given relative node namespace. If no namespace is given, then the default namespace `/` is assumed. The parameters are passed as a list, with each element either a yaml file that contains parameter rules (string or pathlib.Path to the full path of the file), or a dictionary that specifies parameter rules. Keys of the dictionary can be strings or an iterable of Substitutions that will be expanded to a string. Values in the dictionary can be strings, integers, floats, or tuples of Substitutions that will be expanded to a string. Additionally, values in the dictionary can be lists of the aforementioned types, or another dictionary with the same properties. A yaml file with the resulting parameters from the dictionary will be written to a temporary file, the path to which will be passed to the node. Multiple dictionaries/files can be passed: each file path will be passed in in order to the node (where the last definition of a parameter takes effect). .. deprecated:: Foxy Parameters `node_executable`, `node_name`, and `node_namespace` are deprecated. Use `executable`, `name`, and `namespace` instead. :param: executable the name of the executable to find if a package is provided or otherwise a path to the executable to run. :param: node_executable (DEPRECATED) the name of the executable to find if a package is provided or otherwise a path to the executable to run. :param: package the package in which the node executable can be found :param: name the name of the node :param: namespace the ROS namespace for this Node :param: exec_name the label used to represent the process. Defaults to the basename of node executable. :param: node_name (DEPRECATED) the name of the node :param: node_namespace (DEPRECATED) the ros namespace for this Node :param: parameters list of names of yaml files with parameter rules, or dictionaries of parameters. :param: remappings ordered list of 'to' and 'from' string pairs to be passed to the node as ROS remapping rules :param: arguments list of extra arguments for the node """ if node_executable is not None: warnings.warn( "The parameter 'node_executable' is deprecated, use 'executable' instead", stacklevel=2) if executable is not None: raise RuntimeError( "Passing both 'node_executable' and 'executable' parameters. " "Only use 'executable'") executable = node_executable if package is not None: cmd = [ExecutableInPackage(package=package, executable=executable)] else: cmd = [executable] cmd += [] if arguments is None else arguments # Reserve space for ros specific arguments. # The substitutions will get expanded when the action is executed. cmd += ['--ros-args' ] # Prepend ros specific arguments with --ros-args flag if node_name is not None: warnings.warn( "The parameter 'node_name' is deprecated, use 'name' instead", stacklevel=2) if name is not None: raise RuntimeError( "Passing both 'node_name' and 'name' parameters. Only use 'name'." ) cmd += [ '-r', LocalSubstitution("ros_specific_arguments['name']", description='node name') ] name = node_name if name is not None: cmd += [ '-r', LocalSubstitution("ros_specific_arguments['name']", description='node name') ] if node_namespace: warnings.warn( "The parameter 'node_namespace' is deprecated, use 'namespace' instead" ) if namespace: raise RuntimeError( "Passing both 'node_namespace' and 'namespace' parameters. " "Only use 'namespace'.") namespace = node_namespace if parameters is not None: ensure_argument_type(parameters, (list), 'parameters', 'Node') # All elements in the list are paths to files with parameters (or substitutions that # evaluate to paths), or dictionaries of parameters (fields can be substitutions). normalized_params = normalize_parameters(parameters) # Forward 'exec_name' as to ExecuteProcess constructor kwargs['name'] = exec_name super().__init__(cmd=cmd, **kwargs) self.__package = package self.__node_executable = executable self.__node_name = name self.__node_namespace = namespace self.__parameters = [] if parameters is None else normalized_params self.__remappings = [] if remappings is None else list( normalize_remap_rules(remappings)) self.__arguments = arguments self.__expanded_node_name = self.UNSPECIFIED_NODE_NAME self.__expanded_node_namespace = self.UNSPECIFIED_NODE_NAMESPACE self.__expanded_parameter_arguments = None # type: Optional[List[Tuple[Text, bool]]] self.__final_node_name = None # type: Optional[Text] self.__expanded_remappings = None # type: Optional[List[Tuple[Text, Text]]] self.__substitutions_performed = False self.__logger = launch.logging.get_logger(__name__)
def __init__(self, *, package: SomeSubstitutionsType, node_executable: SomeSubstitutionsType, node_name: Optional[SomeSubstitutionsType] = None, node_namespace: Optional[SomeSubstitutionsType] = None, remappings: Optional[Iterable[Tuple[ SomeSubstitutionsType, SomeSubstitutionsType]]] = None, arguments: Optional[Iterable[SomeSubstitutionsType]] = None, **kwargs) -> None: """ Construct an Node action. Many arguments are passed eventually to :class:`launch.actions.ExecuteProcess`, so see the documentation of that class for additional details. However, the `cmd` is not meant to be used, instead use the `node_executable` and `arguments` keyword arguments to this function. This action, once executed, delegates most work to the :class:`launch.actions.ExecuteProcess`, but it also converts some ROS specific arguments into generic command line arguments. The launch_ros.substitutions.ExecutableInPackage substitution is used to find the executable at runtime, so this Action also raise the exceptions that substituion can raise when the package or executable are not found. If the node_name is not given (or is None) then no name is passed to the node on creation and instead the default name specified within the code of the node is used instead. The node_namespace can either be absolute (i.e. starts with /) or relative. If absolute, then nothing else is considered and this is passed directly to the node to set the namespace. If relative, the namespace in the 'ros_namespace' LaunchConfiguration will be prepended to the given relative node namespace. If no node_namespace is given, then the default namespace `/` is assumed. :param: package the package in which the node executable can be found :param: node_executable the name of the executable to find :param: node_name the name of the node :param: node_namespace the ros namespace for this Node :param: remappings ordered list of 'to' and 'from' string pairs to be passed to the node as ROS remapping rules :param: arguments list of extra arguments for the node """ cmd = [ ExecutableInPackage(package=package, executable=node_executable) ] cmd += [] if arguments is None else arguments # Reserve space for ros specific arguments. # The substitutions will get expanded when the action is executed. ros_args_index = 0 if node_name is not None: cmd += [ LocalSubstitution( 'ros_specific_arguments[{}]'.format(ros_args_index)) ] ros_args_index += 1 if node_namespace is not None: cmd += [ LocalSubstitution( 'ros_specific_arguments[{}]'.format(ros_args_index)) ] ros_args_index += 1 if remappings is not None: for k, v in remappings: cmd += [ LocalSubstitution( 'ros_specific_arguments[{}]'.format(ros_args_index)) ] ros_args_index += 1 super().__init__(cmd=cmd, **kwargs) self.__package = package self.__node_executable = node_executable self.__node_name = node_name self.__node_namespace = node_namespace self.__remappings = [] if remappings is None else remappings self.__arguments = arguments self.__expanded_node_name = '<node_name_unspecified>' self.__expanded_node_namespace = '/' self.__final_node_name = None # type: Optional[Text] self.__expanded_remappings = None # type: Optional[Dict[Text, Text]] self.__substitutions_performed = False
def _perform_substitutions(self, context: LaunchContext) -> None: try: if self.__substitutions_performed: # This function may have already been called by a subclass' `execute`, for example. return self.__substitutions_performed = True if self.__node_name is not None: self.__expanded_node_name = perform_substitutions( context, normalize_to_list_of_substitutions(self.__node_name)) validate_node_name(self.__expanded_node_name) self.__expanded_node_name.lstrip('/') expanded_node_namespace = None if self.__node_namespace is not None: expanded_node_namespace = perform_substitutions( context, normalize_to_list_of_substitutions(self.__node_namespace)) base_ns = context.launch_configurations.get('ros_namespace', None) if base_ns is not None or expanded_node_namespace is not None: if expanded_node_namespace is None: expanded_node_namespace = '' if base_ns is None: base_ns = '' if not expanded_node_namespace.startswith('/'): expanded_node_namespace = ( base_ns + '/' + expanded_node_namespace).rstrip('/') if not expanded_node_namespace.startswith('/'): expanded_node_namespace = '/' + expanded_node_namespace self.__expanded_node_namespace = expanded_node_namespace if expanded_node_namespace is not None: cmd_extension = [ '-r', LocalSubstitution("ros_specific_arguments['ns']") ] self.cmd.extend([ normalize_to_list_of_substitutions(x) for x in cmd_extension ]) validate_namespace(self.__expanded_node_namespace) except Exception: self.__logger.error( "Error while expanding or validating node name or namespace for '{}':" .format( 'package={}, executable={}, name={}, namespace={}'.format( self.__package, self.__node_executable, self.__node_name, self.__node_namespace, ))) raise self.__final_node_name = '' if self.__expanded_node_namespace != '/': self.__final_node_name += self.__expanded_node_namespace self.__final_node_name += '/' + self.__expanded_node_name # expand global parameters first, # so they can be overriden with specific parameters of this Node global_params = context.launch_configurations.get('ros_params', None) if global_params is not None or self.__parameters is not None: self.__expanded_parameter_files = [] if global_params is not None: param_file_path = self._create_params_file_from_dict(global_params) self.__expanded_parameter_files.append(param_file_path) cmd_extension = ['--params-file', f'{param_file_path}'] self.cmd.extend( [normalize_to_list_of_substitutions(x) for x in cmd_extension]) assert os.path.isfile(param_file_path) # expand parameters too if self.__parameters is not None: evaluated_parameters = evaluate_parameters(context, self.__parameters) for i, params in enumerate(evaluated_parameters): if isinstance(params, dict): param_file_path = self._create_params_file_from_dict( params) assert os.path.isfile(param_file_path) elif isinstance(params, pathlib.Path): param_file_path = str(params) else: raise RuntimeError( 'invalid normalized parameters {}'.format( repr(params))) if not os.path.isfile(param_file_path): self.__logger.warning( 'Parameter file path is not a file: {}'.format( param_file_path), ) continue self.__expanded_parameter_files.append(param_file_path) cmd_extension = ['--params-file', f'{param_file_path}'] self.cmd.extend([ normalize_to_list_of_substitutions(x) for x in cmd_extension ]) # expand remappings too if self.__remappings is not None: self.__expanded_remappings = [] for k, v in self.__remappings: key = perform_substitutions( context, normalize_to_list_of_substitutions(k)) value = perform_substitutions( context, normalize_to_list_of_substitutions(v)) self.__expanded_remappings.append((key, value))