def _plot_test_boost(self, df): task, = self.rtapp_tasks ana = self.trace.ana( task=task, ) fig = hv.Layout( [ ( plot_signal(df['cost_margin']).options( 'Curve', color='red' ) * plot_signal(df['boost_points'].astype(int)).options( 'Curve', color='black' ) * plot_signal(df['expected_cost_margin']).options( 'Curve', color='blue' ) * plot_signal(df['base_cost']).options( 'Curve', color='orange' ) * plot_signal(df['allowed_cost']).options( 'Curve', color='green' ) * ana.tasks.plot_tasks_activation(overlay=True) ).options( title='Ramp boost for 5% => 75% util step', ylabel='Cost (% of max cost)', ), ana.frequency.plot_cpu_frequencies(cpu=self.cpu, average=False), ( ana.load_tracking.plot_task_signals( signals=['util', 'enqueued'], colors=['orange', 'red'] ) * ana.tasks.plot_tasks_activation(overlay=True) ), ] ).cols(1) self._save_debug_plot(fig, name=f'ramp_boost') return fig
def _plot_signal(signal): df = self.df_task_signal(task, signal) df = df_refit_index(df, window=window) return plot_signal( df[signal], name=signal, ).options(dict(Curve=dict(alpha=0.5)), )
def plot_cpu(cpu): name = f'CPU{cpu} util' series = util_df[cpu].copy(deep=False) series.index.name = 'Time' series.name = name fig = plot_signal(series).options( 'Curve', ylabel='Utilization', ) # The "y" dimension has the name of the series that we plotted fig = fig.redim.range(**{name: (-10, 1034)}) times, utils = zip(*series.items()) fig *= hv.Overlay([ hv.VSpan(start, end).options( alpha=0.1, color='grey', ) for util, start, end in zip( utils, times, times[1:], ) if not util ]) return fig
def plot_capacity(cpu): try: df = self.df_cpus_signal('capacity', cpus=[cpu]) except MissingTraceEventError: return _hv_neutral() else: if df.empty: return _hv_neutral() else: return plot_signal(series_refit_index(df['capacity'], window=window), name='capacity')
def plot_dev_freq_cooling_states(self, device: str): """ Plot the state evolution of a devfreq cooling device :param device: The devfreq devices to consider :type device: str """ df = self.df_devfreq_cooling_state([device]) df = df_refit_index(df, window=self.trace.window) return plot_signal( df['cdev_state'], name=f'Device "{device}"', ).options( title='devfreq cooling devices status' )
def plot_latency(self, task: TaskID): """ Plot the Latency/Slack and Performance data for the specified task. :param task: the rt-app task to filter for :type task: int or str or lisa.trace.TaskID .. seealso:: :meth:`plot_perf` for metrics definition. """ task = self.trace.get_task_id(task) return hv.Overlay([ plot_signal( self.df_rtapp_stats(task)[col], name=f'{task} {col} (us)', ) for col in ('slack', 'wu_lat') ]).options(title=f'Task {task} (start) Latency and (completion) Slack')
def _plot_pelt(self, task, signal_name, simulated, test_name): ana = self.trace.ana( backend='bokeh', task=task, tasks=[task], ) fig = ( ana.load_tracking.plot_task_signals(signals=[signal_name]) * plot_signal(simulated, name=f'simulated {signal_name}') * ana.tasks.plot_tasks_activation( alpha=0.2, overlay=True, which_cpu=False, # TODO: reeanble that when we get working twinx # duration=True, )) self._save_debug_plot(fig, name=f'{test_name}_{signal_name}') return fig
def _plot_phases(self, test, failures, signal=None): task, = self.rtapp_task_ids ana = self.trace.ana( task=task, tasks=[task], ) figs = [ (ana.tasks.plot_tasks_activation(overlay=True, which_cpu=True) * ana.rta.plot_phases(wlgen_profile=self.rtapp_profile) * hv.Overlay([ hv.HLine(failure).options(alpha=0.5, color='red') for failure in failures ])), ] if signal is not None: figs.append(plot_signal(signal)) fig = hv.Layout(figs).cols(1) self._save_debug_plot(fig, name=f'utilclamp_{test}') return fig
def plot_perf(self, task: TaskID): r""" Plot the performance index. :param task: the rt-app task to filter for :type task: int or str or lisa.trace.TaskID The perf index is defined as: .. math:: perf_index = \frac{slack}{c_period - c_run} where - ``c_period``: is the configured period for an activation - ``c_run``: is the configured run time for an activation, assuming to run at the maximum frequency and on the maximum capacity CPU. - ``slack``: is the measured slack for an activation The slack is defined as the different among the activation deadline and the actual completion time of the activation. The deadline defines also the start of the next activation, thus in normal conditions a task activation is always required to complete before its deadline. The slack is thus a positive value if a task complete before its deadline. It's zero when a task complete an activation right at its eadline. It's negative when the completion is over the deadline. Thus, a performance index in [0..1] range represents activations completed within their deadlines. While, the more the performance index is negative the more the task is late with respect to its deadline. """ task = self.trace.get_task_id(task) return plot_signal( self.df_rtapp_stats(task)['perf_index'], name=f'{task} performance index', )
def plot_event_field(self, event: str, field: str, filter_columns=None, filter_f=None): """ Plot a signal represented by the filtered values of a field of an event. :param event: FTrace event name of interest. :type event: str :param field: Name of the field of ``event``. :type field: str :param filter_columns: Pre-filter the dataframe using :func:`lisa.datautils.df_filter`. Also, a signal will be inferred from the column names being used and will be passed to :meth:`lisa.trace.Trace.df_event`. :type filter_columns: dict or None :param filter_f: Function used to filter the dataframe of the event. The function must take a dataframe as only parameter and return a filtered dataframe. It is applied after ``filter_columns`` filter. :type filter_f: collections.abc.Callable """ trace = self.trace if filter_columns: signals = [SignalDesc(event, sorted(filter_columns.keys()))] else: signals = None df = trace.df_event(event, signals=signals) if filter_columns: df = df_filter(df, filter_columns) if filter_f: df = filter_f(df) df = df_refit_index(df, window=trace.window) return plot_signal(df[field], name=field)
def plot_cpu_cooling_states(self, cpu: CPU): """ Plot the state evolution of a cpufreq cooling device :param cpu: The CPU. Whole clusters can be controlled as a single cooling device, they will be plotted as long this CPU belongs to the cluster. :type cpu: int """ window = self.trace.window df = self.df_cpufreq_cooling_state([cpu]) df = df_refit_index(df, window=window) series = series_refit_index(df['cdev_state'], window=window) cdev_name = f"CPUs {mask_to_list(df.cpus.unique()[0])}" return plot_signal( series, name=cdev_name, ).options( title='cpufreq cooling devices status' )
def _plot_tasks_X(self, event, name, target_cpus, window, per_sec): df = self.trace.df_event(event) if target_cpus: df = df[df['target_cpu'].isin(target_cpus)] series = series_rolling_apply( df["target_cpu"], lambda x: x.count() / (window if per_sec else 1), window, window_float_index=False, center=True ) if per_sec: label = f"Number of task {name} per second ({window}s windows)" else: label = f"Number of task {name} within {window}s windows" series = series_refit_index(series, window=self.trace.window) series.name = name return plot_signal(series, name=label)
def plot_cpu_frequencies(self, cpu: CPU, average: bool = True): """ Plot frequency for the specified CPU :param cpu: The CPU for which to plot frequencies :type cpus: int :param average: If ``True``, add a horizontal line which is the frequency average. :type average: bool If ``sched_overutilized`` events are available, the plots will also show the intervals of time where the system was overutilized. """ logger = self.logger df = self.df_cpu_frequency(cpu) if "freqs" in self.trace.plat_info: frequencies = self.trace.plat_info['freqs'][cpu] else: logger.info(f"Estimating CPU{cpu} frequencies from trace") frequencies = sorted(list(df.frequency.unique())) logger.debug(f"Estimated frequencies: {frequencies}") avg = self.get_average_cpu_frequency(cpu) logger.info("Average frequency for CPU{} : {:.3f} GHz".format( cpu, avg / 1e6)) df = df_refit_index(df, window=self.trace.window) fig = plot_signal(df['frequency'], name=f'Frequency of CPU{cpu} (Hz)') if average and avg > 0: fig *= hv.HLine(avg, group='average').opts(color='red') plot_overutilized = self.ana.status.plot_overutilized if self.trace.has_events(plot_overutilized.used_events): fig *= plot_overutilized() return fig
def plot_thermal_zone_temperature(self, thermal_zone_id: int): """ Plot temperature of thermal zones (all by default) :param thermal_zone_id: ID of the zone :type thermal_zone_id: int """ window = self.trace.window df = self.df_thermal_zones_temperature() df = df[df['id'] == thermal_zone_id] df = df_refit_index(df, window=window) tz_name = df.thermal_zone.unique()[0] return plot_signal( series_refit_index(df['temp'], window=window), name=f'Thermal zone "{tz_name}"', ).options( title='Temperature evolution', ylabel='Temperature (°C.10e3)' )
def plot_extra(task, df): figs = [] if duty_cycle: figs.append( plot_signal(df['duty_cycle'], name=f'Duty cycle of {task}') ) if duration: def plot_duration(active, label): duration_series = df[df['active'] == active]['duration'] # Add blanks in the plot when the state is not the one we care about duration_series = duration_series.reindex_like(df) return plot_signal(duration_series, name=f'{label} duration of {task}') figs.extend( plot_duration(active, label) for active, label in ( (True, 'Activations'), (False, 'Sleep') ) ) return figs
def plot_task_required_capacity(self, task: TaskID): """ Plot the minimum required capacity of a task :param task: The name or PID of the task, or a tuple ``(pid, comm)`` :type task: str or int or tuple """ window = self.trace.window task_ids = self.trace.get_task_ids(task) df = self.df_tasks_signal('required_capacity') df = df_filter_task_ids(df, task_ids) df = df_refit_index(df, window=window) # Build task names (there could be multiple, during the task lifetime) task_name = f"Task ({', '.join(map(str, task_ids))})" return plot_signal( df['required_capacity'], name='required_capacity', ).options( title=f'Required CPU capacity for task {task}', ylabel='Utilization', )
def plot_peripheral_frequency(self, clk_name: str, average: bool = True): """ Plot frequency for the specified peripheral clock frequency :param clk_name: The clock name for which to plot frequency :type clk_name: str :param average: If ``True``, add a horizontal line which is the frequency average. :type average: bool """ df = self.df_peripheral_clock_effective_rate(clk_name) freq = df['effective_rate'] freq = series_refit_index(freq, window=self.trace.window) fig = plot_signal(freq, name=f'Frequency of {clk_name} (Hz)') if average: avg = series_mean(freq) if avg > 0: fig *= hv.HLine(avg, group='average').opts(color='red') return fig
def _plot_signal(cpu, signal): df = self.df_cpus_signal(signal, cpus=[cpu]) df = df_refit_index(df, window=window) return plot_signal(df[signal], name=signal).options( dict(Curve=dict(alpha=0.5)), )
def plot_duration(active, label): duration_series = df[df['active'] == active]['duration'] # Add blanks in the plot when the state is not the one we care about duration_series = duration_series.reindex_like(df) return plot_signal(duration_series, name=f'{label} duration of {task}')