def plot_cpu_cooling_states(self, cpu: CPU, axis, local_fig): """ 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) cdev_name = f"CPUs {mask_to_list(df.cpus.unique()[0])}" series = series_refit_index(df['cdev_state'], window=window) series.plot(drawstyle="steps-post", ax=axis, label=f"\"{cdev_name}\"") axis.legend() if local_fig: axis.grid(True) axis.set_title("cpufreq cooling devices status") axis.yaxis.set_major_locator(MaxNLocator(integer=True)) axis.grid(axis='y')
def plot_thermal_zone_temperature(self, thermal_zone_id: int, axis, local_fig): """ 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] series = series_refit_index(df['temp'], window=window) series.plot(drawstyle="steps-post", ax=axis, label=f"Thermal zone \"{tz_name}\"") axis.legend() if local_fig: axis.grid(True) axis.set_title("Temperature evolution") axis.set_ylabel("Temperature (°C.10e3)")
def plot_tasks_forks(self, target_cpus: TypedList[CPU]=None, window: float=0.01, per_sec: bool=False, axis=None, local_fig=None): """ Plot task forks over time :param target_cpus: :type target_cpus: :param window: The rolling window size for fork counts. :type window: float :param per_sec: Display wakeups per second if True, else wakeup counts within the window :type per_sec: bool """ df = self.trace.df_event("sched_wakeup_new") 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) series = series_refit_index(series, window=self.trace.window) series.plot(ax=axis, legend=False) if per_sec: axis.set_title("Number of task forks per second ({}s windows)".format(window)) else: axis.set_title("Number of task forks within {}s windows".format(window))
def plot_task_residency(self, task: TaskID, axis, local_fig): """ Plot on which CPUs the task ran on over time :param task: Task to track :type task: int or str or tuple(int, str) """ task_id = self.trace.get_task_id(task, update=False) sw_df = self.trace.df_event("sched_switch") sw_df = df_filter_task_ids(sw_df, [task_id], pid_col='next_pid', comm_col='next_comm') if "freq-domains" in self.trace.plat_info: # If we are aware of frequency domains, use one color per domain for domain in self.trace.plat_info["freq-domains"]: series = sw_df[sw_df["__cpu"].isin(domain)]["__cpu"] if series.empty: # Cycle the colours to stay consistent self.cycle_colors(axis) else: series = series_refit_index(series, window=self.trace.window) series.plot( ax=axis, style='+', label="Task running in domain {}".format(domain)) else: series = series_refit_index(sw_df['__cpu'], window=self.trace.window) series.plot(ax=axis, style='+') plot_overutilized = self.trace.analysis.status.plot_overutilized if self.trace.has_events(plot_overutilized.used_events): plot_overutilized(axis=axis) # Add an extra CPU lane to make room for the legend axis.set_ylim(-0.95, self.trace.cpus_count - 0.05) axis.set_title("CPU residency of task \"{}\"".format(task)) axis.set_ylabel('CPUs') axis.grid(True) axis.legend()
def plot_residency(): if "freq-domains" in self.trace.plat_info: # If we are aware of frequency domains, use one color per domain for domain in self.trace.plat_info["freq-domains"]: series = sw_df[sw_df["__cpu"].isin(domain)]["__cpu"] series = series_refit_index(series, window=self.trace.window) if series.empty: return _hv_neutral() else: return self._plot_markers( series, label=f"Task running in domain {domain}" ) else: self._plot_markers( series_refit_index(sw_df['__cpu'], window=self.trace.window) )
def get_average_cpu_frequency(self, cpu): """ Get the average frequency for a given CPU :param cpu: The CPU to analyse :type cpu: int """ df = self.df_cpu_frequency(cpu) freq = series_refit_index(df['frequency'], window=self.trace.window) return series_mean(freq)
def plot_placement(cols, placement_df): placement = cols['placement'] series = df["cpu"] series = series_refit_index(series, window=self.trace.window) series.name = 'cpu' return hv.Scatter( series, label=placement, ).options( marker='+', yticks=list(range(nr_cpus)), )
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_util(self): trace = self.trace analysis = trace.analysis.load_tracking fig, axes = analysis.setup_plot(nrows=len(self.rtapp_tasks)) for task, axis in zip(self.rtapp_tasks, axes): analysis.plot_task_signals(task, signals=['util'], axis=axis) trace.analysis.rta.plot_phases(task, axis=axis, wlgen_profile=self.rtapp_profile) activation_axis = axis.twinx() trace.analysis.tasks.plot_task_activation(task, duty_cycle=True, overlay=True, alpha=0.2, axis=activation_axis) df_activations = trace.analysis.tasks.df_task_activation(task) df_util = analysis.df_task_signal(task, 'util') def compute_means(row): start = row.name end = start + row['duration'] phase_activations = df_window(df_activations, (start, end)) phase_util = df_window(df_util, (start, end)) series = pd.Series({ 'Phase duty cycle average': series_mean(phase_activations['duty_cycle']), 'Phase util tunnel average': kernel_util_mean( phase_util['util'], plat_info=self.plat_info, ), }) return series df_means = trace.analysis.rta.df_phases(task).apply(compute_means, axis=1) df_means = series_refit_index(df_means, window=trace.window) df_means['Phase duty cycle average'].plot(drawstyle='steps-post', ax=activation_axis) df_means['Phase util tunnel average'].plot(drawstyle='steps-post', ax=axis) activation_axis.legend() axis.legend() filepath = ArtifactPath.join(self.res_dir, 'tasks_util.png') analysis.save_plot(fig, filepath=filepath) filepath = ArtifactPath.join(self.res_dir, 'cpus_util.png') cpus = sorted(self.cpus) analysis.plot_cpus_signals(cpus, signals=['util'], filepath=filepath)
def plot_task_placement(self, task, axis, local_fig): """ Plot the CPU placement of the task :param task: The name or PID of the task, or a tuple ``(pid, comm)`` :type task: str or int or tuple """ task_id = self.trace.get_task_id(task, update=False) # Get all utilization update events df = self.df_task_signal(task_id, 'required_capacity') cpu_capacities = self.trace.plat_info["cpu-capacities"] def evaluate_placement(cpu, required_capacity): capacity = cpu_capacities[cpu] if capacity < required_capacity: return "CPU capacity < required capacity" elif capacity == required_capacity: return "CPU capacity == required capacity" else: return "CPU capacity > required capacity" df["placement"] = df.apply(lambda row: evaluate_placement( row["cpu"], row["required_capacity"]), axis=1) for stat in df["placement"].unique(): series = df[df.placement == stat]["cpu"] series = series_refit_index(series, self.trace.start, self.trace.end) series.plot(ax=axis, style="+", label=stat) plot_overutilized = self.trace.analysis.status.plot_overutilized if self.trace.has_events(plot_overutilized.used_events): plot_overutilized(axis=axis) axis.set_title("Utilization vs placement of task \"{}\"".format(task)) axis.grid(True) axis.legend()
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_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_task_placement(self, task: TaskID, axis, local_fig): """ Plot the CPU placement of the task :param task: The name or PID of the task, or a tuple ``(pid, comm)`` :type task: str or int or tuple """ task_id = self.trace.get_task_id(task, update=False) # Get all utilization update events df = self.df_task_signal(task_id, 'required_capacity').copy() cpu_capacities = self.trace.plat_info["cpu-capacities"]['orig'] df['capacity'] = df['cpu'].map(cpu_capacities) def add_placement(df, comp, comp_str): placement = "CPU capacity {} required capacity".format(comp_str) condition = comp(df['capacity'], df['required_capacity']) df.loc[condition, 'placement'] = placement add_placement(df, operator.lt, '<') add_placement(df, operator.gt, '>') add_placement(df, operator.eq, '==') for cols, placement_df in df_split_signals(df, ['placement']): placement = cols['placement'] series = df["cpu"] series = series_refit_index(series, window=self.trace.window) series.plot(ax=axis, style="+", label=placement) plot_overutilized = self.trace.analysis.status.plot_overutilized if self.trace.has_events(plot_overutilized.used_events): plot_overutilized(axis=axis) if local_fig: axis.set_title( 'Utilization vs placement of task "{}"'.format(task)) axis.grid(True) axis.legend()
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_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 get_expected_cpu_util(self): """ Get the per-phase average CPU utilization expected from the duty cycle of the tasks found in the trace. :returns: A dict of the shape {cpu : {phase_id : expected_util}} .. note:: This is more robust than just looking at the duty cycle in the task profile, since rtapp might not reproduce accurately the duty cycle it was asked. """ cpu_capacities = self.plat_info['cpu-capacities']['rtapp'] cpu_util = {} cpu_freqs = self.plat_info['freqs'] try: freq_df = self.trace.analysis.frequency.df_cpus_frequency() except MissingTraceEventError: cpus_rel_freq = None else: cpus_rel_freq = { # Frequency, normalized according to max frequency on that CPU cols['cpu']: df['frequency'] / max(cpu_freqs[cols['cpu']]) for cols, df in df_split_signals(freq_df, ['cpu']) } for task in self.rtapp_task_ids: df = self.trace.analysis.tasks.df_task_activation(task) for row in self.trace.analysis.rta.df_phases(task).itertuples(): phase = row.phase duration = row.duration start = row.Index end = start + duration # Ignore the first quarter of the util signal of each phase, since # it's impacted by the phase change, and util can be affected # (rtapp does some bookkeeping at the beginning of phases) # start += duration / 4 # readjust the duration to take into account the modification of start duration = end - start window = (start, end) phase_df = df_window(df, window, clip_window=True) for cpu in self.cpus: if cpus_rel_freq is None: rel_freq_mean = 1 else: phase_freq_series = df_window(cpus_rel_freq[cpu], window=window, clip_window=True) # # We might not have frequency data at the beginning of the # # trace, or if not frequency transition happened at all. if phase_freq_series.empty: rel_freq_mean = 1 else: # If we lack freq data at the beginning of the # window, assume the frequency was right. if phase_freq_series.index[0] > start: phase_freq_series = pd.concat([ pd.Series([1.0], index=[start]), phase_freq_series ]) # Extend the frequency to the right so that the mean # takes into account all the data we have freq_window = (phase_freq_series.index[0], end) rel_freq_mean = series_mean( series_refit_index(phase_freq_series, window=freq_window)) cpu_phase_df = phase_df[phase_df['cpu'] == cpu].dropna() if cpu_phase_df.empty: duty_cycle = 0 cpu_residency = 0 else: duty_cycle = series_mean( df_refit_index(cpu_phase_df['duty_cycle'], window=window)) cpu_residency = end - max(cpu_phase_df.index[0], start) phase_util = UTIL_SCALE * duty_cycle * ( cpu_capacities[cpu] / UTIL_SCALE) # Pro-rata with the time spent on that CPU, so we get # the correct average. phase_util *= cpu_residency / duration # We might not have run at max freq, e.g. because of # thermal capping, so take that into account phase_util *= rel_freq_mean cpu_util.setdefault(cpu, {}).setdefault(phase, 0) cpu_util[cpu][phase] += phase_util return cpu_util
def plotter(axis, local_fig): freq_axis, state_axis = axis freq_axis.get_figure().suptitle('Peripheral frequency', y=.97, fontsize=16, horizontalalignment='center') freq = self.df_peripheral_clock_effective_rate(clk) freq = df_refit_index(freq, window=window) # Plot frequency information (set rate) freq_axis.set_title("Clock frequency for " + clk) set_rate = freq['state'].dropna() rate_axis_lib = 0 if len(set_rate) > 0: rate_axis_lib = set_rate.max() set_rate.plot(style=['b--'], ax=freq_axis, drawstyle='steps-post', alpha=0.4, label="clock_set_rate value") freq_axis.hlines(set_rate.iloc[-1], set_rate.index[-1], end, linestyle='--', color='b', alpha=0.4) else: logger.warning('No clock_set_rate events to plot') # Plot frequency information (effective rate) eff_rate = freq['effective_rate'].dropna() eff_rate = series_refit_index(eff_rate, window=window) if len(eff_rate) > 0 and eff_rate.max() > 0: rate_axis_lib = max(rate_axis_lib, eff_rate.max()) eff_rate.plot(style=['b-'], ax=freq_axis, drawstyle='steps-post', alpha=1.0, label="Effective rate (with on/off)") freq_axis.hlines(eff_rate.iloc[-1], eff_rate.index[-1], end, linestyle='-', color='b', alpha=1.0) else: logger.warning('No effective frequency events to plot') freq_axis.set_ylim(0, rate_axis_lib * 1.1) freq_axis.set_xlabel('') freq_axis.grid(True) freq_axis.legend() def mhz(x, pos): return '{:1.2f} MHz'.format(x * 1e-6) freq_axis.get_yaxis().set_major_formatter(FuncFormatter(mhz)) on = freq[freq.state == 1] state_axis.hlines([0] * len(on), on['start'], on['start'] + on['len'], linewidth=10.0, label='clock on', color='green') off = freq[freq.state == 0] state_axis.hlines([0] * len(off), off['start'], off['start'] + off['len'], linewidth=10.0, label='clock off', color='red') # Plot time period that the clock state was unknown from the trace indeterminate = pd.concat([on, off]).sort_index() if indeterminate.empty: indet_range_max = end else: indet_range_max = indeterminate.index[0] state_axis.hlines(0, 0, indet_range_max, linewidth=1.0, label='indeterminate clock state', linestyle='--') state_axis.legend(bbox_to_anchor=(0., 1.02, 1., 0.102), loc=3, ncol=3, mode='expand') state_axis.set_yticks([]) state_axis.set_xlabel('seconds') state_axis.set_xlim(start, end)