Beispiel #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)
Beispiel #2
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)
Beispiel #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.

        .. 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)
Beispiel #4
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()
Beispiel #5
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)
Beispiel #6
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)