def stop(self): self.process.terminate() timeout_secs = 10 output = '' for _ in range(timeout_secs): if self.process.poll() is not None: break time.sleep(1) else: output += _read_nonblock(self.process.stdout) self.process.kill() self.logger.error('iio-capture did not terminate gracefully') if self.process.poll() is None: msg = 'Could not terminate iio-capture:\n{}' raise HostError(msg.format(output)) if self.process.returncode != 15: # iio-capture exits with 15 when killed if sys.version_info[0] == 3: output += self.process.stdout.read().decode( sys.stdout.encoding or 'utf-8', 'replace') else: output += self.process.stdout.read() self.logger.info('ACME instrument encountered an error, ' 'you may want to try rebooting the ACME device:\n' ' ssh root@{} reboot'.format(self.host)) raise HostError( 'iio-capture exited with an error ({}), output:\n{}'.format( self.process.returncode, output)) if not os.path.isfile(self.raw_data_file): raise HostError('Output CSV not generated.') self.process = None
def __init__( self, target, resistor_values, # pylint: disable=R0914 labels=None, host='localhost', port=45677, device_id='Dev1', v_range=2.5, dv_range=0.2, sample_rate_hz=10000, channel_map=(0, 1, 2, 3, 4, 5, 6, 7, 16, 17, 18, 19, 20, 21, 22, 23), keep_raw=False, time_as_clock_boottime=True): # pylint: disable=no-member super(DaqInstrument, self).__init__(target) self.keep_raw = keep_raw self._need_reset = True self._raw_files = [] self.tempdir = None self.target_boottime_clock_at_start = 0.0 if DaqClient is None: raise HostError( 'Could not import "daqpower": {}'.format(import_error_mesg)) if labels is None: labels = ['PORT_{}'.format(i) for i in range(len(resistor_values))] if len(labels) != len(resistor_values): raise ValueError( '"labels" and "resistor_values" must be of the same length') self.daq_client = DaqClient(host, port) try: devices = self.daq_client.list_devices() if device_id not in devices: msg = 'Device "{}" is not found on the DAQ server. Available devices are: "{}"' raise ValueError(msg.format(device_id, ', '.join(devices))) except Exception as e: raise HostError('Problem querying DAQ server: {}'.format(e)) self.device_config = DeviceConfiguration( device_id=device_id, v_range=v_range, dv_range=dv_range, sampling_rate=sample_rate_hz, resistor_values=resistor_values, channel_map=channel_map, labels=labels) self.sample_rate_hz = sample_rate_hz self.time_as_clock_boottime = time_as_clock_boottime self.add_channel('Time', 'time') for label in labels: for kind in ['power', 'voltage']: self.add_channel(label, kind) if time_as_clock_boottime: host_path = os.path.join(PACKAGE_BIN_DIRECTORY, self.target.abi, 'get_clock_boottime') self.clock_boottime_cmd = self.target.install_if_needed( host_path, search_system_binaries=False)
def _validate_image_bundle(self, image_bundle): if not tarfile.is_tarfile(image_bundle): raise HostError('File {} is not a tarfile'.format(image_bundle)) with tarfile.open(image_bundle) as tar: files = [tf.name for tf in tar.getmembers()] if not any(pf in files for pf in (self.partitions_file_name, '{}/{}'.format(files[0], self.partitions_file_name))): HostError('Image bundle does not contain the required partition file (see documentation)')
def adb_get_device(timeout=None, adb_server=None): """ Returns the serial number of a connected android device. If there are more than one device connected to the machine, or it could not find any device connected, :class:`devlib.exceptions.HostError` is raised. """ # TODO this is a hacky way to issue a adb command to all listed devices # Ensure server is started so the 'daemon started successfully' message # doesn't confuse the parsing below adb_command(None, 'start-server', adb_server=adb_server) # The output of calling adb devices consists of a heading line then # a list of the devices sperated by new line # The last line is a blank new line. in otherwords, if there is a device found # then the output length is 2 + (1 for each device) start = time.time() while True: output = adb_command(None, "devices", adb_server=adb_server).splitlines() # pylint: disable=E1103 output_length = len(output) if output_length == 3: # output[1] is the 2nd line in the output which has the device name # Splitting the line by '\t' gives a list of two indexes, which has # device serial in 0 number and device type in 1. return output[1].split('\t')[0] elif output_length > 3: message = '{} Android devices found; either explicitly specify ' +\ 'the device you want, or make sure only one is connected.' raise HostError(message.format(output_length - 2)) else: if timeout < time.time() - start: raise HostError('No device is connected and available') time.sleep(1)
def __try_import(path): try: return __import__(path, {}, {}, ['']) except Exception as e: he = HostError('Could not load {}: {}'.format(path, str(e))) he.module = path he.exc_info = sys.exc_info() he.orig_exc = e raise he
def __init__( self, target, events=None, buffer_size=None, buffer_size_step=1000, buffer_size_file='/sys/kernel/debug/tracing/buffer_size_kb', marker_file='/sys/kernel/debug/tracing/trace_marker', 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.buffer_size = buffer_size self.buffer_size_step = buffer_size_step self.buffer_size_file = buffer_size_file self.marker_file = marker_file 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._reset_needed = True 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'
def get_mapping(base_dir, partition_file): mapping = {} with open(partition_file) as pf: for line in pf: pair = line.split() if len(pair) != 2: HostError('partitions.txt is not properly formated') image_path = os.path.join(base_dir, pair[1]) if not os.path.isfile(expand_path(image_path)): HostError('file {} was not found in the bundle or was misplaced'.format(pair[1])) mapping[pair[0]] = image_path return mapping
def __init__( self, target, resistor_values, # pylint: disable=R0914 labels=None, host='localhost', port=45677, device_id='Dev1', v_range=2.5, dv_range=0.2, sample_rate_hz=10000, channel_map=(0, 1, 2, 3, 4, 5, 6, 7, 16, 17, 18, 19, 20, 21, 22, 23), ): # pylint: disable=no-member super(DaqInstrument, self).__init__(target) self._need_reset = True self._raw_files = [] if execute_command is None: raise HostError( 'Could not import "daqpower": {}'.format(import_error_mesg)) if labels is None: labels = [ 'PORT_{}'.format(i) for i in xrange(len(resistor_values)) ] if len(labels) != len(resistor_values): raise ValueError( '"labels" and "resistor_values" must be of the same length') self.server_config = ServerConfiguration(host=host, port=port) result = self.execute('list_devices') if result.status == Status.OK: if device_id not in result.data: raise ValueError( 'Device "{}" is not found on the DAQ server.'.format( device_id)) elif result.status != Status.OKISH: raise HostError('Problem querying DAQ server: {}'.format( result.message)) self.device_config = DeviceConfiguration( device_id=device_id, v_range=v_range, dv_range=dv_range, sampling_rate=sample_rate_hz, resistor_values=resistor_values, channel_map=channel_map, labels=labels) self.sample_rate_hz = sample_rate_hz for label in labels: for kind in ['power', 'voltage']: self.add_channel(label, kind)
def validate_image_bundle(bundle): if not tarfile.is_tarfile(bundle): raise HostError( 'Image bundle {} does not appear to be a valid TAR file.'.format( bundle)) with tarfile.open(bundle) as tar: try: tar.getmember('config.txt') except KeyError: try: tar.getmember('./config.txt') except KeyError: msg = 'Tarball {} does not appear to be a valid image bundle (did not see config.txt).' raise HostError(msg.format(bundle))
def _give_password(password, command): if not sshpass: raise HostError( 'Must have sshpass installed on the host in order to use password-based auth.' ) pass_string = "sshpass -p '{}' ".format(password) return pass_string + command
def stop(self): process = self.process self.process = None if not process: raise RuntimeError('Monsoon script not started') process.poll() if process.returncode is not None: stdout, stderr = process.communicate() raise HostError( 'Monsoon script exited unexpectedly with exit code {}.\n' 'stdout:\n{}\nstderr:\n{}'.format(process.returncode, stdout, stderr)) process.send_signal(signal.SIGINT) stderr = process.stderr.read() self.buffer_file.close() with open(self.buffer_file.name) as f: stdout = f.read() os.remove(self.buffer_file.name) self.buffer_file = None self.output = (stdout, stderr) os.system(self.monsoon_bin + ' --usbpassthrough on') # Wait for USB connection to be restored print('waiting for usb connection to be back') os.system('adb wait-for-device')
def __init__( self, target, resistor_values, labels=None, device_entry='/dev/ttyACM0', ): super(EnergyProbeInstrument, self).__init__(target) self.resistor_values = resistor_values if labels is not None: self.labels = labels else: self.labels = [ 'PORT_{}'.format(i) for i in xrange(len(resistor_values)) ] self.device_entry = device_entry self.caiman = which('caiman') if self.caiman is None: raise HostError('caiman must be installed on the host ' '(see https://github.com/ARM-software/caiman)') if pandas is None: self.logger.info( "pandas package will significantly speed up this instrument") self.logger.info("to install it try: pip install pandas") self.attributes_per_sample = 3 self.bytes_per_sample = self.attributes_per_sample * 4 self.attributes = ['power', 'voltage', 'current'] self.command = None self.raw_output_directory = None self.process = None for label in self.labels: for kind in self.attributes: self.add_channel(label, kind)
def get_connection(timeout, init_dtr=None, logcls=SerialLogger, logfile=None, *args, **kwargs): if init_dtr is not None: kwargs['dsrdtr'] = True try: conn = serial.Serial(*args, **kwargs) except serial.SerialException as e: raise HostError(str(e)) if init_dtr is not None: conn.setDTR(init_dtr) conn.nonblocking() conn.flushOutput() target = fdpexpect.fdspawn(conn.fileno(), timeout=timeout, logfile=logfile) target.logfile_read = logcls('read') target.logfile_send = logcls('send') # Monkey-patching sendline to introduce a short delay after # chacters are sent to the serial. If two sendline s are issued # one after another the second one might start putting characters # into the serial device before the first one has finished, causing # corruption. The delay prevents that. tsln = target.sendline def sendline(x): tsln(x) time.sleep(0.1) target.sendline = sendline return target, conn
def __init__(self, target, categories=None, buffer_size=None, strict=False): super(SystraceCollector, self).__init__(target) self.categories = categories or DEFAULT_CATEGORIES self.buffer_size = buffer_size self._systrace_process = None self._tmpfile = None # Try to find a systrace binary self.systrace_binary = None platform_tools = devlib.utils.android.platform_tools systrace_binary_path = os.path.join(platform_tools, 'systrace', 'systrace.py') if not os.path.isfile(systrace_binary_path): raise HostError('Could not find any systrace binary under {}'.format(platform_tools)) self.systrace_binary = systrace_binary_path # Filter the requested categories for category in self.categories: if category not in self.available_categories: message = 'Category [{}] not available for tracing'.format(category) if strict: raise TargetStableError(message) self.logger.warning(message) self.categories = list(set(self.categories) & set(self.available_categories)) if not self.categories: raise TargetStableError('None of the requested categories are available')
def _goto_dump(self, stats_file, target_dump): if target_dump < 0: raise HostError('Cannot go to dump {}'.format(target_dump)) # Go to required dump quickly if it was visited before if target_dump in self._dump_pos_cache: stats_file.seek(self._dump_pos_cache[target_dump]) return target_dump # Or start from the closest dump already visited before the required one prev_dumps = filter(lambda x: x < target_dump, self._dump_pos_cache.keys()) curr_dump = max(prev_dumps) curr_pos = self._dump_pos_cache[curr_dump] stats_file.seek(curr_pos) # And iterate until target_dump dump_iterator = iter_statistics_dump(stats_file) while curr_dump < target_dump: try: next(dump_iterator) except StopIteration: break # End of passed dump is beginning og next one curr_pos = stats_file.tell() curr_dump += 1 self._dump_pos_cache[curr_dump] = curr_pos return curr_dump
def __init__( self, target, resistor_values, labels=None, device_entry='/dev/ttyACM0', ): super(EnergyProbeInstrument, self).__init__(target) self.resistor_values = resistor_values if labels is not None: self.labels = labels else: self.labels = [ 'PORT_{}'.format(i) for i in range(len(resistor_values)) ] self.device_entry = device_entry self.caiman = which('caiman') if self.caiman is None: raise HostError('caiman must be installed on the host ' '(see https://github.com/ARM-software/caiman)') self.attributes_per_sample = 3 self.bytes_per_sample = self.attributes_per_sample * 4 self.attributes = ['power', 'voltage', 'current'] self.command = None self.raw_output_directory = None self.process = None self.sample_rate_hz = 10000 # Determined empirically self.raw_data_file = None for label in self.labels: for kind in self.attributes: self.add_channel(label, kind)
def __init__(self, target, apk=None, service='.TrafficMetricsService'): """ Additional paramerter: :apk: Path to the APK file that contains ``com.arm.devlab.netstats`` package. If not specified, it will be assumed that an APK with name "netstats.apk" is located in the same directory as the Python module for the instrument. :service: Name of the service to be launched. This service must be present in the APK. """ if target.os != 'android': raise TargetError( 'netstats insturment only supports Android targets') if apk is None: apk = os.path.join(THIS_DIR, 'netstats.apk') if not os.path.isfile(apk): raise HostError( 'APK for netstats instrument does not exist ({})'.format(apk)) super(NetstatsInstrument, self).__init__(target) self.apk = apk self.package = ApkInfo(self.apk).package self.service = service self.tag = None self.command = None self.stop_command = 'am kill {}'.format(self.package) for package in self.target.list_packages(): self.add_channel(package, 'tx') self.add_channel(package, 'rx')
def adb_connect(device, timeout=None, attempts=MAX_ATTEMPTS, adb_server=None): _check_env() tries = 0 output = None while tries <= attempts: tries += 1 if device: if "." in device: # Connect is required only for ADB-over-IP # ADB does not automatically remove a network device from it's # devices list when the connection is broken by the remote, so the # adb connection may have gone "stale", resulting in adb blocking # indefinitely when making calls to the device. To avoid this, # always disconnect first. adb_disconnect(device, adb_server) adb_cmd = get_adb_command(None, 'connect', adb_server) command = '{} {}'.format(adb_cmd, quote(device)) logger.debug(command) output, _ = check_output(command, shell=True, timeout=timeout) if _ping(device, adb_server): break time.sleep(10) else: # did not connect to the device message = 'Could not connect to {}'.format(device or 'a device') if output: message += '; got: "{}"'.format(output) raise HostError(message)
def _scp(self, source, dest, timeout=30): # NOTE: the version of scp in Ubuntu 12.04 occasionally (and bizarrely) # fails to connect to a device if port is explicitly specified using -P # option, even if it is the default port, 22. To minimize this problem, # only specify -P for scp if the port is *not* the default. port_string = '-P {}'.format(quote(str( self.port))) if (self.port and self.port != 22) else '' keyfile_string = '-i {}'.format(quote( self.keyfile)) if self.keyfile else '' options = " ".join( ["-o{}={}".format(key, val) for key, val in self.options.items()]) command = '{} {} -r {} {} {} {}'.format(scp, options, keyfile_string, port_string, quote(source), quote(dest)) command_redacted = command logger.debug(command) if self.password: command, command_redacted = _give_password(self.password, command) try: check_output(command, timeout=timeout, shell=True) except subprocess.CalledProcessError as e: raise_from( HostError("Failed to copy file with '{}'. Output:\n{}".format( command_redacted, e.output)), None) except TimeoutError as e: raise TimeoutError(command_redacted, e.output)
def stop(self): process = self.process self.process = None if not process: raise RuntimeError('Monsoon script not started') process.poll() if process.returncode is not None: stdout, stderr = process.communicate() if sys.version_info[0] == 3: stdout = stdout.encode(sys.stdout.encoding) stderr = stderr.encode(sys.stdout.encoding) raise HostError( 'Monsoon script exited unexpectedly with exit code {}.\n' 'stdout:\n{}\nstderr:\n{}'.format(process.returncode, stdout, stderr)) process.send_signal(signal.SIGINT) stderr = process.stderr.read() self.buffer_file.close() with open(self.buffer_file.name) as f: stdout = f.read() os.remove(self.buffer_file.name) self.buffer_file = None self.output = (stdout, stderr)
def report(self, binfile, destfile): # To get the output of trace.dat, trace-cmd must be installed # This is done host-side because the generated file is very large try: command = '{} report {} > {}'.format(self.host_binary, binfile, destfile) self.logger.debug(command) process = subprocess.Popen(command, stderr=subprocess.PIPE, shell=True) _, error = process.communicate() if process.returncode: raise TargetError( 'trace-cmd returned non-zero exit code {}'.format( process.returncode)) if error: # logged at debug level, as trace-cmd always outputs some # errors that seem benign. self.logger.debug(error) if os.path.isfile(destfile): self.logger.debug('Verifying traces.') with open(destfile) as fh: for line in fh: if 'EVENTS DROPPED' in line: self.logger.warning('Dropped events detected.') break else: self.logger.debug('Trace verified.') else: self.logger.warning('Could not generate trace.txt.') except OSError: raise HostError( 'Could not find trace-cmd. Please make sure it is installed and is in PATH.' )
def get_data(self, output_file): temp_file = tempfile.mktemp() self.target.pull(self.on_target_file, temp_file) self.target.remove(self.on_target_file) with open(temp_file, 'rb') as fh: reader = csv.reader(fh) headings = reader.next() # Figure out which columns from the collected csv we actually want select_columns = [] for chan in self.active_channels: try: select_columns.append(headings.index(chan.name)) except ValueError: raise HostError('Channel "{}" is not in {}'.format( chan.name, temp_file)) with open(output_file, 'wb') as wfh: write_headings = [ '{}_{}'.format(c.site, c.kind) for c in self.active_channels ] writer = csv.writer(wfh) writer.writerow(write_headings) for row in reader: write_row = [row[c] for c in select_columns] writer.writerow(write_row) return MeasurementsCsv(output_file, self.active_channels)
def reset(self, sites=None, kinds=None): super(DaqInstrument, self).reset(sites, kinds) self.execute('close') result = self.execute('configure', config=self.device_config) if not result.status == Status.OK: # pylint: disable=no-member raise HostError(result.message) self._need_reset = False
def push(self, source, dest, timeout=None): if timeout is None: timeout = self.timeout command = "push '{}' '{}'".format(source, dest) if not os.path.exists(source): raise HostError('No such file "{}"'.format(source)) return adb_command(self.device, command, timeout=timeout, adb_server=self.adb_server)
def _check_env(): global ssh, scp, sshpass # pylint: disable=global-statement if not ssh: ssh = which('ssh') scp = which('scp') sshpass = which('sshpass') if not (ssh and scp): raise HostError('OpenSSH must be installed on the host.')
def _init_common(env): logger.debug('ANDROID_HOME: {}'.format(env.android_home)) build_tools_directory = os.path.join(env.android_home, 'build-tools') if not os.path.isdir(build_tools_directory): msg = '''ANDROID_HOME ({}) does not appear to have valid Android SDK install (cannot find build-tools)''' raise HostError(msg.format(env.android_home)) versions = os.listdir(build_tools_directory) for version in reversed(sorted(versions)): aapt_path = os.path.join(build_tools_directory, version, 'aapt') if os.path.isfile(aapt_path): logger.debug('Using aapt for version {}'.format(version)) env.aapt = aapt_path break else: raise HostError('aapt not found. Please make sure at least one Android ' 'platform is installed.')
def _give_password(password, command): if not sshpass: raise HostError( 'Must have sshpass installed on the host in order to use password-based auth.' ) pass_template = "sshpass -p {} " pass_string = pass_template.format(quote(password)) redacted_string = pass_template.format(quote('<redacted>')) return (pass_string + command, redacted_string + command)
def stop(self): self.process.poll() if self.process.returncode is not None: stdout, stderr = self.process.communicate() raise HostError( 'Energy Probe: Caiman exited unexpectedly with exit code {}.\n' 'stdout:\n{}\nstderr:\n{}'.format(self.process.returncode, stdout, stderr)) os.killpg(self.process.pid, signal.SIGINT)
def get_data(self, outfile): # pylint: disable=R0914 self.tempdir = tempfile.mkdtemp(prefix='daq-raw-') self.daq_client.get_data(self.tempdir) raw_file_map = {} for entry in os.listdir(self.tempdir): site = os.path.splitext(entry)[0] path = os.path.join(self.tempdir, entry) raw_file_map[site] = path self._raw_files.append(path) active_sites = unique([c.site for c in self.active_channels]) file_handles = [] try: site_readers = {} for site in active_sites: try: site_file = raw_file_map[site] reader, fh = create_reader(site_file) site_readers[site] = reader file_handles.append(fh) except KeyError: if not site.startswith("Time"): message = 'Could not get DAQ trace for {}; Obtained traces are in {}' raise HostError(message.format(site, self.tempdir)) # The first row is the headers channel_order = ['Time_time'] for site, reader in site_readers.items(): channel_order.extend( ['{}_{}'.format(site, kind) for kind in next(reader)]) def _read_rows(): row_iter = zip_longest(*site_readers.values(), fillvalue=(None, None)) for raw_row in row_iter: raw_row = list(chain.from_iterable(raw_row)) raw_row.insert(0, _read_rows.row_time_s) yield raw_row _read_rows.row_time_s += 1.0 / self.sample_rate_hz _read_rows.row_time_s = self.target_boottime_clock_at_start with csvwriter(outfile) as writer: field_names = [c.label for c in self.active_channels] writer.writerow(field_names) for raw_row in _read_rows(): row = [ raw_row[channel_order.index(f)] for f in field_names ] writer.writerow(row) return MeasurementsCsv(outfile, self.active_channels, self.sample_rate_hz) finally: for fh in file_handles: fh.close()
def get_data(self, outfile): # pylint: disable=R0914 tempdir = tempfile.mkdtemp(prefix='daq-raw-') self.execute('get_data', output_directory=tempdir) raw_file_map = {} for entry in os.listdir(tempdir): site = os.path.splitext(entry)[0] path = os.path.join(tempdir, entry) raw_file_map[site] = path self._raw_files.append(path) active_sites = unique([c.site for c in self.active_channels]) file_handles = [] try: site_readers = {} for site in active_sites: try: site_file = raw_file_map[site] fh = open(site_file, 'rb') site_readers[site] = csv.reader(fh) file_handles.append(fh) except KeyError: message = 'Could not get DAQ trace for {}; Obtained traces are in {}' raise HostError(message.format(site, tempdir)) # The first row is the headers channel_order = [] for site, reader in site_readers.iteritems(): channel_order.extend( ['{}_{}'.format(site, kind) for kind in reader.next()]) def _read_next_rows(): parts = [] for reader in site_readers.itervalues(): try: parts.extend(reader.next()) except StopIteration: parts.extend([None, None]) return list(chain(parts)) with open(outfile, 'wb') as wfh: field_names = [c.label for c in self.active_channels] writer = csv.writer(wfh) writer.writerow(field_names) raw_row = _read_next_rows() while any(raw_row): row = [ raw_row[channel_order.index(f)] for f in field_names ] writer.writerow(row) raw_row = _read_next_rows() return MeasurementsCsv(outfile, self.active_channels, self.sample_rate_hz) finally: for fh in file_handles: fh.close()