From 4a5d6f93691219ef029f67a60a0fab70da43248b Mon Sep 17 00:00:00 2001 From: Brendan Jackman Date: Wed, 2 Nov 2016 16:34:59 +0000 Subject: [PATCH 01/10] energy_model: Add EnergyModel class This class provides a model of systems with: - CPU capacity at different frequencies. - Power usage at different frequency. - Power usage in different idle states. The model is aware of topologically shared resources (clusters), topological dependencies for idle states (power domains) and frequency domains. The intended use case for this model is for testing energy-aware scheduling in a platform-agnostic way. --- libs/utils/energy_model.py | 106 +++++++++++++++ tests/lisa/__init__.py | 0 tests/lisa/test_energy_model.py | 229 ++++++++++++++++++++++++++++++++ 3 files changed, 335 insertions(+) create mode 100644 libs/utils/energy_model.py create mode 100644 tests/lisa/__init__.py create mode 100644 tests/lisa/test_energy_model.py diff --git a/libs/utils/energy_model.py b/libs/utils/energy_model.py new file mode 100644 index 000000000..147a6b54e --- /dev/null +++ b/libs/utils/energy_model.py @@ -0,0 +1,106 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (C) 2016, ARM Limited and contributors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import logging +from collections import namedtuple, OrderedDict + +import pandas as pd +import numpy as np + +from devlib.utils.misc import memoized + +ActiveState = namedtuple("ActiveState", ["capacity", "power"]) +ActiveState.__new__.__defaults__ = (None, None) + +class EnergyModelNode(namedtuple("EnergyModelNode", + ["cpus", "active_states", "idle_states", + "power_domain", "freq_domain"])): + @property + def max_capacity(self): + return max(s.capacity for s in self.active_states.values()) + + def idle_state_idx(self, state): + return self.idle_states.keys().index(state) + +EnergyModelNode.__new__.__defaults__ = (None, None, None, None, None) + +class PowerDomain(object): + def __init__(self, idle_states, parent, cpus): + self.cpus = set() + self.idle_states = idle_states + + self.parent = parent + self.add_cpus(set(cpus)) + + def add_cpus(self, cpus): + self.cpus = self.cpus.union(cpus) + if self.parent: + self.parent.add_cpus(self.cpus) + + def __repr__(self): + return "PowerDomain(cpus={})".format(list(self.cpus)) + +class EnergyModel(object): + """ + Represents hierarchical CPU topology with power and capacity data + + Describes a CPU topology similarly to trappy's Topology class, additionally + describing relative CPU compute capacity, frequency domains and energy costs + in various configurations. + + The topology is stored in "levels", currently hard-coded to be "cpu" and + "cluster". Each level is a list of EnergyModelNode objects. An EnergyModel + node is a CPU or group of CPUs with associated power and (optionally) + capacity characteristics. + """ + + # TODO check that this is the highest cap available + capacity_scale = 1024 + + def __init__(self, levels=None): + self._levels = levels + + self.num_cpus = len(self._levels["cpu"]) + self.cpus = [n.cpus[0] for n in levels["cpu"]] + if self.cpus != range(self.num_cpus): + raise ValueError("CPUs are sparse or out of order") + if any(len(n.cpus) != 1 for n in levels["cpu"]): + raise ValueError("'cpu' level nodes must all have exactly 1 CPU") + + @property + @memoized + def biggest_cpus(self): + max_cap = max(n.max_capacity for n in self._levels["cpu"]) + return [n.cpus[0] for n in self._levels["cpu"] + if n.max_capacity == max_cap] + + @property + @memoized + def littlest_cpus(self): + min_cap = min(n.max_capacity for n in self._levels["cpu"]) + return [n.cpus[0] for n in self._levels["cpu"] + if n.max_capacity == min_cap] + + + @property + @memoized + def is_heterogeneous(self): + """ + True iff CPUs do not all have the same efficiency and OPP range + """ + states = self._levels["cpu"][0].active_states + return any(c.active_states != states for c in self._levels["cpu"][1:]) diff --git a/tests/lisa/__init__.py b/tests/lisa/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lisa/test_energy_model.py b/tests/lisa/test_energy_model.py new file mode 100644 index 000000000..a072abbf5 --- /dev/null +++ b/tests/lisa/test_energy_model.py @@ -0,0 +1,229 @@ +from collections import OrderedDict +import unittest +from unittest import TestCase + + +from energy_model import (EnergyModel, + ActiveState, EnergyModelNode, PowerDomain) + +little_cluster_active_states = OrderedDict([ + (1000, ActiveState(power=10)), + (2000, ActiveState(power=20)), +]) + +little_cluster_idle_states = OrderedDict([ + ("WFI", 5), + ("cpu-sleep-0", 5), + ("cluster-sleep-0", 1), +]) + +little_cpu_active_states = OrderedDict([ + (1000, ActiveState(capacity=100, power=100)), + (1500, ActiveState(capacity=150, power=150)), + (2000, ActiveState(capacity=200, power=200)), +]) + +little_cpu_idle_states = OrderedDict([ + ("WFI", 5), + ("cpu-sleep-0", 0), + ("cluster-sleep-0", 0), +]) + +littles=[0, 1] +little_pd = PowerDomain(cpus=littles, + idle_states=["cluster-sleep-0"], + parent=None) + +def little_cpu_node(cpu): + cpu_pd = PowerDomain(cpus=[cpu], + idle_states=["WFI", "cpu-sleep-0"], + parent=little_pd) + + return EnergyModelNode([cpu], + active_states=little_cpu_active_states, + idle_states=little_cpu_idle_states, + power_domain=cpu_pd, + freq_domain=littles) + +big_cluster_active_states = OrderedDict([ + (3000, ActiveState(power=30)), + (4000, ActiveState(power=40)), +]) + +big_cluster_idle_states = OrderedDict([ + ("WFI", 8), + ("cpu-sleep-0", 8), + ("cluster-sleep-0", 2), +]) + +big_cpu_active_states = OrderedDict([ + (3000, ActiveState(capacity=300, power=300)), + (4000, ActiveState(capacity=400, power=400)), +]) + +big_cpu_idle_states = OrderedDict([ + ("WFI", 9), + ("cpu-sleep-0", 0), + ("cluster-sleep-0", 0), +]) + +bigs=[2, 3] +big_pd = PowerDomain(cpus=bigs, + idle_states=["cluster-sleep-0"], + parent=None) + +def big_cpu_node(cpu): + cpu_pd = PowerDomain(cpus=[cpu], + idle_states=["WFI", "cpu-sleep-0"], + parent=big_pd) + + return EnergyModelNode([cpu], + active_states=big_cpu_active_states, + idle_states=big_cpu_idle_states, + power_domain=cpu_pd, + freq_domain=bigs) + +levels = { + "cluster": [ + EnergyModelNode(cpus=bigs, + active_states=big_cluster_active_states, + idle_states=big_cluster_idle_states), + EnergyModelNode(cpus=littles, + active_states=little_cluster_active_states, + idle_states=little_cluster_idle_states) + ], + "cpu": [little_cpu_node(0), little_cpu_node(1), + big_cpu_node(2), big_cpu_node(3)] +} + +em = EnergyModel(levels=levels) + +@unittest.skip("No worky") +class TestOptimalPlacement(TestCase): + def test_single_small(self): + placements = em.find_optimal_placements({"task1": 1}) + # This will break if the order of the returned list changes, sorry. + self.assertEqual(placements, [{"task1": 0}, {"task1": 1}]) + + def test_single_big(self): + placements = em.find_optimal_placements({"task1": 350}) + # This will break if the order of the returned list changes, sorry. + self.assertEqual(placements, [{"task1": 2}, {"task1": 3}]) + + def test_packing(self): + tasks = {"task" + str(i) : 10 for i in range(5)} + placements = em.find_optimal_placements(tasks) + for placement in placements: + self.assertTrue(all(cpu in [0, 1] for cpu in placement.values())) + + # TODO test overutilized (should return nothing) + +class TestBiggestCpus(TestCase): + def test_biggest_cpus(self): + self.assertEqual(em.biggest_cpus, [2, 3]) + +class TestMaxCap(TestCase): + def test_max_cap(self): + max_caps = [n.max_capacity for n in em.get_level("cpu")] + self.assertEqual(max_caps, [200, 200, 400, 400]) + +class TestEnergyEst(TestCase): + def test_all_overutilized(self): + big_cpu = 400 * 2 + little_cpu = 200 * 2 + big_cluster = 40 + little_cluster = 20 + + total = big_cpu + little_cpu + big_cluster + little_cluster + + power = em.estimate_from_cpu_util([10000] * 4) + exp = { + "power" : total, + (0): { "active": little_cpu, "idle": 0}, + (1): { "active": little_cpu, "idle": 0}, + (2): { "active": big_cpu, "idle": 0}, + (3): { "active": big_cpu, "idle": 0}, + (0, 1): { "active": little_cluster, "idle": 0}, + (2, 3): { "active": big_cluster, "idle": 0} + } + for k, v in power.iteritems(): + self.assertAlmostEqual(v, power[k]) + + def test_all_idle(self): + self.assertEqual(em.estimate_from_cpu_util([0, 0, 0, 0])["power"], + 0 * 4 # CPU power = 0 + + 2 # big cluster power + + 1) # LITTLE cluster power + + def test_one_little_half_lowest(self): + cpu0_util = 100 * 0.5 + self.assertEqual(em.estimate_from_cpu_util([cpu0_util, 0, 0, 0])["power"], + (0.5 * 100) # CPU0 active power + + (0.5 * 5) # CPU0 idle power + + (0.5 * 5) # LITTLE cluster idle power + + (0.5 * 10) # LITTLE cluster active power + + 2) # big cluster power + +class TestIdleIdxs(TestCase): + def test_zero_util_deepest(self): + self.assertEqual(em._guess_idle_idxs([0] * 4), [2] * 4) + + def test_single_cpu_used(self): + states = em._guess_idle_idxs([0, 0, 0, 1]) + self.assertEqual(states, [2, 2, 1, -1]) + + states = em._guess_idle_idxs([0, 1, 0, 0]) + self.assertEqual(states, [1, -1, 2, 2,]) + + def test_all_cpus_used(self): + states = em._guess_idle_idxs([1, 1, 1, 1]) + self.assertEqual(states, [-1] * 4) + + def test_one_cpu_per_cluster(self): + states = em._guess_idle_idxs([0, 1, 0, 1]) + self.assertEqual(states, [1, -1] * 2) + +class TestIdleStates(TestCase): + def test_zero_util_deepest(self): + self.assertEqual(em.guess_idle_states([0] * 4), ["cluster-sleep-0"] * 4) + + def test_single_cpu_used(self): + states = em.guess_idle_states([0, 0, 0, 1]) + self.assertEqual(states, ["cluster-sleep-0", "cluster-sleep-0", + "cpu-sleep-0", "WFI"]) + + states = em.guess_idle_states([0, 1, 0, 0]) + self.assertEqual(states, ["cpu-sleep-0", "WFI", + "cluster-sleep-0", "cluster-sleep-0",]) + + def test_all_cpus_used(self): + states = em.guess_idle_states([1, 1, 1, 1]) + self.assertEqual(states, ["WFI"] * 4) + + def test_one_cpu_per_cluster(self): + states = em.guess_idle_states([0, 1, 0, 1]) + self.assertEqual(states, ["cpu-sleep-0", "WFI"] * 2) + +class TestFreqs(TestCase): + + def test_zero_util_slowest(self): + self.assertEqual(em.guess_freqs([0] * 4), + [1000, 1000, 3000, 3000]) + + def test_high_util_fastest(self): + self.assertEqual(em.guess_freqs([100000] * 4), + [2000, 2000, 4000, 4000]) + + def test_freq_domains(self): + self.assertEqual(em.guess_freqs([0, 0, 0, 10000]), + [1000, 1000, 4000, 4000]) + + self.assertEqual(em.guess_freqs([0, 10000, 0, 10000]), + [2000, 2000, 4000, 4000]) + + self.assertEqual(em.guess_freqs([0, 10000, 0, 0]), + [2000, 2000, 3000, 3000]) + + def test_middle_freq(self): + self.assertEqual(em.guess_freqs([0, 110, 0, 0]), + [1500, 1500, 3000, 3000]) -- GitLab From d82c7a723ccdff257cb1dac7ce8b4ab380218f1f Mon Sep 17 00:00:00 2001 From: Brendan Jackman Date: Wed, 2 Nov 2016 16:40:23 +0000 Subject: [PATCH 02/10] libs/utils/platforms: Add __init__.py --- libs/utils/platforms/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 libs/utils/platforms/__init__.py diff --git a/libs/utils/platforms/__init__.py b/libs/utils/platforms/__init__.py new file mode 100644 index 000000000..e69de29bb -- GitLab From 9c8d162df4df342e3be4a736e1e39f98f3ba5ba0 Mon Sep 17 00:00:00 2001 From: Brendan Jackman Date: Wed, 2 Nov 2016 16:40:58 +0000 Subject: [PATCH 03/10] platforms: Add energy model for Juno r0 --- libs/utils/env.py | 2 + libs/utils/platforms/juno_energy.py | 109 ++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 libs/utils/platforms/juno_energy.py diff --git a/libs/utils/env.py b/libs/utils/env.py index 79c49282d..376630db6 100644 --- a/libs/utils/env.py +++ b/libs/utils/env.py @@ -27,6 +27,7 @@ import unittest import devlib +import platforms.juno_energy from wlgen import RTA from energy import EnergyMeter from conf import JsonConf @@ -371,6 +372,7 @@ class TestEnv(ShareState): # Initialize JUNO board elif self.conf['board'].upper() in ('JUNO', 'JUNO2'): platform = devlib.platform.arm.Juno() + self.nrg_model = platforms.juno_energy.juno_energy self.__modules = ['bl', 'hwmon', 'cpufreq'] # Initialize OAK board diff --git a/libs/utils/platforms/juno_energy.py b/libs/utils/platforms/juno_energy.py new file mode 100644 index 000000000..203279435 --- /dev/null +++ b/libs/utils/platforms/juno_energy.py @@ -0,0 +1,109 @@ +from energy_model import ActiveState, EnergyModelNode, PowerDomain, EnergyModel + +from collections import OrderedDict + +a53_cluster_active_states = OrderedDict([ + (450000, ActiveState(power=26)), + (575000, ActiveState(power=30)), + (700000, ActiveState(power=39)), + (775000, ActiveState(power=47)), + (850000, ActiveState(power=57)), +]) + +# TODO warn if any of the idle states aren't represented by power domains +a53_cluster_idle_states = OrderedDict([ + ("WFI", 56), + ("cpu-sleep-0", 56), + ("cluster-sleep-0", 17), +]) + +a53_cpu_active_states = OrderedDict([ + (450000, ActiveState(capacity=235, power=33)), + (575000, ActiveState(capacity=302, power=46)), + (700000, ActiveState(capacity=368, power=61)), + (775000, ActiveState(capacity=406, power=76)), + (850000, ActiveState(capacity=447, power=93)), +]) + +a53_cpu_idle_states = OrderedDict([ + ("WFI", 6), + ("cpu-sleep-0", 0), + ("cluster-sleep-0", 0), +]) + +a53s = [0, 3, 4, 5] +a53_pd = PowerDomain(cpus=a53s, idle_states=["cluster-sleep-0"], parent=None) + +def a53_cpu_node(cpu): + cpu_pd=PowerDomain(cpus=[cpu], + parent=a53_pd, + idle_states=["WFI", "cpu-sleep-0"]) + + return EnergyModelNode([cpu], + active_states=a53_cpu_active_states, + idle_states=a53_cpu_idle_states, + power_domain=cpu_pd, + freq_domain=a53s) + +a57_cluster_active_states = OrderedDict([ + (450000, ActiveState(power=24)), + (625000, ActiveState(power=32)), + (800000, ActiveState(power=43)), + (950000, ActiveState(power=49)), + (1100000, ActiveState(power=64)), +]) + +a57_cluster_idle_states = OrderedDict([ + ("WFI", 65), + ("cpu-sleep-0", 65), + ("cluster-sleep-0", 24), +]) + +a57_cpu_active_states = OrderedDict([ + (450000, ActiveState(capacity=417, power=168)), + (625000, ActiveState(capacity=579, power=251)), + (800000, ActiveState(capacity=744, power=359)), + (950000, ActiveState(capacity=883, power=479)), + (1100000, ActiveState(capacity=1023, power=616)), +]) + +a57_cpu_idle_states = OrderedDict([ + ("WFI", 15), + ("cpu-sleep-0", 0), + ("cluster-sleep-0", 0), +]) + +a57s = [1, 2] +a57_pd = PowerDomain(cpus=a57s, idle_states=["cluster-sleep-0"], parent=None) + +def a57_cpu_node(cpu): + cpu_pd = PowerDomain(cpus=[cpu], + parent=a57_pd, + idle_states=["WFI", "cpu-sleep-0"]) + + return EnergyModelNode([cpu], + active_states=a57_cpu_active_states, + idle_states=a57_cpu_idle_states, + power_domain=cpu_pd, + freq_domain=a57s) + +juno_energy_levels = { + 'cluster': [ + EnergyModelNode(cpus=[0, 3, 4, 5], + active_states=a53_cluster_active_states, + idle_states=a53_cluster_idle_states), + EnergyModelNode(cpus=[1, 2], + active_states=a57_cluster_active_states, + idle_states=a57_cluster_idle_states) + ], + 'cpu': [ + a53_cpu_node(0), + a57_cpu_node(1), + a57_cpu_node(2), + a53_cpu_node(3), + a53_cpu_node(4), + a53_cpu_node(5) + ] +} + +juno_energy = EnergyModel(levels=juno_energy_levels) -- GitLab From 977327868a0513eb55c53084091476dc010a4d24 Mon Sep 17 00:00:00 2001 From: Brendan Jackman Date: Wed, 2 Nov 2016 16:40:35 +0000 Subject: [PATCH 04/10] platforms: Add energy model for HiKey --- libs/utils/env.py | 7 ++++ libs/utils/platforms/hikey_energy.py | 58 ++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 libs/utils/platforms/hikey_energy.py diff --git a/libs/utils/env.py b/libs/utils/env.py index 376630db6..825e9e802 100644 --- a/libs/utils/env.py +++ b/libs/utils/env.py @@ -28,6 +28,7 @@ import unittest import devlib import platforms.juno_energy +import platforms.hikey_energy from wlgen import RTA from energy import EnergyMeter from conf import JsonConf @@ -380,6 +381,12 @@ class TestEnv(ShareState): platform = Platform(model='MT8173') self.__modules = ['bl', 'cpufreq'] + + elif self.conf['board'].upper() == 'HIKEY': + self.nrg_model = platforms.hikey_energy.hikey_energy + self.__modules = [ "cpufreq", "cpuidle" ] + platform = Platform(model='hikey') + elif self.conf['board'] != 'UNKNOWN': # Initilize from platform descriptor (if available) board = self._load_board(self.conf['board']) diff --git a/libs/utils/platforms/hikey_energy.py b/libs/utils/platforms/hikey_energy.py new file mode 100644 index 000000000..c39e56297 --- /dev/null +++ b/libs/utils/platforms/hikey_energy.py @@ -0,0 +1,58 @@ +from energy_model import ActiveState, EnergyModelNode, PowerDomain, EnergyModel + +from collections import OrderedDict + +cluster_active_states = OrderedDict([ + (208000, ActiveState(capacity=178, power=16)), + (432000, ActiveState(capacity=369, power=29)), + (729000, ActiveState(capacity=622, power=47)), + (960000, ActiveState(capacity=819, power=75)), + (1200000, ActiveState(capacity=1024, power=112)) +]) + +cluster_idle_states = OrderedDict([ + ('WFI', 107), + ('cpu-sleep', 47), + ('cluster-sleep', 0) +]) + +cpu_active_states = OrderedDict([ + (208000, ActiveState(capacity=178, power=69)), + (432000, ActiveState(capacity=369, power=125)), + (729000, ActiveState(capacity=622, power=224)), + (960000, ActiveState(capacity=819, power=367)), + (1200000, ActiveState(capacity=1024, power=670)) +]) + +cpu_idle_states = OrderedDict([('WFI', 15), ('cpu-sleep', 0), ('cluster-sleep', 0)]) + +cpus = range(8) + +cluster_pds = [ + PowerDomain(cpus=[0, 1, 2, 3], idle_states=["cluster-sleep"], parent=None), + PowerDomain(cpus=[4, 5, 6, 7], idle_states=["cluster-sleep"], parent=None), +] + +def cpu_node(cpu): + cpu_pd=PowerDomain(cpus=[cpu], + parent=cluster_pds[cpu / 4], + idle_states=["WFI", "cpu-sleep"]) + + return EnergyModelNode([cpu], + active_states=cpu_active_states, + idle_states=cpu_idle_states, + power_domain=cpu_pd, + freq_domain=cpus) +hikey_energy_levels = { + 'cluster': [ + EnergyModelNode(cpus=[0, 1, 2, 3], + active_states=cluster_active_states, + idle_states=cluster_idle_states), + EnergyModelNode(cpus=[4, 5, 6, 7], + active_states=cluster_active_states, + idle_states=cluster_idle_states) + ], + 'cpu': [cpu_node(c) for c in cpus] +} + +hikey_energy = EnergyModel(levels=hikey_energy_levels) -- GitLab From 3c1623c1b069a982724c04bfa4d53a215d96d5e1 Mon Sep 17 00:00:00 2001 From: Brendan Jackman Date: Fri, 18 Nov 2016 15:03:50 +0000 Subject: [PATCH 05/10] platforms: add energy model for Pixel --- libs/utils/env.py | 8 ++ libs/utils/platforms/pixel_energy.py | 187 +++++++++++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 libs/utils/platforms/pixel_energy.py diff --git a/libs/utils/env.py b/libs/utils/env.py index 825e9e802..c68615bf9 100644 --- a/libs/utils/env.py +++ b/libs/utils/env.py @@ -29,6 +29,8 @@ import devlib import platforms.juno_energy import platforms.hikey_energy +import platforms.pixel_energy + from wlgen import RTA from energy import EnergyMeter from conf import JsonConf @@ -387,6 +389,12 @@ class TestEnv(ShareState): self.__modules = [ "cpufreq", "cpuidle" ] platform = Platform(model='hikey') + # Initialize Pixel phone + elif self.conf['board'].upper() == 'PIXEL': + self.nrg_model = platforms.pixel_energy.pixel_energy + self.__modules = ['hwmon', 'cpufreq'] + platform = Platform(model='pixel') + elif self.conf['board'] != 'UNKNOWN': # Initilize from platform descriptor (if available) board = self._load_board(self.conf['board']) diff --git a/libs/utils/platforms/pixel_energy.py b/libs/utils/platforms/pixel_energy.py new file mode 100644 index 000000000..ebf4c0eea --- /dev/null +++ b/libs/utils/platforms/pixel_energy.py @@ -0,0 +1,187 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (C) 2016, ARM Limited and contributors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from energy_model import ActiveState, EnergyModelNode, PowerDomain, EnergyModel + +from collections import OrderedDict + +silver_cpu_active_states = OrderedDict([ + (307200, ActiveState(capacity=149, power=90)), + (384000, ActiveState(capacity=188, power=111)), + (460800, ActiveState(capacity=225, power=133)), + (537600, ActiveState(capacity=257, power=160)), + (614400, ActiveState(capacity=281, power=182)), + (691200, ActiveState(capacity=315, power=210)), + (768000, ActiveState(capacity=368, power=251)), + (844800, ActiveState(capacity=406, power=306)), + (902400, ActiveState(capacity=428, power=332)), + (979200, ActiveState(capacity=469, power=379)), + (1056000, ActiveState(capacity=502, power=438)), + (1132800, ActiveState(capacity=538, power=494)), + (1209600, ActiveState(capacity=581, power=550)), + (1286400, ActiveState(capacity=611, power=613)), + (1363200, ActiveState(capacity=648, power=670)), + (1440000, ActiveState(capacity=684, power=752)), + (1516800, ActiveState(capacity=729, power=848)), + (1593600, ActiveState(capacity=763, power=925)), +]) + +silver_cluster_active_states = OrderedDict([ + (307200, ActiveState(power=4)), + (384000, ActiveState(power=4)), + (460800, ActiveState(power=4)), + (537600, ActiveState(power=4)), + (614400, ActiveState(power=4)), + (691200, ActiveState(power=4)), + (768000, ActiveState(power=8)), + (844800, ActiveState(power=9)), + (902400, ActiveState(power=15)), + (979200, ActiveState(power=16)), + (1056000, ActiveState(power=21)), + (1132800, ActiveState(power=22)), + (1209600, ActiveState(power=29)), + (1286400, ActiveState(power=32)), + (1363200, ActiveState(power=42)), + (1440000, ActiveState(power=49)), + (1516800, ActiveState(power=41)), + (1593600, ActiveState(power=52)), +]) + +gold_cpu_active_states = OrderedDict([ + (307200, ActiveState(capacity=149, power=93)), + (384000, ActiveState(capacity=188, power=111)), + (460800, ActiveState(capacity=225, power=133)), + (537600, ActiveState(capacity=257, power=160)), + (614400, ActiveState(capacity=281, power=182)), + (691200, ActiveState(capacity=315, power=210)), + (748800, ActiveState(capacity=348, power=252)), + (825600, ActiveState(capacity=374, power=290)), + (902400, ActiveState(capacity=428, power=332)), + (979200, ActiveState(capacity=469, power=379)), + (1056000, ActiveState(capacity=502, power=438)), + (1132800, ActiveState(capacity=538, power=494)), + (1209600, ActiveState(capacity=581, power=550)), + (1286400, ActiveState(capacity=611, power=613)), + (1363200, ActiveState(capacity=648, power=670)), + (1440000, ActiveState(capacity=684, power=752)), + (1516800, ActiveState(capacity=729, power=848)), + (1593600, ActiveState(capacity=763, power=925)), + (1670400, ActiveState(capacity=795, power=1018)), + (1747200, ActiveState(capacity=832, power=1073)), + (1824000, ActiveState(capacity=868, power=1209)), + (1900800, ActiveState(capacity=905, power=1298)), + (1977600, ActiveState(capacity=952, power=1428)), + (2054400, ActiveState(capacity=979, power=1521)), + (2150400, ActiveState(capacity=1024, power=1715)), +]) + +gold_cluster_active_states = OrderedDict([ +(307200, ActiveState(power=4)), +(384000, ActiveState(power=4)), +(460800, ActiveState(power=4)), +(537600, ActiveState(power=4)), +(614400, ActiveState(power=4)), +(691200, ActiveState(power=4)), +(748800, ActiveState(power=7)), +(825600, ActiveState(power=10)), +(902400, ActiveState(power=15)), +(979200, ActiveState(power=16)), +(1056000, ActiveState(power=21)), +(1132800, ActiveState(power=22)), +(1209600, ActiveState(power=29)), +(1286400, ActiveState(power=32)), +(1363200, ActiveState(power=42)), +(1440000, ActiveState(power=49)), +(1516800, ActiveState(power=41)), +(1593600, ActiveState(power=52)), +(1670400, ActiveState(power=62)), +(1747200, ActiveState(power=69)), +(1824000, ActiveState(power=75)), +(1900800, ActiveState(power=81)), +(1977600, ActiveState(power=90)), +(2054400, ActiveState(power=93)), +(2150400, ActiveState(power=96)), +]) + +a53_cluster_active_states = OrderedDict([ + (450000, ActiveState(power=26)), + (575000, ActiveState(power=30)), + (700000, ActiveState(power=39)), + (775000, ActiveState(power=47)), + (850000, ActiveState(power=57)), +]) + +# TODO warn if any of the idle states aren't represented by power domains +cpu_idle_states = OrderedDict([ + ("WFI", 2), + ("cpu-sleep-0", 0), + ("cluster-sleep-0", 0), +]) + +cluster_idle_states = OrderedDict([ + ("WFI", 0), + ("cpu-sleep-0", 0), + ("cluster-sleep-0", 0), +]) + +silvers = [0, 1] +golds = [2, 3] +silver_pd = PowerDomain(cpus=silvers, idle_states=["cluster-sleep-0"], + parent=None) +gold_pd = PowerDomain(cpus=golds, idle_states=["cluster-sleep-0"], + parent=None) + +def silver_cpu_node(cpu): + cpu_pd=PowerDomain(cpus=[cpu], + parent=silver_pd, + idle_states=["WFI", "cpu-sleep-0"]) + + return EnergyModelNode([cpu], + active_states=silver_cpu_active_states, + idle_states=cpu_idle_states, + power_domain=cpu_pd, + freq_domain=silvers) + +def gold_cpu_node(cpu): + cpu_pd=PowerDomain(cpus=[cpu], + parent=gold_pd, + idle_states=["WFI", "cpu-sleep-0"]) + + return EnergyModelNode([cpu], + active_states=gold_cpu_active_states, + idle_states=cpu_idle_states, + power_domain=cpu_pd, + freq_domain=golds) + +levels = { + 'cluster': [ + EnergyModelNode(cpus=silvers, + active_states=silver_cpu_active_states, + idle_states=cpu_idle_states), + EnergyModelNode(cpus=golds, + active_states=gold_cpu_active_states, + idle_states=cpu_idle_states), + ], + 'cpu': [ + silver_cpu_node(0), + silver_cpu_node(1), + gold_cpu_node(2), + gold_cpu_node(3), + ] +} + +pixel_energy = EnergyModel(levels=levels) -- GitLab From 202d589fafbb0467d7e455c550d0620e9fc93d1c Mon Sep 17 00:00:00 2001 From: Brendan Jackman Date: Wed, 2 Nov 2016 17:26:24 +0000 Subject: [PATCH 06/10] tests/eas/acceptance: Remove `bl` reference in first CPU test --- tests/eas/acceptance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/eas/acceptance.py b/tests/eas/acceptance.py index 218bbf219..cf48c4f0a 100644 --- a/tests/eas/acceptance.py +++ b/tests/eas/acceptance.py @@ -93,7 +93,7 @@ class EasTest(LisaTest): self.assertTrue( sched_assert.assertFirstCpu( - self.target.bl.bigs, + self.te.nrg_model.biggest_cpus, rank=len(tasks)), msg="Not all the new generated tasks started on a big CPU") -- GitLab From dd505f98bc34788f3e7819b774926fc5f19b559a Mon Sep 17 00:00:00 2001 From: Brendan Jackman Date: Tue, 15 Nov 2016 18:23:20 +0000 Subject: [PATCH 07/10] tests/eas/acceptance: Remove big.LITTLE assumption from SmallTaskPacking --- tests/eas/acceptance.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/tests/eas/acceptance.py b/tests/eas/acceptance.py index cf48c4f0a..a64f67d97 100644 --- a/tests/eas/acceptance.py +++ b/tests/eas/acceptance.py @@ -152,7 +152,7 @@ class SmallTaskPacking(EasTest): Goal ==== - Many small tasks are packed in little cpus + Many small tasks are packed on a single cluster with the lowest capacity Detailed Description ==================== @@ -164,7 +164,7 @@ class SmallTaskPacking(EasTest): Expected Behaviour ================== - All tasks run on little cpus. + All tasks run on little cpus and are packed into a single cluster. """ experiments_conf = { @@ -193,20 +193,24 @@ class SmallTaskPacking(EasTest): @experiment_test def test_small_task_residency(self, experiment, tasks): - "Small Task Packing: Test Residency (Little Cluster)" + "Small Task Packing: Test Residency" sched_assert = self.get_multi_assert(experiment) - self.assertTrue( - sched_assert.assertResidency( - "cluster", - self.target.bl.littles, - EXPECTED_RESIDENCY_PCT, - operator.ge, - percent=True, - rank=len(tasks)), - msg="Not all tasks are running on LITTLE cores for at least {}% of their execution time"\ - .format(EXPECTED_RESIDENCY_PCT)) + littlest_cpus = self.te.nrg_model.littlest_cpus + packed_onto_cluster = [] + + for cpus in self.te.topology.get_level("cluster"): + if all(c in littlest_cpus for c in cpus): + if sched_assert.assertResidency( + "cluster", cpus, + EXPECTED_RESIDENCY_PCT, operator.ge, + percent=True, rank=len(tasks)): + return + + msg = "Not all tasks ran on low-capacity cluster for {}% of their time"\ + .format(EXPECTED_RESIDENCY_PCT) + raise AssertionError(msg) class OffloadMigrationAndIdlePull(EasTest): """ -- GitLab From b92d95c4c59a95d79af9c5cb2726ab5ee3a8dab0 Mon Sep 17 00:00:00 2001 From: Brendan Jackman Date: Tue, 15 Nov 2016 18:31:57 +0000 Subject: [PATCH 08/10] tests/eas/acceptance: Add skip_on_smp flag --- tests/eas/acceptance.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/eas/acceptance.py b/tests/eas/acceptance.py index a64f67d97..3ce631093 100644 --- a/tests/eas/acceptance.py +++ b/tests/eas/acceptance.py @@ -20,6 +20,7 @@ import operator import os import trappy import unittest +from unittest import SkipTest from bart.sched.SchedAssert import SchedAssert @@ -66,10 +67,19 @@ class EasTest(LisaTest): }, } + # Set to true to run a test only on heterogeneous systems + skip_on_smp = False + @classmethod def setUpClass(cls, *args, **kwargs): super(EasTest, cls)._init(*args, **kwargs) + @classmethod + def _getExperimentsConf(cls, test_env): + if cls.skip_on_smp and not test_env.nrg_model.is_heterogeneous: + raise SkipTest("Test not required on symmetric systems") + return super(EasTest, cls)._getExperimentsConf(test_env) + @classmethod def _experimentsInit(cls, *args, **kwargs): super(EasTest, cls)._experimentsInit(*args, **kwargs) @@ -116,6 +126,8 @@ class ForkMigration(EasTest): The threads start on a big core. """ + skip_on_smp = True + experiments_conf = { "wloads" : { # Create N 100% tasks and M 10% tasks which run in parallel, where N @@ -261,6 +273,8 @@ class OffloadMigrationAndIdlePull(EasTest): """ + skip_on_smp = True + experiments_conf = { "wloads" : { "early_and_migrators" : { @@ -423,6 +437,8 @@ class WakeMigration(EasTest): the big cpus when they are big. """ + skip_on_smp = True + experiments_conf = { "wloads" : { "wake_migration" : { -- GitLab From 2c7eb622e3ed13f3ee363f2cac31003afb6d5686 Mon Sep 17 00:00:00 2001 From: Brendan Jackman Date: Tue, 15 Nov 2016 18:38:09 +0000 Subject: [PATCH 09/10] tests/eas/acceptance: Only check early tasks in Offoad+IdlePull The migrators probably won't start on big CPUs. --- tests/eas/acceptance.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/eas/acceptance.py b/tests/eas/acceptance.py index 3ce631093..7dd0992cf 100644 --- a/tests/eas/acceptance.py +++ b/tests/eas/acceptance.py @@ -23,6 +23,7 @@ import unittest from unittest import SkipTest from bart.sched.SchedAssert import SchedAssert +from bart.sched.SchedMultiAssert import SchedMultiAssert from devlib.target import TargetError @@ -99,7 +100,8 @@ class EasTest(LisaTest): def _do_test_first_cpu(self, experiment, tasks): """Test that all tasks start on a big CPU""" - sched_assert = self.get_multi_assert(experiment) + sched_assert = SchedMultiAssert( + experiment.out_dir, self.te.topology, tasks) self.assertTrue( sched_assert.assertFirstCpu( @@ -313,7 +315,7 @@ class OffloadMigrationAndIdlePull(EasTest): @experiment_test def test_first_cpu(self, experiment, tasks): """Offload Migration and Idle Pull: Test First CPU""" - self._do_test_first_cpu(experiment, tasks) + self._do_test_first_cpu(experiment, [t for t in tasks if "early" in t]) @experiment_test def test_big_cpus_fully_loaded(self, experiment, tasks): -- GitLab From a833aa16021c29c3700f4294be14e5c7be297c73 Mon Sep 17 00:00:00 2001 From: Brendan Jackman Date: Tue, 15 Nov 2016 18:52:25 +0000 Subject: [PATCH 10/10] tests/eas/acceptance: Remove big.LITTLE assumption for Offload+IdlePull --- tests/eas/acceptance.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/tests/eas/acceptance.py b/tests/eas/acceptance.py index 7dd0992cf..3f9c080a8 100644 --- a/tests/eas/acceptance.py +++ b/tests/eas/acceptance.py @@ -320,7 +320,8 @@ class OffloadMigrationAndIdlePull(EasTest): @experiment_test def test_big_cpus_fully_loaded(self, experiment, tasks): """Offload Migration and Idle Pull: Big cpus are fully loaded as long as there are tasks left to run in the system""" - num_big_cpus = len(self.target.bl.bigs) + bigs = self.te.nrg_model.biggest_cpus + num_big_cpus = len(bigs) sched_assert = self.get_multi_assert(experiment) @@ -328,9 +329,8 @@ class OffloadMigrationAndIdlePull(EasTest): # Window of time until the first migrator finishes window = (self.get_start_time(experiment), end_times[-num_big_cpus]) - busy_time = sched_assert.getCPUBusyTime("cluster", - self.target.bl.bigs, - window=window, percent=True) + busy_time = sched_assert.getCPUBusyTime("cluster", bigs, + window=window, percent=True) msg = "Big cpus were not fully loaded while there were enough big tasks to fill them" self.assertGreater(busy_time, OFFLOAD_EXPECTED_BUSY_TIME_PCT, msg=msg) @@ -340,8 +340,7 @@ class OffloadMigrationAndIdlePull(EasTest): for i in range(num_big_cpus-1): big_cpus_left = num_big_cpus - i - 1 window = (end_times[-num_big_cpus+i], end_times[-num_big_cpus+i+1]) - busy_time = sched_assert.getCPUBusyTime("cluster", - self.target.bl.bigs, + busy_time = sched_assert.getCPUBusyTime("cluster", bigs, window=window, percent=True) expected_busy_time = OFFLOAD_EXPECTED_BUSY_TIME_PCT * \ @@ -354,6 +353,7 @@ class OffloadMigrationAndIdlePull(EasTest): @experiment_test def test_little_cpus_run_tasks(self, experiment, tasks): """Offload Migration and Idle Pull: Little cpus run tasks while bigs are busy""" + littles = self.te.nrg_model.littlest_cpus num_offloaded_tasks = len(tasks) / 2 @@ -369,8 +369,7 @@ class OffloadMigrationAndIdlePull(EasTest): all_tasks_assert = self.get_multi_assert(experiment) - busy_time = all_tasks_assert.getCPUBusyTime("cluster", - self.target.bl.littles, + busy_time = all_tasks_assert.getCPUBusyTime("cluster", littles, window=window) window_len = window[1] - window[0] @@ -392,7 +391,8 @@ class OffloadMigrationAndIdlePull(EasTest): sa = SchedAssert(experiment.out_dir, self.te.topology, execname=task) end_times = self.get_end_times(experiment) window = (0, end_times[task]) - big_residency = sa.getResidency("cluster", self.target.bl.bigs, + big_residency = sa.getResidency("cluster", + self.te.nrg_model.biggest_cpus, window=window, percent=True) msg = "Task {} didn't run on a big cpu.".format(task) @@ -412,7 +412,10 @@ class OffloadMigrationAndIdlePull(EasTest): sa = SchedAssert(experiment.out_dir, self.te.topology, execname=task) msg = "Task {} did not finish on a big cpu".format(task) - self.assertIn(sa.getLastCpu(), self.target.bl.bigs, msg=msg) + + self.assertIn(sa.getLastCpu(), + self.te.nrg_model.biggest_cpus, + msg=msg) class WakeMigration(EasTest): -- GitLab