diff --git a/libs/utils/energy_model.py b/libs/utils/energy_model.py new file mode 100644 index 0000000000000000000000000000000000000000..147a6b54e5090c72f66bf172a69f5987401189fb --- /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/libs/utils/env.py b/libs/utils/env.py index 79c49282d7097153152d845924c12bf3194f29d7..c68615bf9f3301e2fd7fa4a2765b61d55e278a6a 100644 --- a/libs/utils/env.py +++ b/libs/utils/env.py @@ -27,6 +27,10 @@ import unittest 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 @@ -371,6 +375,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 @@ -378,6 +383,18 @@ 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') + + # 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/__init__.py b/libs/utils/platforms/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/libs/utils/platforms/hikey_energy.py b/libs/utils/platforms/hikey_energy.py new file mode 100644 index 0000000000000000000000000000000000000000..c39e562970e08739f113f62738f8b0c87e34402e --- /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) diff --git a/libs/utils/platforms/juno_energy.py b/libs/utils/platforms/juno_energy.py new file mode 100644 index 0000000000000000000000000000000000000000..20327943592a6d1995fdc1497afb139c89e19ccb --- /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) diff --git a/libs/utils/platforms/pixel_energy.py b/libs/utils/platforms/pixel_energy.py new file mode 100644 index 0000000000000000000000000000000000000000..ebf4c0eea9665a8271815892fb7327989dfdfed6 --- /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) diff --git a/tests/eas/acceptance.py b/tests/eas/acceptance.py index 218bbf2190c100f260857dafc0af71a3f2165b51..3f9c080a87617539a0e1d0b229153a90bf322e3f 100644 --- a/tests/eas/acceptance.py +++ b/tests/eas/acceptance.py @@ -20,8 +20,10 @@ import operator import os import trappy import unittest +from unittest import SkipTest from bart.sched.SchedAssert import SchedAssert +from bart.sched.SchedMultiAssert import SchedMultiAssert from devlib.target import TargetError @@ -66,10 +68,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) @@ -89,11 +100,12 @@ 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( - 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") @@ -116,6 +128,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 @@ -152,7 +166,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 +178,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 +207,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): """ @@ -257,6 +275,8 @@ class OffloadMigrationAndIdlePull(EasTest): """ + skip_on_smp = True + experiments_conf = { "wloads" : { "early_and_migrators" : { @@ -295,12 +315,13 @@ 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): """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) @@ -308,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) @@ -320,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 * \ @@ -334,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 @@ -349,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] @@ -372,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) @@ -392,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): @@ -419,6 +442,8 @@ class WakeMigration(EasTest): the big cpus when they are big. """ + skip_on_smp = True + experiments_conf = { "wloads" : { "wake_migration" : { diff --git a/tests/lisa/__init__.py b/tests/lisa/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/lisa/test_energy_model.py b/tests/lisa/test_energy_model.py new file mode 100644 index 0000000000000000000000000000000000000000..a072abbf5a5efb2f7643b12d8d69f490aea0d1a8 --- /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])