def set_governor_tunables(self, cpu, governor=None, **kwargs): """ Set tunables for the specified governor. Tunables should be specified as keyword arguments. Which tunables and values are valid depends on the governor. :param cpu: The cpu for which the governor will be set. This must be the full cpu name as it appears in sysfs, e.g. ``cpu0``. :param governor: The name of the governor. Must be all lower case. The rest should be keyword parameters mapping tunable name onto the value to be set for it. :raises: TargetError if governor specified is not a valid governor name, or if a tunable specified is not valid for the governor, or if could not set tunable. """ if isinstance(cpu, int): cpu = 'cpu{}'.format(cpu) if governor is None: governor = self.get_governor(cpu) valid_tunables = self.list_governor_tunables(cpu) for tunable, value in kwargs.iteritems(): if tunable in valid_tunables: try: path = '/sys/devices/system/cpu/{}/cpufreq/{}/{}'.format( cpu, governor, tunable) self.target.write_value(path, value) except TargetError: # May be an older kernel path = '/sys/devices/system/cpu/cpufreq/{}/{}'.format( governor, tunable) self.target.write_value(path, value) else: message = 'Unexpected tunable {} for governor {} on {}.\n'.format( tunable, governor, cpu) message += 'Available tunables are: {}'.format(valid_tunables) raise TargetError(message)
def __init__(self, target): super(CgroupsModule, self).__init__(target) self.logger = logging.getLogger('CGroups') # Set Devlib's CGroups mount point self.cgroup_root = target.path.join(target.working_directory, 'cgroups') # Get the list of the available controllers subsys = self.list_subsystems() if len(subsys) == 0: self.logger.warning('No CGroups controller available') return # Map hierarchy IDs into a list of controllers hierarchy = {} for ss in subsys: try: hierarchy[ss.hierarchy].append(ss.name) except KeyError: hierarchy[ss.hierarchy] = [ss.name] self.logger.debug('Available hierarchies: %s', hierarchy) # Initialize controllers self.logger.info('Available controllers:') self.controllers = {} for ss in subsys: hid = ss.hierarchy controller = Controller(ss.name, hid, hierarchy[hid]) try: controller.mount(self.target, self.cgroup_root) except TargetError: message = 'Failed to mount "{}" controller' raise TargetError(message.format(controller.kind)) self.logger.info(' %-12s : %s', controller.kind, controller.mount_point) self.controllers[ss.name] = controller
def execute(self, command, timeout=None, check_exit_code=True, as_root=False, strip_colors=True): with self.lock: output = self._execute_and_wait_for_prompt(command, timeout, as_root, strip_colors) if check_exit_code: exit_code_text = self._execute_and_wait_for_prompt( 'echo $?', strip_colors=strip_colors, log=False) try: exit_code = int(exit_code_text.split()[0]) if exit_code: message = 'Got exit code {}\nfrom: {}\nOUTPUT: {}' raise TargetError( message.format(exit_code, command, output)) except (ValueError, IndexError): logger.warning( 'Could not get exit code for "{}",\ngot: "{}"'.format( command, exit_code_text)) return output
def __call__(self, image_bundle=None, images=None, bootargs=None): self.target.hard_reset() with open_serial_connection(port=self.target.platform.serial_port, baudrate=self.target.platform.baudrate, timeout=self.timeout, init_dtr=0) as tty: i = tty.expect( [self.mcc_prompt, AUTOSTART_MESSAGE, OLD_AUTOSTART_MESSAGE]) if i: tty.sendline('') wait_for_vemsd(self.vemsd_mount, tty, self.mcc_prompt, self.short_delay) try: if image_bundle: self._deploy_image_bundle(image_bundle) if images: self._overlay_images(images) os.system('sync') except (IOError, OSError) as e: msg = 'Could not deploy images to {}; got: {}' raise TargetError(msg.format(self.vemsd_mount, e)) self.target.boot() self.target.connect(timeout=30)
def background(self, command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, as_root=False): try: port_string = '-p {}'.format(self.port) if self.port else '' keyfile_string = '-i {}'.format( self.keyfile) if self.keyfile else '' if as_root: command = "sudo -- sh -c '{}'".format(command) command = '{} {} {} {}@{} {}'.format(ssh, keyfile_string, port_string, self.username, self.host, command) logger.debug(command) if self.password: command = _give_password(self.password, command) return subprocess.Popen(command, stdout=stdout, stderr=stderr, shell=True) except EOF: raise TargetError('Connection lost.')
def __init__(self, target): super(CgroupsModule, self).__init__(target) self.logger = logging.getLogger('CGroups') # Initialize controllers mount point mounted = self.target.list_file_systems() if self.cgroup_root not in [e.mount_point for e in mounted]: self.target.execute('mount -t tmpfs {} {}'\ .format('cgroup_root', self.cgroup_root), as_root=True) else: self.logger.debug('cgroup_root already mounted at %s', self.cgroup_root) # Load list of available controllers controllers = [] subsys = self.list_subsystems() for (n, h, c, e) in subsys: controllers.append(n) self.logger.debug('Available controllers: %s', controllers) # Initialize controllers self.controllers = {} for idx in controllers: controller = Controller(idx) self.logger.debug('Init %s controller...', controller.kind) if not controller.probe(self.target): continue try: controller.mount(self.target, self.cgroup_root) except TargetError: message = 'cgroups {} controller is not supported by the target' raise TargetError(message.format(controller.kind)) self.logger.debug('Controller %s enabled', controller.kind) self.controllers[idx] = controller
def kick_off(self, command, as_root=False): """ Like execute but closes adb session and returns immediately, leaving the command running on the device (this is different from execute(background=True) which keeps adb connection open and returns a subprocess object). .. note:: This relies on busybox's nohup applet and so won't work on unrooted devices. """ if not self.is_rooted: raise TargetError( 'kick_off uses busybox\'s nohup applet and so can only be run a rooted device.' ) try: command = 'cd {} && {} nohup {}'.format(self.working_directory, self.bin('busybox'), command) output = self.execute(command, timeout=1, as_root=as_root) except TimeoutError: pass else: raise ValueError( 'Background command exited before timeout; got "{}"'.format( output))
def ssh_get_shell(host, username, password=None, keyfile=None, port=None, timeout=10, telnet=False): _check_env() if telnet: if keyfile: raise ValueError( 'keyfile may not be used with a telnet connection.') conn = TelnetConnection() else: # ssh conn = pxssh.pxssh() try: if keyfile: conn.login(host, username, ssh_key=keyfile, port=port, login_timeout=timeout) else: conn.login(host, username, password, port=port, login_timeout=timeout) except EOF: raise TargetError( 'Could not connect to {}; is the host name correct?'.format(host)) conn.setwinsize(500, 200) conn.sendline('') conn.prompt() conn.setecho(False) return conn
def __init__(self, target): if not hasattr(target, 'hwmon'): raise TargetError('Target does not support HWMON') super(HwmonInstrument, self).__init__(target) self.logger.debug('Discovering available HWMON sensors...') for ts in self.target.hwmon.sensors: try: ts.get_file('input') measure = self.measure_map.get(ts.kind)[0] if measure: self.logger.debug('\tAdding sensor {}'.format(ts.name)) self.add_channel(_guess_site(ts), measure, name=ts.name, sensor=ts) else: self.logger.debug( '\tSkipping sensor {} (unknown kind "{}")'.format( ts.name, ts.kind)) except ValueError: message = 'Skipping sensor {} because it does not have an input file' self.logger.debug(message.format(ts.name)) continue
def _check_ready(self): """ Check if the gem5 platform is ready """ if not self.ready: raise TargetError('Gem5 is not ready to interact yet')
def _gem5_util(self, command): """ Execute a gem5 utility command using the m5 binary on the device """ if self.m5_path is None: raise TargetError('Path to m5 binary on simulated system is not set!') self._gem5_shell('{} {}'.format(self.m5_path, command))
def _gem5_shell(self, command, as_root=False, timeout=None, check_exit_code=True, sync=True): # pylint: disable=R0912 """ Execute a command in the gem5 shell This wraps the telnet connection to gem5 and processes the raw output. This method waits for the shell to return, and then will try and separate the output from the command from the command itself. If this fails, warn, but continue with the potentially wrong output. The exit code is also checked by default, and non-zero exit codes will raise a TargetError. """ if sync: self._sync_gem5_shell() gem5_logger.debug("gem5_shell command: {}".format(command)) if as_root: command = 'echo "{}" | su'.format(escape_double_quotes(command)) # Send the actual command self.conn.send("{}\n".format(command)) # Wait for the response. We just sit here and wait for the prompt to # appear, as gem5 might take a long time to provide the output. This # avoids timeout issues. command_index = -1 while command_index == -1: if self.conn.prompt(): output = re.sub(r' \r([^\n])', r'\1', self.conn.before) output = re.sub(r'[\b]', r'', output) # Deal with line wrapping output = re.sub(r'[\r].+?<', r'', output) command_index = output.find(command) # If we have -1, then we cannot match the command, but the # prompt has returned. Hence, we have a bit of an issue. We # warn, and return the whole output. if command_index == -1: gem5_logger.warn("gem5_shell: Unable to match command in " "command output. Expect parsing errors!") command_index = 0 output = output[command_index + len(command):].strip() # If the gem5 system echoes the executed command, we need to remove that too! if self.strip_echoed_commands: command_index = output.find(command) if command_index != -1: output = output[command_index + len(command):].strip() gem5_logger.debug("gem5_shell output: {}".format(output)) # We get a second prompt. Hence, we need to eat one to make sure that we # stay in sync. If we do not do this, we risk getting out of sync for # slower simulations. self.conn.expect([self.conn.UNIQUE_PROMPT, self.conn.PROMPT], timeout=self.default_timeout) if check_exit_code: exit_code_text = self._gem5_shell('echo $?', as_root=as_root, timeout=timeout, check_exit_code=False, sync=False) try: exit_code = int(exit_code_text.split()[0]) if exit_code: message = 'Got exit code {}\nfrom: {}\nOUTPUT: {}' raise TargetError(message.format(exit_code, command, output)) except (ValueError, IndexError): gem5_logger.warning('Could not get exit code for "{}",\ngot: "{}"'.format(command, exit_code_text)) return output
def connect_gem5(self, port, gem5_simulation, gem5_interact_dir, gem5_out_dir): """ Connect to the telnet port of the gem5 simulation. We connect, and wait for the prompt to be found. We do not use a timeout for this, and wait for the prompt in a while loop as the gem5 simulation can take many hours to reach a prompt when booting the system. We also inject some newlines periodically to try and force gem5 to show a prompt. Once the prompt has been found, we replace it with a unique prompt to ensure that we are able to match it properly. We also disable the echo as this simplifies parsing the output when executing commands on the device. """ host = socket.gethostname() gem5_logger.info("Connecting to the gem5 simulation on port {}".format(port)) # Check if there is no on-going connection yet lock_file_name = '{}{}_{}.LOCK'.format(self.lock_directory, host, port) if os.path.isfile(lock_file_name): # There is already a connection to this gem5 simulation raise TargetError('There is already a connection to the gem5 ' 'simulation using port {} on {}!' .format(port, host)) # Connect to the gem5 telnet port. Use a short timeout here. attempts = 0 while attempts < 10: attempts += 1 try: self.conn = TelnetPxssh(original_prompt=None) self.conn.login(host, self.username, port=port, login_timeout=10, auto_prompt_reset=False) break except pxssh.ExceptionPxssh: pass except EOF as err: self._gem5_EOF_handler(gem5_simulation, gem5_out_dir, err) else: gem5_simulation.kill() raise TargetError("Failed to connect to the gem5 telnet session.") gem5_logger.info("Connected! Waiting for prompt...") # Create the lock file self.lock_file_name = lock_file_name open(self.lock_file_name, 'w').close() # Similar to touch gem5_logger.info("Created lock file {} to prevent reconnecting to " "same simulation".format(self.lock_file_name)) # We need to find the prompt. It might be different if we are resuming # from a checkpoint. Therefore, we test multiple options here. prompt_found = False while not prompt_found: try: self._login_to_device() except TIMEOUT: pass except EOF as err: self._gem5_EOF_handler(gem5_simulation, gem5_out_dir, err) try: # Try and force a prompt to be shown self.conn.send('\n') self.conn.expect([r'# ', r'\$ ', self.conn.UNIQUE_PROMPT, r'\[PEXPECT\][\\\$\#]+ '], timeout=60) prompt_found = True except TIMEOUT: pass except EOF as err: self._gem5_EOF_handler(gem5_simulation, gem5_out_dir, err) gem5_logger.info("Successfully logged in") gem5_logger.info("Setting unique prompt...") self.conn.set_unique_prompt() self.conn.prompt() gem5_logger.info("Prompt found and replaced with a unique string") # We check that the prompt is what we think it should be. If not, we # need to update the regex we use to match. self._find_prompt() self.conn.setecho(False) self._sync_gem5_shell() # Fully connected to gem5 simulation self.gem5_interact_dir = gem5_interact_dir self.gem5_out_dir = gem5_out_dir self.gem5simulation = gem5_simulation # Ready for interaction now self.ready = True
attempts += 1 try: self.conn = TelnetPxssh(original_prompt=None) self.conn.login(host, self.username, port=port, login_timeout=10, auto_prompt_reset=False) break except pxssh.ExceptionPxssh: pass except EOF, err: self._gem5_EOF_handler(gem5_simulation, gem5_out_dir, err) else: gem5_simulation.kill() raise TargetError("Failed to connect to the gem5 telnet session.") gem5_logger.info("Connected! Waiting for prompt...") # Create the lock file self.lock_file_name = lock_file_name open(self.lock_file_name, 'w').close() # Similar to touch gem5_logger.info("Created lock file {} to prevent reconnecting to " "same simulation".format(self.lock_file_name)) # We need to find the prompt. It might be different if we are resuming # from a checkpoint. Therefore, we test multiple options here. prompt_found = False while not prompt_found: try: self._login_to_device()
def install_apk(self, filepath, timeout=None): # pylint: disable=W0221 ext = os.path.splitext(filepath)[1].lower() if ext == '.apk': return adb_command(self.adb_name, "install '{}'".format(filepath), timeout=timeout) else: raise TargetError('Can\'t install {}: unsupported format.'.format(filepath))
def _overwritten_reboot(self): raise TargetError('Rebooting is not allowed on gem5 platforms!')
def check_target(self): if not self.target.has('cpuidle'): raise TargetError('Target does not appear to support cpuidle')
def check_target(self): if not self.target.has('hotplug'): raise TargetError('Target does not appear to support hotplug')
def roi_start(self, label): if label not in self.rois: raise KeyError('Incorrect ROI label: {}'.format(label)) if not self.rois[label].start(): raise TargetError('ROI {} was already running'.format(label))
def adb_shell(device, command, timeout=None, check_exit_code=False, as_root=False, adb_server=None): # NOQA _check_env() if as_root: command = 'echo \'{}\' | su'.format(escape_single_quotes(command)) device_part = [] if adb_server: device_part = ['-H', adb_server] device_part += ['-s', device] if device else [] # On older combinations of ADB/Android versions, the adb host command always # exits with 0 if it was able to run the command on the target, even if the # command failed (https://code.google.com/p/android/issues/detail?id=3254). # Homogenise this behaviour by running the command then echoing the exit # code. adb_shell_command = '({}); echo \"\n$?\"'.format(command) actual_command = ['adb'] + device_part + ['shell', adb_shell_command] logger.debug('adb {} shell {}'.format(' '.join(device_part), command)) try: raw_output, error = check_output(actual_command, timeout, shell=False) except subprocess.CalledProcessError as e: raise TargetError(str(e)) if raw_output: try: output, exit_code, _ = raw_output.replace('\r\n', '\n').replace( '\r', '\n').rsplit('\n', 2) except ValueError: exit_code, _ = raw_output.replace('\r\n', '\n').replace( '\r', '\n').rsplit('\n', 1) output = '' else: # raw_output is empty exit_code = '969696' # just because output = '' if check_exit_code: exit_code = exit_code.strip() re_search = AM_START_ERROR.findall('{}\n{}'.format(output, error)) if exit_code.isdigit(): if int(exit_code): message = ('Got exit code {}\nfrom target command: {}\n' 'STDOUT: {}\nSTDERR: {}') raise TargetError( message.format(exit_code, command, output, error)) elif re_search: message = 'Could not start activity; got the following:\n{}' raise TargetError(message.format(re_search[0])) else: # not all digits if re_search: message = 'Could not start activity; got the following:\n{}' raise TargetError(message.format(re_search[0])) else: message = 'adb has returned early; did not get an exit code. '\ 'Was kill-server invoked?\nOUTPUT:\n-----\n{}\n'\ '-----\nERROR:\n-----\n{}\n-----' raise TargetError(message.format(raw_output, error)) return output
def roi_end(self, label): if label not in self.rois: raise KeyError('Incorrect ROI label: {}'.format(label)) if not self.rois[label].stop(): raise TargetError('ROI {} was not running'.format(label))
def __init__( self, target, events=None, functions=None, buffer_size=None, buffer_size_step=1000, tracing_path='/sys/kernel/debug/tracing', automark=True, autoreport=True, autoview=False, no_install=False, strict=False, report_on_target=False, ): super(FtraceCollector, self).__init__(target) self.events = events if events is not None else DEFAULT_EVENTS self.functions = functions self.buffer_size = buffer_size self.buffer_size_step = buffer_size_step self.tracing_path = tracing_path self.automark = automark self.autoreport = autoreport self.autoview = autoview self.report_on_target = report_on_target self.target_output_file = target.path.join( self.target.working_directory, OUTPUT_TRACE_FILE) text_file_name = target.path.splitext(OUTPUT_TRACE_FILE)[0] + '.txt' self.target_text_file = target.path.join(self.target.working_directory, text_file_name) self.target_binary = None self.host_binary = None self.start_time = None self.stop_time = None self.event_string = None self.function_string = None self._reset_needed = True # Setup tracing paths self.available_events_file = self.target.path.join( self.tracing_path, 'available_events') self.available_functions_file = self.target.path.join( self.tracing_path, 'available_filter_functions') self.buffer_size_file = self.target.path.join(self.tracing_path, 'buffer_size_kb') self.current_tracer_file = self.target.path.join( self.tracing_path, 'current_tracer') self.function_profile_file = self.target.path.join( self.tracing_path, 'function_profile_enabled') self.marker_file = self.target.path.join(self.tracing_path, 'trace_marker') self.ftrace_filter_file = self.target.path.join( self.tracing_path, 'set_ftrace_filter') self.host_binary = which('trace-cmd') self.kernelshark = which('kernelshark') if not self.target.is_rooted: raise TargetError( 'trace-cmd instrument cannot be used on an unrooted device.') if self.autoreport and not self.report_on_target and self.host_binary is None: raise HostError( 'trace-cmd binary must be installed on the host if autoreport=True.' ) if self.autoview and self.kernelshark is None: raise HostError( 'kernelshark binary must be installed on the host if autoview=True.' ) if not no_install: host_file = os.path.join(PACKAGE_BIN_DIRECTORY, self.target.abi, 'trace-cmd') self.target_binary = self.target.install(host_file) else: if not self.target.is_installed('trace-cmd'): raise TargetError( 'No trace-cmd found on device and no_install=True is specified.' ) self.target_binary = 'trace-cmd' # Validate required events to be traced available_events = self.target.execute('cat {}'.format( self.available_events_file), as_root=True).splitlines() selected_events = [] for event in self.events: # Convert globs supported by FTrace into valid regexp globs _event = event if event[0] != '*': _event = '*' + event event_re = re.compile(_event.replace('*', '.*')) # Select events matching the required ones if len(filter(event_re.match, available_events)) == 0: message = 'Event [{}] not available for tracing'.format(event) if strict: raise TargetError(message) self.target.logger.warning(message) else: selected_events.append(event) # If function profiling is enabled we always need at least one event. # Thus, if not other events have been specified, try to add at least # a tracepoint which is always available and possibly triggered few # times. if self.functions and len(selected_events) == 0: selected_events = ['sched_wakeup_new'] self.event_string = _build_trace_events(selected_events) # Check for function tracing support if self.functions: if not self.target.file_exists(self.function_profile_file): raise TargetError('Function profiling not supported. '\ 'A kernel build with CONFIG_FUNCTION_PROFILER enable is required') # Validate required functions to be traced available_functions = self.target.execute( 'cat {}'.format(self.available_functions_file), as_root=True).splitlines() selected_functions = [] for function in self.functions: if function not in available_functions: message = 'Function [{}] not available for profiling'.format( function) if strict: raise TargetError(message) self.target.logger.warning(message) else: selected_functions.append(function) self.function_string = _build_trace_functions(selected_functions)
def __init__(self, platform, host=None, username=None, password=None, port=None, timeout=None, password_prompt=None, original_prompt=None, strip_echoed_commands=False, ): if host is not None: host_system = socket.gethostname() if host_system != host: raise TargetError("Gem5Connection can only connect to gem5 " "simulations on your current host, which " "differs from the one given {}!" .format(host_system, host)) if username is not None and username != 'root': raise ValueError('User should be root in gem5!') if password is not None and password != '': raise ValueError('No password needed in gem5!') self.username = '******' self.is_rooted = True self.password = None self.port = None # Flag to indicate whether commands are echoed by the simulated system self.strip_echoed_commands = strip_echoed_commands # Long timeouts to account for gem5 being slow # Can be overriden if the given timeout is longer self.default_timeout = 3600 if timeout is not None: if timeout > self.default_timeout: logger.info('Overwriting the default timeout of gem5 ({})' ' to {}'.format(self.default_timeout, timeout)) self.default_timeout = timeout else: logger.info('Ignoring the given timeout --> gem5 needs longer timeouts') self.ready_timeout = self.default_timeout * 3 # Counterpart in gem5_interact_dir self.gem5_input_dir = '/mnt/host/' # Location of m5 binary in the gem5 simulated system self.m5_path = None # Actual telnet connection to gem5 simulation self.conn = None # Flag to indicate the gem5 device is ready to interact with the # outer world self.ready = False # Lock file to prevent multiple connections to same gem5 simulation # (gem5 does not allow this) self.lock_directory = '/tmp/' self.lock_file_name = None # Will be set once connected to gem5 # These parameters will be set by either the method to connect to the # gem5 platform or directly to the gem5 simulation # Intermediate directory to push things to gem5 using VirtIO self.gem5_interact_dir = None # Directory to store output from gem5 on the host self.gem5_out_dir = None # Actual gem5 simulation self.gem5simulation = None # Connect to gem5 if platform: self._connect_gem5_platform(platform) # Wait for boot self._wait_for_boot() # Mount the virtIO to transfer files in/out gem5 system self._mount_virtio()
def set_governor(self, governor): if governor not in self.governors: raise TargetError('Governor {} not supported for gpu {}'.format(governor, cpu)) self.target.write_value("/sys/kernel/gpu/gpu_governor", governor)
def __init__( self, target, events=None, functions=None, buffer_size=None, buffer_size_step=1000, tracing_path='/sys/kernel/debug/tracing', automark=True, autoreport=True, autoview=False, no_install=False, ): super(FtraceCollector, self).__init__(target) self.events = events if events is not None else DEFAULT_EVENTS self.functions = functions self.buffer_size = buffer_size self.buffer_size_step = buffer_size_step self.tracing_path = tracing_path self.automark = automark self.autoreport = autoreport self.autoview = autoview self.target_output_file = os.path.join(self.target.working_directory, OUTPUT_TRACE_FILE) self.target_binary = None self.host_binary = None self.start_time = None self.stop_time = None self.event_string = _build_trace_events(self.events) self.function_string = _build_trace_functions(self.functions) self._reset_needed = True # Setup tracing paths self.available_functions_file = self.target.path.join( self.tracing_path, 'available_filter_functions') self.buffer_size_file = self.target.path.join(self.tracing_path, 'buffer_size_kb') self.current_tracer_file = self.target.path.join( self.tracing_path, 'current_tracer') self.function_profile_file = self.target.path.join( self.tracing_path, 'function_profile_enabled') self.marker_file = self.target.path.join(self.tracing_path, 'trace_marker') self.ftrace_filter_file = self.target.path.join( self.tracing_path, 'set_ftrace_filter') self.host_binary = which('trace-cmd') self.kernelshark = which('kernelshark') if not self.target.is_rooted: raise TargetError( 'trace-cmd instrument cannot be used on an unrooted device.') if self.autoreport and self.host_binary is None: raise HostError( 'trace-cmd binary must be installed on the host if autoreport=True.' ) if self.autoview and self.kernelshark is None: raise HostError( 'kernelshark binary must be installed on the host if autoview=True.' ) if not no_install: host_file = os.path.join(PACKAGE_BIN_DIRECTORY, self.target.abi, 'trace-cmd') self.target_binary = self.target.install(host_file) else: if not self.target.is_installed('trace-cmd'): raise TargetError( 'No trace-cmd found on device and no_install=True is specified.' ) self.target_binary = 'trace-cmd' # Check for function tracing support if self.functions: if not self.target.file_exists(self.function_profile_file): raise TargetError('Function profiling not supported. '\ 'A kernel build with CONFIG_FUNCTION_PROFILER enable is required') # Validate required functions to be traced available_functions = self.target.execute('cat {}'.format( self.available_functions_file)).splitlines() for function in self.functions: if function not in available_functions: raise TargetError( 'Function [{}] not available for filtering'.format( function))