def df_peripheral_clock_effective_rate(self, clk_name): # Note: the kernel still defines a "clock_*" variant for each of these, # but it's not actually used anywhere in the code. The new "clk_*" # events are the ones we are interested about. rate_df = self.trace.df_event('clk_set_rate') enable_df = self.trace.df_event('clk_enable').copy() disable_df = self.trace.df_event('clk_disable').copy() # Add 'state' for enable and disable events enable_df['state'] = 1 disable_df['state'] = 0 freq = rate_df[rate_df['name'] == clk_name] enables = enable_df[enable_df['name'] == clk_name] disables = disable_df[disable_df['name'] == clk_name] freq = df_merge((freq, enables, disables)).ffill() freq['start'] = freq.index df_add_delta(freq, col='len', src_col='start', window=self.trace.window, inplace=True) freq['effective_rate'] = np.where(freq['state'] == 0, 0, freq['rate']) return freq
def _get_callgraph(self, tag_df=None, thread_root_functions=None): entry_df = self.df_funcgraph(event='entry').copy(deep=False) entry_df['event'] = _CallGraph._EVENT.ENTRY exit_df = self.df_funcgraph(event='exit').copy(deep=False) exit_df['event'] = _CallGraph._EVENT.EXIT # Attempt to get the CPU capacity signal to normalize the results capacity_cols = ['__cpu', 'event', 'capacity'] try: capacity_df = self.trace.analysis.load_tracking.df_cpus_signal( 'capacity') except MissingTraceEventError: capacity_df = pd.DataFrame(columns=capacity_cols) else: capacity_df = capacity_df.copy(deep=False) capacity_df['__cpu'] = capacity_df['cpu'] capacity_df['event'] = _CallGraph._EVENT.SET_CAPACITY capacity_df = capacity_df[capacity_cols] # Set a reasonable initial capacity try: orig_capacities = self.trace.plat_info['cpu-capacities']['orig'] except KeyError: pass else: orig_capacities_df = pd.DataFrame.from_records( ((-1 * cpu, cpu, _CallGraph._EVENT.SET_CAPACITY, cap) for cpu, cap in orig_capacities.items()), columns=['Time', '__cpu', 'event', 'capacity'], index='Time', ) capacity_df = pd.concat((orig_capacities_df, capacity_df)) to_merge = [entry_df, exit_df, capacity_df] if tag_df is not None: cpu = tag_df['__cpu'] tag_df = tag_df.drop(columns=['__cpu']) tag_df = pd.DataFrame( dict( tags=tag_df.apply(pd.Series.to_dict, axis=1), __cpu=cpu, )) tag_df['event'] = _CallGraph._EVENT.SET_TAG to_merge.append(tag_df) df = df_merge(to_merge) return _CallGraph.from_df(df, thread_root_functions=thread_root_functions)
def compare_with_traces(self, others, normalize=True, **kwargs): """ Compare the :class:`~lisa.trace.Trace` it's called on with the other traces passed as ``others``. The reference is the trace it's called on. :returns: a :class:`lisa.stats.Stats` object just like :meth:`profile_stats`. :param others: List of traces to compare against. :type others: list(lisa.trace.Trace) :Variable keyword arguments: Forwarded to :meth:`profile_stats`. """ ref = self.trace traces = [ref] + list(others) paths = [trace.trace_path for trace in traces] common_prefix_len = len(os.path.commonprefix(paths)) common_suffix_len = len( os.path.commonprefix(list(map(lambda x: str(reversed(x)), paths)))) def get_name(trace): name = trace.trace_path[common_prefix_len:common_suffix_len] if not name: if trace is ref: name = 'ref' else: name = str(traces.index(trace)) return name def get_df(trace): df = self.df_calls(normalize=normalize) df = df.copy(deep=False) df['trace'] = get_name(trace) return df df = df_merge(map(get_df, traces)) ref_group = {'trace': get_name(ref)} return self._profile_stats_from_df(df, ref_group=ref_group, **kwargs)
def df_ramp_boost(self): """ Return a dataframe with schedutil-related signals, sampled at the frequency decisions timestamps for the CPU this bundle was executed on. .. note:: The computed columns only take into account the CPU the test was executing on. It currently does not handle multi-task workloads. """ trace = self.trace cpu = self.cpu task = self.rtapp_task_ids[0] # schedutil_df also has a 'util' column that would conflict schedutil_df = trace.df_event('schedutil_em')[['cpu', 'cost_margin', 'base_freq']] schedutil_df = schedutil_df.copy() schedutil_df['from_schedutil'] = True def compute_base_cost(row): freq = row['base_freq'] cpu = row['cpu'] em = self.plat_info['nrg-model'] active_states = em.cpu_nodes[cpu].active_states freqs = sorted(active_states.keys()) max_freq = max(freqs) def cost(freq): higher_freqs = list(itertools.dropwhile(lambda f: f < freq, freqs)) freq = freqs[-1] if not higher_freqs else higher_freqs[0] active_state = active_states[freq] return active_state.power * max_freq / freq max_cost = max( cost(freq) for freq in active_states.keys() ) return cost(freq) / max_cost * 100 schedutil_df['base_cost'] = schedutil_df.apply(compute_base_cost, axis=1) task_active = trace.analysis.tasks.df_task_states(task)['curr_state'] task_active = task_active.apply(lambda state: int(state == TaskState.TASK_ACTIVE)) task_active = task_active.reindex(schedutil_df.index, method='ffill') # Assume task active == CPU active, since there is only one task assert len(self.rtapp_task_ids) == 1 cpu_active_df = pd.DataFrame({'cpu_active': task_active}) cpu_active_df['cpu'] = cpu cpu_active_df.dropna(inplace=True) df_list = [ schedutil_df, trace.analysis.load_tracking.df_cpus_signal('util'), trace.analysis.load_tracking.df_cpus_signal('enqueued'), cpu_active_df, ] df = df_merge(df_list, filter_columns={'cpu': cpu}) df['from_schedutil'].fillna(value=False, inplace=True) df.ffill(inplace=True) df.dropna(inplace=True) # Reconstitute how schedutil sees signals by subsampling the # "master" dataframe, so we can look at signals coming from other # dataframes df = df[df['from_schedutil'] == True] # pylint: disable=singleton-comparison df.drop(columns=['from_schedutil'], inplace=True) # If there are some NaN at the beginning, just drop some data rather # than using fake data df.dropna(inplace=True) boost_points = ( # util_est_enqueued is the same as last freq update (df['enqueued'].diff() == 0) & # util_avg is increasing (df['util'].diff() >= 0) & # util_avg > util_est_enqueued (df['util'] > df['enqueued']) & # CPU is not idle (df['cpu_active']) ) df['boost_points'] = boost_points df['expected_cost_margin'] = (df['util'] - df['enqueued']).where( cond=boost_points, other=0, ) # cost_margin values range from 0 to 1024 ENERGY_SCALE = 1024 for col in ('expected_cost_margin', 'cost_margin'): df[col] *= 100 / ENERGY_SCALE df['allowed_cost'] = df['base_cost'] + df['cost_margin'] # We cannot know if the first row is supposed to be boosted or not # because we lack history, so we just drop it return df.iloc[1:]
def test_hotplug_rollback(self) -> ResultBundle: """ Test that the hotplug can rollback to its previous state after a failure. All possible steps, up/down combinations will be tested. For each combination, also verify that the hotplug is going through all the steps it is supposed to. """ df = df_merge([ self.trace.df_event('userspace@hotplug_rollback'), self.trace.df_event('cpuhp_enter') ]) # Keep only the states delimited by _mark_trace() df['test'].ffill(inplace=True) df = df[df['test'] == 1] df.drop(columns='test', inplace=True) df['up'].ffill(inplace=True) df['up'] = df['up'].astype(bool) # Read the expected states from full hot(un)plug df['expected'].ffill(inplace=True) df['expected'] = df['expected'].astype(bool) expected_down = self._get_expected_states(df, up=False) expected_up = self._get_expected_states(df, up=True) df = df[~df['expected']] df.drop(columns='expected', inplace=True) def _get_expected_rollback(up, failing_state): return list( filter( partial( operator.gt if up else operator.lt, failing_state, ), chain(expected_up, expected_down) if up else chain(expected_down, expected_up) ) ) def _verify_rollback(df): failing_state = df['failing_state'].iloc[0] up = df['up'].iloc[0] expected = _get_expected_rollback(up, failing_state) return pd.DataFrame(data={ 'failing_state': df['failing_state'], 'up': up, 'result': df['idx'].tolist() == expected }) df['failing_state'].ffill(inplace=True) df.dropna(inplace=True) df = df.groupby(['up', 'failing_state'], observed=True).apply(_verify_rollback) df.drop_duplicates(inplace=True) res = ResultBundle.from_bool(df['result'].all()) res.add_metric('Failed rollback states', df[~df['result']]['failing_state'].tolist()) return res
def df_ramp_boost(self): """ Return a dataframe with schedutil-related signals, sampled at the frequency decisions timestamps for the CPU this bundle was executed on. .. note:: The computed columns only take into account the CPU the test was executing on. It currently does not handle multi-task workloads. """ trace = self.trace cpu = self.cpu # schedutil_df also has a 'util' column that would conflict schedutil_df = trace.df_events('schedutil_em')[['cpu', 'cost_margin']] schedutil_df['from_schedutil'] = True df_list = [ schedutil_df, trace.analysis.load_tracking.df_cpus_signal('util'), trace.analysis.load_tracking.df_cpus_signal('util_est_enqueued'), ] df = df_merge(df_list, filter_columns={'cpu': cpu}) df['from_schedutil'].fillna(value=False, inplace=True) df.ffill(inplace=True) df.dropna(inplace=True) # Reconstitute how schedutil sees signals by subsampling the # "master" dataframe, so we can look at signals coming from other # dataframes df = df[df['from_schedutil'] == True] df.drop(columns=['from_schedutil'], inplace=True) # If there are some NaN at the beginning, just drop some data rather # than using fake data df.dropna(inplace=True) boost_points = ( # util_est_enqueued is the same as last freq update (df['util_est_enqueued'].diff() == 0) & # util_avg is increasing (df['util'].diff() >= 0) & # util_avg > util_est_enqueued (df['util'] > df['util_est_enqueued'])) df['boost_points'] = boost_points df['expected_cost_margin'] = (df['util'] - df['util_est_enqueued']).where( cond=boost_points, other=0, ) # cost_margin values range from 0 to 1024 ENERGY_SCALE = 1024 for col in ('expected_cost_margin', 'cost_margin'): df[col] *= 100 / ENERGY_SCALE # We cannot know if the first row is supposed to be boosted or not # because we lack history, so we just drop it return df.iloc[1:]