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