Пример #1
0
    def _get_plat_info(self, trace_name=None):
        trace_dir = self.traces_dir
        if trace_name:
            trace_dir = os.path.join(trace_dir, trace_name)

        path = os.path.join(trace_dir, 'plat_info.yml')
        return PlatformInfo.from_yaml_map(path)
Пример #2
0
    def __init__(self,
                 trace_path,
                 plat_info=None,
                 events=None,
                 normalize_time=True,
                 trace_format='FTrace',
                 plots_dir=None,
                 plots_prefix=''):

        super().__init__()

        logger = self.get_logger()

        if plat_info is None:
            plat_info = PlatformInfo()

        # The platform information used to run the experiments
        self.plat_info = plat_info

        self.normalize_time = normalize_time

        proxy_cls = type(self.analysis)
        self.events = self._process_events(events, proxy_cls)

        # Path to the trace file
        self.trace_path = trace_path

        # By default, use the trace dir to save plots
        self.plots_dir = plots_dir if plots_dir else os.path.dirname(
            trace_path)

        self.plots_prefix = plots_prefix

        self._parse_trace(self.trace_path, trace_format, normalize_time)
Пример #3
0
    def from_one_conf(cls, path):
        """
        Create a :class:`Target` from a single YAML configuration file.

        This file will be used to provide a :class:`TargetConf` and
        :class:`lisa.platforms.platinfo.PlatformInfo` instances.
        """
        conf = TargetConf.from_yaml_map(path)
        try:
            plat_info = PlatformInfo.from_yaml_map(path)
        except Exception as e:
            cls.get_logger().warn('No platform information could be found: {}'.format(e))
            plat_info = None
        return cls.from_conf(conf=conf, plat_info=plat_info)
Пример #4
0
    def from_one_conf(cls, path):
        """
        Create a :class:`Target` from a single YAML configuration file.

        This file will be used to provide a :class:`TargetConf` and
        :class:`lisa.platforms.platinfo.PlatformInfo` instances.

        .. note:: Only load trusted YAML files as it can lead to abritrary code
            execution.
        """
        conf = TargetConf.from_yaml_map(path)
        try:
            plat_info = PlatformInfo.from_yaml_map(path)
        except Exception as e:  # pylint: disable=broad-except
            cls.get_logger().warning(
                f'No platform information could be found: {e}')
            plat_info = None
        return cls.from_conf(conf=conf, plat_info=plat_info)
Пример #5
0
    def _init_plat_info(self, plat_info=None, name=None, **kwargs):

        if plat_info is None:
            plat_info = PlatformInfo()
        else:
            # Make a copy of the PlatformInfo so we don't modify the original
            # one we were passed when adding the target source to it
            plat_info = copy.copy(plat_info)
            self.logger.info(
                f'User-defined platform information:\n{plat_info}')

        # Take the board name from the target configuration so it becomes
        # available for later inspection. That board name is mostly free form
        # and no specific value should be expected for a given kind of board
        # (i.e. a Juno board might be named "foo-bar-juno-on-my-desk")
        if name:
            plat_info.add_src('target-conf', dict(name=name))

        rta_calib_res_dir = ArtifactPath.join(self._res_dir, 'rta_calib')
        os.makedirs(rta_calib_res_dir, exist_ok=True)
        plat_info.add_target_src(self, rta_calib_res_dir, **kwargs)
        self.plat_info = plat_info
Пример #6
0
 def __init__(self, res_dir, shell_output):
     plat_info = PlatformInfo()
     super().__init__(res_dir, plat_info)
     self.shell_output = shell_output
Пример #7
0
    def _calibrate(cls, target, res_dir):
        res_dir = res_dir if res_dir else target.get_res_dir("rta_calib",
                                                             symlink=False)

        pload_regexp = re.compile(r'pLoad = ([0-9]+)ns')
        pload = {}

        logger = cls.get_logger()

        # Create calibration task
        if target.is_rooted:
            max_rtprio = int(target.execute('ulimit -Hr').splitlines()[0])
            logger.debug(f'Max RT prio: {max_rtprio}')

            priority = max_rtprio + 1 if max_rtprio <= 10 else 10
            sched_policy = 'FIFO'
        else:
            logger.warning(
                'Will use default scheduler class instead of RT since the target is not rooted'
            )
            priority = None
            sched_policy = None

        for cpu in target.list_online_cpus():
            logger.info(f'CPU{cpu} calibration...')

            # RT-app will run a calibration for us, so we just need to
            # run a dummy task and read the output
            calib_task = Periodic(
                duty_cycle_pct=100,
                duration_s=0.001,
                period_ms=1,
                priority=priority,
                sched_policy=sched_policy,
            )
            rta = cls.by_profile(target,
                                 name=f"rta_calib_cpu{cpu}",
                                 profile={'task1': calib_task},
                                 calibration=f"CPU{cpu}",
                                 res_dir=res_dir)

            with rta, target.freeze_userspace():
                # Disable CPU capacities update, since that leads to infinite
                # recursion
                rta.run(as_root=target.is_rooted, update_cpu_capacities=False)

            for line in rta.output.split('\n'):
                pload_match = re.search(pload_regexp, line)
                if pload_match is None:
                    continue
                pload[cpu] = int(pload_match.group(1))
                logger.debug(f'>>> CPU{cpu}: {pload[cpu]}')

        # Avoid circular import issue
        from lisa.platforms.platinfo import PlatformInfo
        snippet_plat_info = PlatformInfo({
            'rtapp': {
                'calib': pload,
            },
        })
        logger.info(
            f'Platform info rt-app calibration configuration:\n{snippet_plat_info.to_yaml_map_str()}'
        )

        plat_info = target.plat_info

        # Sanity check calibration values for asymmetric systems if we have
        # access to capacities
        try:
            orig_capacities = plat_info['cpu-capacities']['orig']
        except KeyError:
            return pload

        capa_ploads = {
            capacity: {cpu: pload[cpu]
                       for cpu in cpus}
            for capacity, cpus in group_by_value(orig_capacities).items()
        }

        # Find the min pload per capacity level, i.e. the fastest detected CPU.
        # It is more likely to represent the right pload, as it has suffered
        # from less IRQ slowdown or similar disturbances that might be random.
        capa_pload = {
            capacity: min(ploads.values())
            for capacity, ploads in capa_ploads.items()
        }

        # Sort by capacity
        capa_pload_list = sorted(capa_pload.items())
        # unzip the list of tuples
        _, pload_list = zip(*capa_pload_list)

        # If sorting according to capa was not equivalent to reverse sorting
        # according to pload (small pload=fast cpu)
        if list(pload_list) != sorted(pload_list, reverse=True):
            raise CalibrationError(
                'Calibration values reports big cores less capable than LITTLE cores'
            )

        # Check that the CPU capacities seen by rt-app are similar to the one
        # the kernel uses
        orig_capacities = plat_info['cpu-capacities']['orig']
        true_capacities = cls.get_cpu_capacities_from_calibrations(
            orig_capacities, pload)
        cls.warn_capacities_mismatch(orig_capacities, true_capacities)

        return pload
Пример #8
0
    def _calibrate(cls, target, res_dir):
        res_dir = res_dir if res_dir else target .get_res_dir(
            "rta_calib", symlink=False
        )

        pload_regexp = re.compile(r'pLoad = ([0-9]+)ns')
        pload = {}

        logger = cls.get_logger()

        # Create calibration task
        if target.is_rooted:
            max_rtprio = int(target.execute('ulimit -Hr').split('\r')[0])
            logger.debug('Max RT prio: {}'.format(max_rtprio))

            priority = max_rtprio if max_rtprio <= 10 else 10
            sched_policy = 'FIFO'
        else:
            logger.warning('Will use default scheduler class instead of RT since the target is not rooted')
            priority = None
            sched_policy = None

        for cpu in target.list_online_cpus():
            logger.info('CPU{} calibration...'.format(cpu))

            # RT-app will run a calibration for us, so we just need to
            # run a dummy task and read the output
            calib_task = Periodic(
                duty_cycle_pct=100,
                duration_s=0.001,
                period_ms=1,
                priority=priority,
                sched_policy=sched_policy,
            )
            rta = cls.by_profile(target, name="rta_calib_cpu{}".format(cpu),
                                 profile={'task1': calib_task},
                                 calibration="CPU{}".format(cpu),
                                 res_dir=res_dir)

            with rta, target.freeze_userspace():
                # Disable CPU capacities update, since that leads to infinite
                # recursion
                rta.run(as_root=target.is_rooted, update_cpu_capacities=False)

            for line in rta.output.split('\n'):
                pload_match = re.search(pload_regexp, line)
                if pload_match is None:
                    continue
                pload[cpu] = int(pload_match.group(1))
                logger.debug('>>> CPU{}: {}'.format(cpu, pload[cpu]))

        # Avoid circular import issue
        from lisa.platforms.platinfo import PlatformInfo
        snippet_plat_info = PlatformInfo({
            'rtapp': {
                'calib': pload,
            },
        })
        logger.info('Platform info rt-app calibration configuration:\n{}'.format(
            snippet_plat_info.to_yaml_map_str()
        ))

        plat_info = target.plat_info

        # Sanity check calibration values for asymmetric systems if we have
        # access to capacities
        try:
            cpu_capacities = plat_info['cpu-capacities']
        except KeyError:
            return pload

        capa_ploads = {
            capacity: {cpu: pload[cpu] for cpu, capa in cpu_caps}
            for capacity, cpu_caps in groupby(cpu_capacities.items(), itemgetter(1))
        }

        # Find the min pload per capacity level, i.e. the fastest detected CPU.
        # It is more likely to represent the right pload, as it has suffered
        # from less IRQ slowdown or similar disturbances that might be random.
        capa_pload = {
            capacity: min(ploads.values())
            for capacity, ploads in capa_ploads.items()
        }

        # Sort by capacity
        capa_pload_list = sorted(capa_pload.items())
        # unzip the list of tuples
        _, pload_list = zip(*capa_pload_list)

        # If sorting according to capa was not equivalent to reverse sorting
        # according to pload (small pload=fast cpu)
        if list(pload_list) != sorted(pload_list, reverse=True):
            raise CalibrationError('Calibration values reports big cores less capable than LITTLE cores')

        # Check that the CPU capacities seen by rt-app are similar to the one
        # the kernel uses
        true_capacities = cls.get_cpu_capacities_from_calibrations(pload)
        capa_factors_pct = {
            cpu: true_capacities[cpu] / cpu_capacities[cpu] * 100
            for cpu in cpu_capacities.keys()
        }
        dispersion_pct = max(abs(100 - factor) for factor in capa_factors_pct.values())

        logger.info('CPU capacities according to rt-app workload: {}'.format(true_capacities))

        if dispersion_pct > 2:
            logger.warning('The calibration values are not inversely proportional to the CPU capacities, the duty cycles will be up to {:.2f}% off on some CPUs: {}'.format(dispersion_pct, capa_factors_pct))

        if dispersion_pct > 20:
            logger.warning('The calibration values are not inversely proportional to the CPU capacities. Either rt-app calibration failed, or the rt-app busy loops has a very different instruction mix compared to the workload used to establish the CPU capacities: {}'.format(capa_factors_pct))

        # Map of CPUs X to list of CPUs Ys that are faster than it although CPUs
        # of Ys have a smaller capacity than X
        if len(capa_ploads) > 1:
            faster_than_map = {
                cpu1: sorted(
                    cpu2
                    for cpu2, pload2 in ploads2.items()
                    # CPU2 faster than CPU1
                    if pload2 < pload1
                )
                for (capa1, ploads1), (capa2, ploads2) in itertools.permutations(capa_ploads.items())
                for cpu1, pload1 in ploads1.items()
                # Only look at permutations in which CPUs of ploads1 are supposed
                # to be faster than the one in ploads2
                if capa1 > capa2
            }
        else:
            faster_than_map = {}

        # Remove empty lists
        faster_than_map = {
            cpu: faster_cpus
            for cpu, faster_cpus in faster_than_map.items()
            if faster_cpus
        }

        if faster_than_map:
            raise CalibrationError('Some CPUs of higher capacities are slower than other CPUs of smaller capacities: {}'.format(faster_than_map))

        return pload
Пример #9
0
def main(argv=None):
    if argv is None:
        argv = sys.argv[1:]

    plots_map = get_plots_map()
    analysis_nice_name_map = {
        get_analysis_nice_name(name): name
        for name in plots_map.keys()
    }

    parser = argparse.ArgumentParser(
        description="""
CLI for LISA analysis plots and reports from traces.

Available plots:

{}

""".format(get_analysis_listing(plots_map)),
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )

    parser.add_argument(
        'trace',
        help='trace-cmd trace.dat, or systrace file',
    )

    parser.add_argument(
        '--normalize-time',
        action='store_true',
        help=
        'Normalize the time in the plot, i.e. start at 0 instead of uptime timestamp',
    )

    parser.add_argument(
        '--plot',
        nargs=2,
        action='append',
        default=[],
        metavar=('PLOT', 'OUTPUT_PATH'),
        help=
        'Create the given plot. If OUTPUT_PATH is "interactive", an interactive window will be used',
    )

    parser.add_argument(
        '--plot-analysis',
        nargs=3,
        action='append',
        default=[],
        metavar=('ANALYSIS', 'OUTPUT_FOLDER_PATH', 'FORMAT'),
        help='Create all the plots of the given analysis',
    )

    parser.add_argument(
        '--plot-all',
        nargs=2,
        metavar=('OUTPUT_FOLDER_PATH', 'FORMAT'),
        help='Create all the plots in the given folder',
    )

    parser.add_argument(
        '--best-effort',
        action='store_true',
        help=
        'Try to generate as many of the requested plots as possible without early termination.',
    )

    parser.add_argument(
        '--window',
        nargs=2,
        type=float,
        metavar=('BEGIN', 'END'),
        help='Only plot data between BEGIN and END times',
    )

    parser.add_argument(
        '-X',
        '--option',
        nargs=2,
        action='append',
        default=[],
        metavar=('OPTION_NAME', 'VALUE'),
        help=
        'Pass extra parameters to plot methods, e.g. "-X cpu 1". Mismatching names are ignored.',
    )

    parser.add_argument(
        '--matplotlib-backend',
        default='GTK3Agg',
        help='matplotlib backend to use for interactive window',
    )

    parser.add_argument(
        '--plat-info',
        help='Platform information, necessary for some plots',
    )

    parser.add_argument(
        '--xkcd',
        action='store_true',
        help='Graphs will look like XKCD plots',
    )

    args = parser.parse_args(argv)

    flat_plot_map = {
        plot_name: meth
        for analysis_name, plot_list in plots_map.items()
        for plot_name, meth in plot_list.items()
    }

    if args.plat_info:
        plat_info = PlatformInfo.from_yaml_map(args.plat_info)
    else:
        plat_info = None

    if args.plot_all:
        folder, fmt = args.plot_all
        plot_analysis_spec_list = [(get_analysis_nice_name(analysis_name),
                                    folder, fmt)
                                   for analysis_name in plots_map.keys()]
    else:
        plot_analysis_spec_list = []

    plot_analysis_spec_list.extend(args.plot_analysis)

    plot_spec_list = [(plot_name,
                       os.path.join(folder, '{}.{}'.format(plot_name, fmt)))
                      for analysis_name, folder, fmt in plot_analysis_spec_list
                      for plot_name, meth in plots_map[
                          analysis_nice_name_map[analysis_name]].items()]

    plot_spec_list.extend(args.plot)

    # Build minimal event list to speed up trace loading time
    plot_methods = set()
    for plot_name, file_path in plot_spec_list:
        try:
            f = flat_plot_map[plot_name]
        except KeyError:
            error('Unknown plot "{}", see --help'.format(plot_name))

        plot_methods.add(f)

    # If best effort is used, we don't want to trigger exceptions ahead of
    # time. Let it fail for individual plot methods instead, so the trace can
    # be used for the other events
    if args.best_effort:
        events = None
    else:
        events = set()
        for f in plot_methods:
            with contextlib.suppress(AttributeError):
                events.update(f.used_events.get_all_events())

        events = sorted(events)
        print('Parsing trace events: {}'.format(', '.join(events)))

    trace = Trace(args.trace,
                  plat_info=plat_info,
                  events=events,
                  normalize_time=args.normalize_time,
                  write_swap=True)
    if args.window:
        window = args.window

        def clip(l, x, r):
            if x < l:
                return l
            elif x > r:
                return r
            else:
                return x

        window = (
            clip(trace.window[0], window[0], trace.window[1]),
            clip(trace.window[0], window[1], trace.window[1]),
        )
        # There is no overlap between trace and user window, reset to trace
        # window
        if window[0] == window[1]:
            print(
                'Window {} does not overlap with trace time range, maybe you forgot --normalize-time ?'
                .format(tuple(args.window)))
            window = trace.window

        trace = trace.get_view(window)

    for plot_name, file_path in sorted(plot_spec_list):
        interactive = file_path == 'interactive'
        f = flat_plot_map[plot_name]
        if interactive:
            matplotlib.use(args.matplotlib_backend)
            file_path = None
        else:
            dirname = os.path.dirname(file_path)
            if dirname:
                os.makedirs(dirname, exist_ok=True)

        kwargs = make_plot_kwargs(f,
                                  file_path,
                                  interactive=interactive,
                                  extra_options=args.option)

        xkcd_cm = plt.xkcd() if args.xkcd else nullcontext()
        with handle_plot_excep(exit_on_error=not args.best_effort):
            with xkcd_cm:
                TraceAnalysisBase.call_on_trace(f, trace, kwargs)

        if interactive:
            plt.show()
Пример #10
0
    def from_custom_cli(cls, argv=None, params=None) -> 'Target':
        """
        Create a Target from command line arguments.

        :param argv: The list of arguments. ``sys.argv[1:]`` will be used if
          this is ``None``.
        :type argv: list(str)

        :param params: Dictionary of custom parameters to add to the parser. It
            is in the form of
            ``{param_name: {dict of ArgumentParser.add_argument() options}}``.
        :type params: dict(str, dict)

        :return: A tuple ``(args, target)``

        .. note:: This method should not be relied upon to implement long-term
            scripts, it's more designed for quick scripting.
        """
        parser = argparse.ArgumentParser(
            formatter_class=argparse.RawDescriptionHelpFormatter,
            description=textwrap.dedent(
                """
                Connect to a target using the provided configuration in order
                to run a test.

                EXAMPLES

                --conf can point to a YAML target configuration file
                with all the necessary connection information:
                $ {script} --conf my_target.yml

                Alternatively, --kind must be set along the relevant credentials:
                $ {script} --kind linux --host 192.0.2.1 --username root --password root

                In both cases, --conf can also contain a PlatformInfo YAML description.

                """.format(
                    script=os.path.basename(sys.argv[0])
                )))

        parser.add_argument("--conf", '-c',
            help="Path to a TargetConf and PlatformInfo yaml file. Other options will override what is specified in the file."
        )

        parser.add_argument("--kind", "-k",
            choices=["android", "linux", "host"],
            help="The kind of target to connect to.")

        device_group = parser.add_mutually_exclusive_group()
        device_group.add_argument("--device", "-d",
                            help="The ADB ID of the target. Superseeds --host. Only applies to Android kind.")
        device_group.add_argument("--host", "-n",
                            help="The hostname/IP of the target.")

        parser.add_argument("--username", "-u",
                            help="Login username. Only applies to Linux kind.")
        parser.add_argument("--password", "-p",
                            help="Login password. Only applies to Linux kind.")

        parser.add_argument("--log-level",
                            default='info',
                            choices=('warning', 'info', 'debug'),
                            help="Verbosity level of the logs.")

        parser.add_argument("--res-dir", "-o",
                            help="Result directory of the created Target. If no directory is specified, a default location under $LISA_HOME will be used.")

        params = params or {}
        for param, settings in params.items():
            parser.add_argument('--{}'.format(param), **settings)
        custom_params = {k.replace('-', '_') for k in params.keys()}

        # Options that are not a key in TargetConf must be listed here
        not_target_conf_opt = {
            'platform_info', 'log_level', 'res_dir', 'conf',
        }
        not_target_conf_opt.update(custom_params)

        args = parser.parse_args(argv)
        setup_logging(level=args.log_level.upper())

        target_conf = TargetConf()
        platform_info = None

        if args.conf:
            # Tentatively load a PlatformInfo from the conf file
            with contextlib.suppress(KeyError):
                platform_info = PlatformInfo.from_yaml_map(args.conf)

            # Load the TargetConf from the file, and update it with command
            # line arguments
            try:
                conf = TargetConf.from_yaml_map(args.conf)
            except KeyError:
                pass
            else:
                target_conf.add_src(args.conf, conf)

        target_conf.add_src('command-line', {
            k: v for k, v in vars(args).items()
            if v is not None and k not in not_target_conf_opt
        })

        # Some sanity check to get better error messages
        if not target_conf:
            parser.error('--conf with target configuration or any of the connection options is required')

        if args.kind == 'android':
            if ('host' not in target_conf) and ('device' not in target_conf):
                parser.error('--host or --device must be specified')

        if args.kind == 'linux':
            for required in ['host', 'username', 'password']:
                if required not in target_conf:
                    parser.error('--{} must be specified'.format(required))

        custom_args = {
            param: value
            for param, value in vars(args).items()
            if param in custom_params
        }
        custom_args = argparse.Namespace(**custom_args)

        return custom_args, cls.from_conf(conf=target_conf, plat_info=platform_info, res_dir=args.res_dir)
Пример #11
0
    def __init__(self, kind, name='<noname>', tools=[], res_dir=None,
        plat_info=None, workdir=None, device=None, host=None, port=None,
        username='******', password=None, keyfile=None, devlib_platform=None,
        devlib_excluded_modules=[], wait_boot=True, wait_boot_timeout=10,
    ):

        super().__init__()
        logger = self.get_logger()

        self.name = name

        res_dir = res_dir if res_dir else self._get_res_dir(
            root=os.path.join(LISA_HOME, RESULT_DIR),
            relative='',
            name='{}-{}'.format(self.__class__.__qualname__, self.name),
            append_time=True,
            symlink=True
        )

        self._res_dir = res_dir
        os.makedirs(self._res_dir, exist_ok=True)
        if os.listdir(self._res_dir):
            raise ValueError('res_dir must be empty: {}'.format(self._res_dir))

        if plat_info is None:
            plat_info = PlatformInfo()
        else:
            # Make a copy of the PlatformInfo so we don't modify the original
            # one we were passed when adding the target source to it
            plat_info = copy.copy(plat_info)
            logger.info('User-defined platform information:\n%s', plat_info)

        self.plat_info = plat_info

        # Take the board name from the target configuration so it becomes
        # available for later inspection. That board name is mostly free form
        # and no specific value should be expected for a given kind of board
        # (i.e. a Juno board might be named "foo-bar-juno-on-my-desk")
        if name:
            self.plat_info.add_src('target-conf', dict(name=name))


        self._installed_tools = set()
        self.target = self._init_target(
                kind=kind,
                name=name,
                workdir=workdir,
                device=device,
                host=host,
                port=port,
                username=username,
                password=password,
                keyfile=keyfile,
                devlib_platform=devlib_platform,
                devlib_excluded_modules=devlib_excluded_modules,
                wait_boot=wait_boot,
                wait_boot_timeout=wait_boot_timeout,
            )

        # Initialize binary tools to deploy
        if tools:
            logger.info('Tools to install: %s', tools)
            self.install_tools(tools)

        # Autodetect information from the target, after the Target is
        # initialized. Expensive computations are deferred so they will only be
        # computed when actually needed.

        rta_calib_res_dir = os.path.join(self._res_dir, 'rta_calib')
        os.makedirs(rta_calib_res_dir)
        self.plat_info.add_target_src(self, rta_calib_res_dir, fallback=True)

        logger.info('Effective platform information:\n{}'.format(self.plat_info))
Пример #12
0
from lisa.target import Target, TargetConf
from lisa.platforms.platinfo import PlatformInfo

ASSET_DIR = os.path.join(os.path.dirname(__file__), 'assets')

HOST_TARGET_CONF = TargetConf({
    'kind': 'host',
    # Don't load cpufreq, it usually won't work with CI targets
    'devlib': {
        'excluded-modules': ['cpufreq', 'hwmon'],
    },
})

HOST_PLAT_INFO = PlatformInfo({
    # With no cpufreq, we won't be able to do calibration. Provide dummy.
    'rtapp': {
        'calib': {c: 100 for c in list(range(64))},
    },
})


def create_local_target():
    """
    :returns: A localhost :class:`lisa.target.Target` instance
    """
    return Target.from_conf(conf=HOST_TARGET_CONF, plat_info=HOST_PLAT_INFO)


class StorageTestCase(TestCase):
    """
    A base class for tests that also provides a directory
    """
Пример #13
0
    def __init__(
        self,
        kind,
        name='<noname>',
        tools=[],
        res_dir=None,
        plat_info=None,
        lazy_platinfo=False,
        workdir=None,
        device=None,
        host=None,
        port=None,
        username=None,
        password=None,
        keyfile=None,
        strict_host_check=None,
        devlib_platform=None,
        devlib_excluded_modules=[],
        wait_boot=True,
        wait_boot_timeout=10,
    ):
        # pylint: disable=dangerous-default-value
        super().__init__()
        logger = self.get_logger()

        self.name = name

        res_dir = res_dir if res_dir else self._get_res_dir(
            root=os.path.join(LISA_HOME, RESULT_DIR),
            relative='',
            name=f'{self.__class__.__qualname__}-{self.name}',
            append_time=True,
            symlink=True)

        self._res_dir = res_dir
        os.makedirs(self._res_dir, exist_ok=True)
        if os.listdir(self._res_dir):
            raise ValueError(f'res_dir must be empty: {self._res_dir}')

        if plat_info is None:
            plat_info = PlatformInfo()
        else:
            # Make a copy of the PlatformInfo so we don't modify the original
            # one we were passed when adding the target source to it
            plat_info = copy.copy(plat_info)
            logger.info(f'User-defined platform information:\n{plat_info}')

        self.plat_info = plat_info

        # Take the board name from the target configuration so it becomes
        # available for later inspection. That board name is mostly free form
        # and no specific value should be expected for a given kind of board
        # (i.e. a Juno board might be named "foo-bar-juno-on-my-desk")
        if name:
            self.plat_info.add_src('target-conf', dict(name=name))

        self._installed_tools = set()
        self.target = self._init_target(
            kind=kind,
            name=name,
            workdir=workdir,
            device=device,
            host=host,
            port=port,
            username=username,
            password=password,
            keyfile=keyfile,
            strict_host_check=strict_host_check,
            devlib_platform=devlib_platform,
            wait_boot=wait_boot,
            wait_boot_timeout=wait_boot_timeout,
        )

        devlib_excluded_modules = set(devlib_excluded_modules)
        # Sorry, can't let you do that. Messing with cgroups in a systemd
        # system is pretty bad idea.
        if self._uses_systemd:
            logger.warning(
                'Will not load cgroups devlib module: target is using systemd, which already uses cgroups'
            )
            devlib_excluded_modules.add('cgroups')

        self._devlib_loadable_modules = _DEVLIB_AVAILABLE_MODULES - devlib_excluded_modules

        # Initialize binary tools to deploy
        if tools:
            logger.info(f'Tools to install: {tools}')
            self.install_tools(tools)

        # Autodetect information from the target, after the Target is
        # initialized. Expensive computations are deferred so they will only be
        # computed when actually needed.

        rta_calib_res_dir = ArtifactPath.join(self._res_dir, 'rta_calib')
        os.makedirs(rta_calib_res_dir)
        self.plat_info.add_target_src(self,
                                      rta_calib_res_dir,
                                      deferred=lazy_platinfo,
                                      fallback=True)

        logger.info(f'Effective platform information:\n{self.plat_info}')
Пример #14
0
    def from_cli(cls, argv=None) -> 'Target':
        """
        Create a Target from command line arguments.

        :param argv: The list of arguments. ``sys.argv[1:]`` will be used if
          this is ``None``.
        :type argv: list(str)

        Trying to use this in a script that expects extra arguments is bound
        to be confusing (help message woes, argument clashes...), so for now
        this should only be used in scripts that only expect Target args.
        """
        parser = argparse.ArgumentParser(
            formatter_class=argparse.RawDescriptionHelpFormatter,
            description=textwrap.dedent("""
                Connect to a target using the provided configuration in order
                to run a test.

                EXAMPLES

                --target-conf can point to a YAML target configuration file
                with all the necessary connection information:
                $ {script} --target-conf my_target.yml

                Alternatively, --kind must be set along the relevant credentials:
                $ {script} --kind linux --host 192.0.2.1 --username root --password root

                In both cases, --platform-info can point to a PlatformInfo YAML
                file.

                """.format(script=os.path.basename(sys.argv[0]))))

        kind_group = parser.add_mutually_exclusive_group(required=True)
        kind_group.add_argument("--kind",
                                "-k",
                                choices=["android", "linux", "host"],
                                help="The kind of target to connect to.")

        kind_group.add_argument(
            "--target-conf",
            "-t",
            help=
            "Path to a TargetConf yaml file. Superseeds other target connection related options."
        )

        device_group = parser.add_mutually_exclusive_group()
        device_group.add_argument(
            "--device",
            "-d",
            help=
            "The ADB ID of the target. Superseeds --host. Only applies to Android kind."
        )
        device_group.add_argument("--host",
                                  "-n",
                                  help="The hostname/IP of the target.")

        parser.add_argument("--username",
                            "-u",
                            help="Login username. Only applies to Linux kind.")
        parser.add_argument("--password",
                            "-p",
                            help="Login password. Only applies to Linux kind.")

        parser.add_argument("--platform-info",
                            "-pi",
                            help="Path to a PlatformInfo yaml file.")

        parser.add_argument("--log-level",
                            default='info',
                            choices=('warning', 'info', 'debug'),
                            help="Verbosity level of the logs.")

        parser.add_argument(
            "--res-dir",
            "-o",
            help=
            "Result directory of the created Target. If no directory is specified, a default location under $LISA_HOME will be used."
        )

        # Options that are not a key in TargetConf must be listed here
        not_target_conf_opt = (
            'platform_info',
            'log_level',
            'res_dir',
            'target_conf',
        )

        args = parser.parse_args(argv)
        setup_logging(level=args.log_level.upper())

        if args.kind == 'android':
            if not (args.host or args.device):
                parser.error('--host or --device must be specified')
        if args.kind == 'linux':
            for required in ['host', 'username', 'password']:
                if getattr(args, required) is None:
                    parser.error('--{} must be specified'.format(required))

        platform_info = PlatformInfo.from_yaml_map(
            args.platform_info) if args.platform_info else None
        if args.target_conf:
            target_conf = TargetConf.from_yaml_map(args.target_conf)
        else:
            target_conf = TargetConf({
                k: v
                for k, v in vars(args).items()
                if v is not None and k not in not_target_conf_opt
            })

        return cls.from_conf(conf=target_conf,
                             plat_info=platform_info,
                             res_dir=args.res_dir)