Пример #1
0
    def _from_target(cls,
                     target: Target,
                     *,
                     res_dir: ArtifactPath,
                     cpu,
                     freq,
                     switch_governor=True) -> 'UserspaceSanityItem':
        """
        Create a :class:`UserspaceSanityItem` from a live :class:`lisa.target.Target`.

        :param cpu: CPU to run on.
        :type cpu: int

        :param freq: Frequency to run at.
        :type freq: int

        :param switch_governor: Switch the governor to userspace, and undo it at the end.
            If that has been done in advance, not doing it for every item saves substantial time.
        :type switch_governor: bool
        """

        sysbench = Sysbench(target, res_dir=res_dir)

        cm = target.cpufreq.use_governor(
            'userspace') if switch_governor else nullcontext()
        with cm:
            target.cpufreq.set_frequency(cpu, freq)
            sysbench.run(cpus=[cpu], max_duration_s=1)

        work = sysbench.output.nr_events
        return cls(res_dir, target.plat_info, cpu, freq, work)
Пример #2
0
    def run(self,
            cpus=None,
            cgroup=None,
            as_root=False,
            update_cpu_capacities=None):
        logger = self.get_logger()
        plat_info = self.target.plat_info
        writeable_capacities = plat_info['cpu-capacities']['writeable']

        if update_cpu_capacities:
            if not writeable_capacities:
                raise ValueError(
                    'CPU capacities are not writeable on this target, please use update_cpu_capacities=False or None'
                )
        # If left to None, we update if possible
        elif update_cpu_capacities is None:
            update_cpu_capacities = writeable_capacities
            if not writeable_capacities:
                logger.warning(
                    'CPU capacities will not be updated on this platform')

        if update_cpu_capacities:
            rtapp_capacities = plat_info['cpu-capacities']['rtapp']
            logger.info(
                f'Will update CPU capacities in sysfs: {rtapp_capacities}')

            write_kwargs = [
                dict(
                    path=f'/sys/devices/system/cpu/cpu{cpu}/cpu_capacity',
                    value=capa,
                    verify=True,
                ) for cpu, capa in sorted(rtapp_capacities.items())
            ]
            capa_cm = self.target.batch_revertable_write_value(write_kwargs)
        else:
            # There might not be any rtapp calibration available, specifically
            # when we are being called to run the calibration workload.
            try:
                rtapp_capacities = plat_info['cpu-capacities']['rtapp']
                orig_capacities = plat_info['cpu-capacities']['orig']
            except KeyError:
                pass
            else:
                # Spit out some warning in case we are not going to update the
                # capacities, so we know what to expect
                RTA.warn_capacities_mismatch(orig_capacities, rtapp_capacities)

            capa_cm = nullcontext()

        with capa_cm:
            super().run(cpus, cgroup, as_root)

        if self.log_stats:
            logger.debug(f'Pulling logfiles to: {self.res_dir}')
            for task in self.tasks:
                # RT-app appends some number to the logs, so we can't predict the
                # exact filename
                logfile = self.target.path.join(self.run_dir, f'*{task}*.log')
                self.target.pull(logfile, self.res_dir, globbing=True)
Пример #3
0
    def test_check_events(self):
        if self.expected_success:
            cm = nullcontext()
        else:
            cm = self.assertRaises(MissingTraceEventError)

        with cm:
            print('Checking: {}'.format(self.checker))
            self.checker.check_events(self.EVENTS_SET)
Пример #4
0
    def get_cpu_calibrations(cls, target, res_dir=None):
        """
        Get the rt-ap calibration value for all CPUs.

        :param target: Devlib target to run calibration on.
        :returns: Dict mapping CPU numbers to rt-app calibration values.
        """

        if not target.is_module_available('cpufreq'):
            cls.get_logger().warning(
                'cpufreq module not loaded, skipping setting frequency to max')
            cm = nullcontext()
        else:
            cm = target.cpufreq.use_governor('performance')

        with cm, target.disable_idle_states():
            return cls._calibrate(target, res_dir)
Пример #5
0
            def wrapper(self,
                        *args,
                        filepath=None,
                        axis=None,
                        output=None,
                        img_format=None,
                        always_save=False,
                        colors: TypedList[str] = None,
                        linestyles: TypedList[str] = None,
                        markers: TypedList[str] = None,
                        rc_params=None,
                        **kwargs):
                def is_f_param(param):
                    """
                    Return True if the parameter is for `f`, False if it is
                    for setup_plot()
                    """
                    try:
                        desc = inspect.signature(f).parameters[param]
                    except KeyError:
                        return False
                    else:
                        # Passing kwargs=42 to a function taking **kwargs
                        # should not return True here, as we only consider
                        # explicitly listed arguments
                        return desc.kind not in (
                            inspect.Parameter.VAR_KEYWORD,
                            inspect.Parameter.VAR_POSITIONAL,
                        )

                # Factor the *args inside the **kwargs by binding them to the
                # user-facing signature, which is the one of the wrapper.
                kwargs.update(
                    inspect.signature(wrapper).bind_partial(self,
                                                            *args).arguments)

                f_kwargs = {
                    param: val
                    for param, val in kwargs.items() if is_f_param(param)
                }

                img_format = img_format or guess_format(filepath) or 'png'
                local_fig = axis is None

                # When we create the figure ourselves, always save the plot to
                # the default location
                if local_fig and filepath is None and always_save:
                    filepath = self.get_default_plot_path(
                        img_format=img_format,
                        plot_name=f.__name__,
                    )

                cyclers = dict(
                    color=colors,
                    linestyle=linestyles,
                    marker=markers,
                )
                cyclers = {
                    name: value
                    for name, value in cyclers.items() if value
                }
                if cyclers:
                    cyclers = [
                        make_cycler(**{name: value})
                        for name, value in cyclers.items()
                    ]
                    set_cycler = lambda axis: cls.set_axis_cycler(
                        axis, *cyclers)
                else:
                    set_cycler = lambda axis: nullcontext()

                if rc_params:
                    set_rc_params = lambda axis: cls.set_axis_rc_params(
                        axis, rc_params)
                else:
                    set_rc_params = lambda axis: nullcontext()

                # Allow returning an axis directly, or just update a given axis
                if return_axis:
                    # In that case, the function takes all the kwargs
                    with set_cycler(axis), set_rc_params(axis):
                        axis = f(**kwargs, axis=axis)
                else:
                    if local_fig:
                        setup_plot_kwargs = {
                            param: val
                            for param, val in kwargs.items()
                            if param not in f_kwargs
                        }
                        fig, axis = self.setup_plot(**setup_plot_kwargs)

                    f_kwargs.update(
                        axis=axis,
                        local_fig=f_kwargs.get('local_fig', local_fig),
                    )
                    with set_cycler(axis), set_rc_params(axis):
                        f(**f_kwargs)

                if isinstance(axis, numpy.ndarray):
                    fig = axis[0].get_figure()
                else:
                    fig = axis.get_figure()

                def resolve_formatter(fmt):
                    format_map = {
                        'rst': cls._get_rst_content,
                        'html': cls._get_html,
                    }
                    try:
                        return format_map[fmt]
                    except KeyError:
                        raise ValueError(f'Unsupported format: {fmt}')

                if output is None:
                    out = axis

                    # Show the LISA figure toolbar
                    if is_running_ipython():
                        # Make sure we only add one button per figure
                        try:
                            toolbar = self._get_fig_data(fig, 'toolbar')
                        except KeyError:
                            toolbar = self._make_fig_toolbar(fig)
                            self._set_fig_data(fig, 'toolbar', toolbar)
                            display(toolbar)

                        mplcursors.cursor(fig)
                else:
                    out = resolve_formatter(output)(f, [], f_kwargs, axis)

                if filepath:
                    if img_format in ('html', 'rst'):
                        content = resolve_formatter(img_format)(f, [],
                                                                f_kwargs, axis)

                        with open(filepath, 'wt', encoding='utf-8') as fd:
                            fd.write(content)
                    else:
                        fig.savefig(filepath,
                                    format=img_format,
                                    bbox_inches='tight')

                return out
Пример #6
0
    def run_rtapp(cls,
                  target,
                  res_dir,
                  profile=None,
                  ftrace_coll=None,
                  cg_cfg=None,
                  wipe_run_dir=True):
        """
        Run the given RTA profile on the target, and collect an ftrace trace.

        :param target: target to execute the workload on.
        :type target: lisa.target.Target

        :param res_dir: Artifact folder where the artifacts will be stored.
        :type res_dir: str or lisa.utils.ArtifactPath

        :param profile: ``rt-app`` profile, as a dictionary of
            ``dict(task_name, RTATask)``. If ``None``,
            :meth:`~lisa.tests.base.RTATestBundle.get_rtapp_profile` is called
            with ``target.plat_info``.
        :type profile: dict(str, lisa.wlgen.rta.RTATask)

        :param ftrace_coll: Ftrace collector to use to record the trace. This
            allows recording extra events compared to the default one, which is
            based on the ``ftrace_conf`` class attribute.
        :type ftrace_coll: lisa.trace.FtraceCollector

        :param cg_cfg: CGroup configuration dictionary. If ``None``,
            :meth:`lisa.tests.base.RTATestBundle.get_cgroup_configuration` is
            called with ``target.plat_info``.
        :type cg_cfg: dict

        :param wipe_run_dir: Remove the run directory on the target after
            execution of the workload.
        :type wipe_run_dir: bool
        """

        trace_path = ArtifactPath.join(res_dir, cls.TRACE_PATH)
        dmesg_path = ArtifactPath.join(res_dir, cls.DMESG_PATH)
        ftrace_coll = ftrace_coll or FtraceCollector.from_conf(
            target, cls.ftrace_conf)
        dmesg_coll = DmesgCollector(target)

        profile = profile or cls.get_rtapp_profile(target.plat_info)
        cg_cfg = cg_cfg or cls.get_cgroup_configuration(target.plat_info)

        trace_events = [
            event.replace('rtapp_', '') for event in ftrace_coll.events
            if event.startswith("rtapp_")
        ]

        wload = RTA.by_profile(target,
                               "rta_{}".format(cls.__name__.lower()),
                               profile,
                               res_dir=res_dir,
                               trace_events=trace_events)
        cgroup = cls._target_configure_cgroup(target, cg_cfg)
        as_root = cgroup is not None
        wload_cm = wload if wipe_run_dir else nullcontext(wload)

        # Pre-hit the calibration information, in case this is a lazy value.
        # This avoids polluting the trace and the dmesg output with the
        # calibration tasks. Since we know that rt-app will always need it for
        # anything useful, it's reasonable to do it here.
        target.plat_info['rtapp']['calib']

        with wload_cm, dmesg_coll, ftrace_coll, target.freeze_userspace():
            wload.run(cgroup=cgroup, as_root=as_root)

        ftrace_coll.get_trace(trace_path)
        dmesg_coll.get_trace(dmesg_path)
        return trace_path
Пример #7
0
    def run(self, cpus=None, cgroup=None, background=False, as_root=False, update_cpu_capacities=None):
        logger = self.get_logger()

        if update_cpu_capacities is None:
            update_cpu_capacities = True
            best_effort = True
        else:
            best_effort = False

        if update_cpu_capacities:
            plat_info = self.target.plat_info
            calib_map = plat_info['rtapp']['calib']
            true_capacities = self.get_cpu_capacities_from_calibrations(calib_map)

            # Average in a capacity class, since the kernel will only use one
            # value for the whole class anyway
            new_capacities = {}
            for capa_class in plat_info['capacity-classes']:
                avg_capa = mean(
                    capa
                    for cpu, capa in true_capacities.items()
                    if cpu in capa_class
                )
                new_capacities.update({cpu: avg_capa for cpu in capa_class})

            # Make sure that the max cap is 1024 and that we use integer values
            new_max_cap = max(new_capacities.values())
            new_capacities = {
                cpu: int(capa * (1024 / new_max_cap))
                for cpu, capa in new_capacities.items()
            }

            write_kwargs = [
                dict(
                    path='/sys/devices/system/cpu/cpu{}/cpu_capacity'.format(cpu),
                    value=capa,
                    verify=True,
                )
                for cpu, capa in sorted(new_capacities.items())
            ]

            cm = self.target.batch_revertable_write_value(write_kwargs)
            class _CM():
                def __enter__(self):
                    logger.info('Updating CPU capacities in sysfs: {}'.format(new_capacities))
                    try:
                        cm.__enter__()
                    except TargetStableError as e:
                        if best_effort:
                            logger.warning('Could not update the CPU capacities: {}'.format(e))
                        else:
                            raise

                def __exit__(self, *args, **kwargs):
                    return cm.__exit__(*args, **kwargs)

            capa_cm = _CM()
        else:
            capa_cm = nullcontext()

        with capa_cm:
            super().run(cpus, cgroup, background, as_root)

        if background:
            # TODO: handle background case
            return

        if not self.log_stats:
            return
        logger.debug('Pulling logfiles to: {}'.format(self.res_dir))
        for task in self.tasks:
            # RT-app appends some number to the logs, so we can't predict the
            # exact filename
            logfile = self.target.path.join(self.run_dir, '*{}*.log'.format(task))
            self.target.pull(logfile, self.res_dir)
Пример #8
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()
Пример #9
0
            def wrapper(self,
                        *args,
                        filepath=None,
                        axis=None,
                        output=None,
                        img_format=None,
                        always_save=False,
                        colors=None,
                        **kwargs):

                # Bind the function to the instance, so we avoid having "self"
                # showing up in the signature, which breaks parameter
                # formatting code.
                f = func.__get__(self, type(self))

                def is_f_param(param):
                    """
                    Return True if the parameter is for `f`, False if it is
                    for setup_plot()
                    """
                    try:
                        desc = inspect.signature(f).parameters[param]
                    except KeyError:
                        return False
                    else:
                        # Passing kwargs=42 to a function taking **kwargs
                        # should not return True here, as we only consider
                        # explicitely listed arguments
                        return desc.kind not in (
                            inspect.Parameter.VAR_KEYWORD,
                            inspect.Parameter.VAR_POSITIONAL,
                        )

                f_kwargs = {
                    param: val
                    for param, val in kwargs.items() if is_f_param(param)
                }

                img_format = img_format or guess_format(filepath) or 'png'
                local_fig = axis is None

                # When we create the figure ourselves, always save the plot to
                # the default location
                if local_fig and filepath is None and always_save:
                    filepath = self.get_default_plot_path(
                        img_format=img_format,
                        plot_name=f.__name__,
                    )

                if colors:
                    cycler = make_cycler(color=colors)
                    set_cycler = lambda axis: cls.set_axis_cycler(axis, cycler)
                else:
                    set_cycler = lambda axis: nullcontext()
                # Allow returning an axis directly, or just update a given axis
                if return_axis:
                    # In that case, the function takes all the kwargs
                    with set_cycler(axis):
                        axis = f(*args, **kwargs, axis=axis)
                else:
                    if local_fig:
                        setup_plot_kwargs = {
                            param: val
                            for param, val in kwargs.items()
                            if param not in f_kwargs
                        }
                        fig, axis = self.setup_plot(**setup_plot_kwargs)

                    with set_cycler(axis):
                        f(*args, axis=axis, local_fig=local_fig, **f_kwargs)

                if isinstance(axis, numpy.ndarray):
                    fig = axis[0].get_figure()
                else:
                    fig = axis.get_figure()

                def resolve_formatter(fmt):
                    format_map = {
                        'rst': cls._get_rst_content,
                        'html': cls._get_html,
                    }
                    try:
                        return format_map[fmt]
                    except KeyError:
                        raise ValueError('Unsupported format: {}'.format(fmt))

                if output is None:
                    out = axis

                    # Show the LISA figure toolbar
                    if is_running_ipython():
                        # Make sure we only add one button per figure
                        try:
                            toolbar = self._get_fig_data(fig, 'toolbar')
                        except KeyError:
                            toolbar = self._make_fig_toolbar(fig)
                            self._set_fig_data(fig, 'toolbar', toolbar)
                            display(toolbar)

                        mplcursors.cursor(fig)
                else:
                    out = resolve_formatter(output)(f, args, f_kwargs, axis)

                if filepath:
                    if img_format in ('html', 'rst'):
                        content = resolve_formatter(img_format)(f, args,
                                                                f_kwargs, axis)

                        with open(filepath, 'wt', encoding='utf-8') as fd:
                            fd.write(content)
                    else:
                        fig.savefig(filepath,
                                    format=img_format,
                                    bbox_inches='tight')

                return out