Example #1
0
    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
Example #2
0
    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)
Example #3
0
    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)
Example #4
0
    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:]
Example #5
0
    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
Example #6
0
    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:]