diff --git a/lisa/energy_model.py b/lisa/energy_model.py index 2e8d02d79de68528697442441890569135edd631..f8686e351844d9ad9a88154750ff15bf15334478 100644 --- a/lisa/energy_model.py +++ b/lisa/energy_model.py @@ -31,6 +31,15 @@ from devlib.utils.misc import mask_to_list, ranges_to_list from devlib.exception import TargetStableError from trappy.stats.grammar import Parser +#TODO: This should be moved into a utility library somewhere if its useful elsewhere +from itertools import zip_longest +def grouper(iterable, n, fillvalue=None): + """Collect data into fixed-length chunks or blocks""" + # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx" + # Since the same iterator is used, it will yield a new item every time zip call next() on it + args = [iter(iterable)] * n + return zip_longest(*args, fillvalue=fillvalue) + """Classes for modeling and estimating energy usage of CPU systems""" def read_multiple_oneline_files(target, glob_patterns): @@ -1011,6 +1020,7 @@ class EnergyModel(Serializable, Loggable): # Read all the files we might need in one go, otherwise this will take # ages. sge_globs = [sge_path('**', '**', '**', 'cap_states'), + sge_path('**', '**', '**', 'nr_cap_states'), sge_path('**', '**', '**', 'idle_states')] sge_file_values = read_multiple_oneline_files(target, sge_globs) @@ -1034,13 +1044,35 @@ class EnergyModel(Serializable, Loggable): def read_active_states(cpu, domain_level): cap_states_path = sge_path(cpu, domain_level, 0, 'cap_states') cap_states_strs = read_sge_file(cap_states_path).split() - - # cap_states lists the capacity of each state followed by its power, - # in increasing order. The `zip` call does this: + nr_cap_states_path = sge_path(cpu, domain_level, 0, 'nr_cap_states') + nr_cap_states_strs = read_sge_file(nr_cap_states_path).split() + # there are potentially two formats for this data which can be + # differentiated by knowing how many strings were obtained when + # we split cap_states *and* how many cap states there are. + # If the split has 2x the number of states, the reported states + # are from a kernel without frequency-model support and there + # two values per state. If the split has 3x the number of states + # then the reported states are from a kernel which *has* + # frequency model support, and each state has three values to parse. + nr_values = len(cap_states_strs) + nr_states = int(nr_cap_states_strs[0]) + em_member_count = int(nr_values/nr_states) + if em_member_count not in (2, 3): + raise TargetStableError('Unsupported cap_states format ' + 'cpu={} domain_level={} path={}'.format(cpu, domain_level, cap_states_path)) + + # Here we split the incoming cap_states_strs list into em_member_count lists, so that + # we can use the first one (representing capacity) and the last one (representing power) + # to build the EM class. What we get is + # for a 2-element list: # [c0, p0, c1, p1, c2, p2] -> [(c0, p0), (c1, p1), (c2, p2)] + # or for a 3-element list: + # [c0, f0, p0, c1, f1, p1, c2, f2, p2] -> [(c0, f0, p0), (c1, f1, p1), (c2, f2, p2)] + # it's generic, and doesn't care if the EM gets any more values in between so long as the + # capacity is first and power is last. cap_states = [ActiveState(capacity=int(c), power=int(p)) - for c, p in zip(cap_states_strs[0::2], - cap_states_strs[1::2])] + for c, p in map(lambda x: (x[0],x[-1]), grouper(cap_states_strs, em_member_count))] + freqs = target.cpufreq.list_frequencies(cpu) return OrderedDict(list(zip(sorted(freqs), cap_states)))