From 4e034a651e2017fd0d5eca6d8b3389c8f6b434bc Mon Sep 17 00:00:00 2001
From: Chris Redpath <chris.redpath@arm.com>
Date: Fri, 15 Jun 2018 15:14:26 +0100
Subject: [PATCH 1/2] energy_model: Add loader method to read simplified EM
 from target

If we cannot find an energy directory in the first domain & group of the
first online CPU, this target may be using the simplified EM which can be
loaded from many sources and provides a view of the loaded energy model
through a dedicated folder at /sys/devices/system/cpu/energy_model.

Provide a method to load from the new location and some utilities to try
to work out if one of them is present and load the correct one.

Signed-off-by: Chris Redpath <chris.redpath@arm.com>
---
 libs/utils/energy_model.py | 148 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 144 insertions(+), 4 deletions(-)

diff --git a/libs/utils/energy_model.py b/libs/utils/energy_model.py
index e7badc7ca..a954c3a0f 100644
--- a/libs/utils/energy_model.py
+++ b/libs/utils/energy_model.py
@@ -24,7 +24,7 @@ import re
 import pandas as pd
 import numpy as np
 
-from devlib.utils.misc import memoized, mask_to_list
+from devlib.utils.misc import memoized, mask_to_list, ranges_to_list
 from devlib import TargetError
 from trappy.stats.grammar import Parser
 
@@ -774,7 +774,105 @@ class EnergyModel(object):
         return sorted(ret, key=lambda x: x[0])
 
     @classmethod
-    def from_target(cls, target):
+    def from_simplifiedEM_target(cls, target,
+            directory='/sys/devices/system/cpu/energy_model'):
+        """
+        Create an EnergyModel by reading a target filesystem on a device with
+        the new Simplified Energy Model present.
+
+        This uses the energy_model sysctl added by EAS patches to exposes
+        the frequency domains, together with a tuple of capacity, frequency
+        and active power for each CPU. This feature is not upstream in mainline
+        Linux (as of v4.17), and only exists in Android kernels later than
+        android-4.14.
+
+        Wrt. idle states - the EnergyModel constructed won't be aware of
+        any power data or topological dependencies for entering "cluster"
+        idle states since the simplified model has no such concept.
+
+        Initialises only Active States for CPUs and clears all other levels.
+
+        :param target: Devlib target object to read filesystem from. Must have
+                       cpufreq and cpuidle modules enabled.
+        :returns: Constructed EnergyModel object based on the parameters
+                  reported by the target.
+        """
+        if 'cpuidle' not in target.modules:
+            raise TargetError('Requires cpuidle devlib module. Please ensure '
+                               '"cpuidle" is listed in your target/test modules')
+
+        # Simplified EM on-disk format (for each frequency domain):
+        #    /sys/devices/system/cpu/energy_model/<frequency_domain>/..
+        #        ../capacity
+        #           contains a space-separated list of capacities in increasing order
+        #        ../cpus
+        #           cpulist-formatted representation of the cpus in the frequency domain
+        #        ../frequency
+        #           space-separated list of frequencies in corresponding order to capacities
+        #        ../power
+        #           space-separated list of power consumption in corresponding order to capacities
+        # taken together, the contents of capacity, frequency and power give you the required
+        # tuple for ActiveStates.
+        # hence, domain should be supplied as a glob, and fields should be
+        #   capacity, cpus, frequency, power
+
+        sysfs_em = target.read_tree_values(directory, depth=3)
+
+        if not sysfs_em:
+            raise TargetError('Simplified Energy Model not exposed '
+                              'at {} in sysfs.'.format(directory))
+
+        cpu_to_fdom = {}
+        for fd, fields in sysfs_em.iteritems():
+            cpus = ranges_to_list(fields["cpus"])
+            for cpu in cpus:
+                cpu_to_fdom[cpu] = fd
+            sysfs_em[fd]['cpus'] = cpus
+            sysfs_em[fd]['frequency'] = map(int, sysfs_em[fd]['frequency'].split(' '))
+            sysfs_em[fd]['power'] =  map(int, sysfs_em[fd]['power'].split(' '))
+            sysfs_em[fd]['capacity'] = map(int, sysfs_em[fd]['capacity'].split(' '))
+
+        def read_active_states(cpu):
+            fd = sysfs_em[cpu_to_fdom[cpu]]
+            cstates = zip(fd['capacity'], fd['power'])
+            active_states = [ActiveState(c, p) for c, p in cstates]
+            return OrderedDict(zip(fd['frequency'], active_states))
+
+        def read_idle_states(cpu):
+            # idle states are not supported in the new model
+            # record 0 power for them all, but name them according to target
+            names = [s.name for s in target.cpuidle.get_states(cpu)]
+            return OrderedDict((name, 0) for name in names)
+
+        # Read the CPU-level data
+        cpus = range(target.number_of_cpus)
+        cpu_nodes = []
+        for cpu in cpus:
+            node = EnergyModelNode(
+                cpu=cpu,
+                active_states=read_active_states(cpu),
+                idle_states=read_idle_states(cpu))
+            cpu_nodes.append(node)
+
+        root = EnergyModelRoot(children=cpu_nodes)
+        freq_domains = [sysfs_em[fdom]['cpus'] for fdom in sysfs_em]
+
+        # We don't have a way to read the idle power domains from sysfs (the kernel
+        # isn't even aware of them) so we'll just have to assume each CPU is its
+        # own power domain and all idle states are independent of each other.
+        cpu_pds = []
+        for cpu in cpus:
+            names = [s.name for s in target.cpuidle.get_states(cpu)]
+            cpu_pds.append(PowerDomain(cpu=cpu, idle_states=names))
+
+        root_pd = PowerDomain(children=cpu_pds, idle_states=[])
+        return cls(root_node=root,
+                   root_power_domain=root_pd,
+                   freq_domains=freq_domains)
+
+    @classmethod
+    def from_sd_target(cls, target, filename=
+            '/proc/sys/kernel/sched_domain/cpu{}/domain{}/group{}/energy/{}'):
         """
         Create an EnergyModel by reading a target filesystem
 
@@ -804,8 +902,7 @@ class EnergyModel(object):
                                '"cpuidle" is listed in your target/test modules')
 
         def sge_path(cpu, domain, group, field):
-            f = '/proc/sys/kernel/sched_domain/cpu{}/domain{}/group{}/energy/{}'
-            return f.format(cpu, domain, group, field)
+            return filename.format(cpu, domain, group, field)
 
         # Read all the files we might need in one go, otherwise this will take
         # ages.
@@ -897,6 +994,49 @@ class EnergyModel(object):
                    root_power_domain=root_pd,
                    freq_domains=freq_domains)
 
+    @classmethod
+    def from_target(cls, target):
+        """
+        Create an EnergyModel by reading a target filesystem
+
+        If present, load an EM provided via dt using from_sd_target, since these
+        devices make the full EM available via the sched domain in sysfs. If
+        there is no EM at this location, attempt to load the simplified EM
+        made available via dedicated sysfs files.
+
+        :param target: Devlib target object to read filesystem from. Must have
+                       cpufreq and cpuidle modules enabled.
+        :returns: Constructed EnergyModel object based on the parameters
+                  reported by the target.
+        """
+        _log = logging.getLogger('EMReader')
+
+        def em_present_in_sd(target):
+            try:
+                cpu = target.list_online_cpus()[0]
+                d = '/proc/sys/kernel/sched_domain/cpu{}/domain0/group0/energy'.format(cpu)
+                _log.info('Checking if directory {} exists'.format(d))
+                return target.directory_exists(d)
+            except Exception:
+                return False
+
+        def simplified_em_present_in_cpusysfs(target):
+            try:
+                d = '/sys/devices/system/cpu/energy_model'
+                _log.info('Checking if directory {} exists'.format(d))
+                return target.directory_exists(d)
+            except Exception:
+                return False
+
+        if em_present_in_sd(target):
+            _log.info('Attempting to load EM from sched domains.')
+            return cls.from_sd_target(target)
+        elif simplified_em_present_in_cpusysfs(target):
+            _log.info('Attempting to load simplified EM.')
+            return cls.from_simplifiedEM_target(target)
+        else:
+            raise TargetError('Unable to probe for energy model on target.')
+
     def estimate_from_trace(self, trace):
         """
         Estimate the energy consumption of the system by looking at a trace
-- 
GitLab


From a9ca62e84a002315f9890de5107bd403647e0c65 Mon Sep 17 00:00:00 2001
From: Chris Redpath <chris.redpath@arm.com>
Date: Tue, 19 Jun 2018 17:57:53 +0100
Subject: [PATCH 2/2] EnergyModel: Wrap test/loader functions in a dictionary

If we wrap these functions up in a dictionary we can add new methods
with less code churn. I'm not 100% convinced this is a good pattern,
but it's here for review.

Signed-off-by: Chris Redpath <chris.redpath@arm.com>
---
 libs/utils/energy_model.py | 64 +++++++++++++++++++++++++-------------
 1 file changed, 42 insertions(+), 22 deletions(-)

diff --git a/libs/utils/energy_model.py b/libs/utils/energy_model.py
index a954c3a0f..d01149ee7 100644
--- a/libs/utils/energy_model.py
+++ b/libs/utils/energy_model.py
@@ -1011,31 +1011,51 @@ class EnergyModel(object):
         """
         _log = logging.getLogger('EMReader')
 
-        def em_present_in_sd(target):
+        # To add a new EM reader type, the following is required:
+        # 1. Create an inline function to test for EM presence which takes a
+        #    target as the first parameter. Any exceptions raised here will
+        #    be caught in the loader loop.
+        # 2. Create a function which returns an EnergyModel as a member of this
+        #    class, also with a target as the first parameter.
+        # 3. Add an entry to the em_loaders dict where 'check' contains the
+        #    inline function and 'load' contains the class member function
+        # 4. If you need any additional data, add it to the em_loaders dict - any
+        #    additional keys will be passed to both 'check' and 'load' functions
+        #    as named parameters.
+
+        # Utility functions to determine if we should try to use a particular
+        # EM loader function.
+        def em_present_in_sd(target, filename=None):
+            cpu = target.list_online_cpus()[0]
+            f = filename.format(cpu, 0, 0, 'cap_states')
+            return target.file_exists(f)
+        def simplified_em_present_in_cpusysfs(target, directory=None):
+            return target.directory_exists(directory)
+
+        # em_loaders dictionary joins EM loaders and the identifying functions
+        # with any associated metadata
+        em_loaders = {
+            'sd'    : { 'check': em_present_in_sd,
+                        'load': cls.from_sd_target,
+                        'filename': '/proc/sys/kernel/sched_domain/cpu{}/domain{}/group{}/energy/{}' },
+            'sysfs' : { 'check': simplified_em_present_in_cpusysfs,
+                        'load': cls.from_simplifiedEM_target,
+                        'directory': '/sys/devices/system/cpu/energy_model' }
+                     }
+
+        for loader_type in em_loaders:
+            args = dict(em_loaders[loader_type])
+            check = args.pop('check')
+            load = args.pop('load')
             try:
-                cpu = target.list_online_cpus()[0]
-                d = '/proc/sys/kernel/sched_domain/cpu{}/domain0/group0/energy'.format(cpu)
-                _log.info('Checking if directory {} exists'.format(d))
-                return target.directory_exists(d)
+                em_present = check(target, **args)
             except Exception:
-                return False
+                em_present = False
+            if em_present:
+                _log.info('Attempting to load EM using {}'.format(load.__name__))
+                return load(target, **args)
 
-        def simplified_em_present_in_cpusysfs(target):
-            try:
-                d = '/sys/devices/system/cpu/energy_model'
-                _log.info('Checking if directory {} exists'.format(d))
-                return target.directory_exists(d)
-            except Exception:
-                return False
-
-        if em_present_in_sd(target):
-            _log.info('Attempting to load EM from sched domains.')
-            return cls.from_sd_target(target)
-        elif simplified_em_present_in_cpusysfs(target):
-            _log.info('Attempting to load simplified EM.')
-            return cls.from_simplifiedEM_target(target)
-        else:
-            raise TargetError('Unable to probe for energy model on target.')
+        raise TargetError('Unable to probe for energy model on target.')
 
     def estimate_from_trace(self, trace):
         """
-- 
GitLab