diff --git a/libs/utils/platforms/hikey960_energy.py b/libs/utils/platforms/hikey960_energy.py new file mode 100644 index 0000000000000000000000000000000000000000..382a8398ca4d4a6b00bff4aac2367c0a3aa3af46 --- /dev/null +++ b/libs/utils/platforms/hikey960_energy.py @@ -0,0 +1,115 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (C) 2018, 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 collections import OrderedDict + +from energy_model import (ActiveState, EnergyModelNode, EnergyModelRoot, + PowerDomain, EnergyModel) + +a53_cluster_active_states = OrderedDict([ + (533000, ActiveState(power=12)), + (999000, ActiveState(power=22)), + (1402000, ActiveState(power=36)), + (1709000, ActiveState(power=67)), + (1844000, ActiveState(power=144)), +]) + +# TODO warn if any of the idle states aren't represented by power domains +a53_cluster_idle_states = OrderedDict([ + ("WFI", 12), + ("cpu-sleep-0", 12), + ("cluster-sleep-0", 0), +]) + +a53_cpu_active_states = OrderedDict([ + (533000, ActiveState(capacity=133, power=87)), + (999000, ActiveState(capacity=250, power=164)), + (1402000, ActiveState(capacity=351, power=265)), + (1709000, ActiveState(capacity=429, power=388)), + (1844000, ActiveState(capacity=462, power=502)), +]) + +a53_cpu_idle_states = OrderedDict([ + ("WFI", 5), + ("cpu-sleep-0", 0), + ("cluster-sleep-0", 0), +]) + +a53s = [0, 1, 2, 3] + +def a53_cpu_node(cpu): + return EnergyModelNode(cpu=cpu, + active_states=a53_cpu_active_states, + idle_states=a53_cpu_idle_states) + +a73_cluster_active_states = OrderedDict([ + (903000, ActiveState(power=102)), + (1421000, ActiveState(power=124)), + (1805000, ActiveState(power=221)), + (2112000, ActiveState(power=330)), + (2362000, ActiveState(power=433)), +]) + +a73_cluster_idle_states = OrderedDict([ + ("WFI", 102), + ("cpu-sleep-0", 102), + ("cluster-sleep-0", 0), +]) + +a73_cpu_active_states = OrderedDict([ + (903000, ActiveState(capacity=390, power=404)), + (1421000, ActiveState(capacity=615, power=861)), + (1805000, ActiveState(capacity=782, power=1398)), + (2112000, ActiveState(capacity=915, power=2200)), + (2362000, ActiveState(capacity=1024, power=2848)), +]) + +a73_cpu_idle_states = OrderedDict([ + ("WFI", 18), + ("cpu-sleep-0", 0), + ("cluster-sleep-0", 0), +]) + +a73s = [4, 5, 6, 7] + +def a73_cpu_node(cpu): + return EnergyModelNode(cpu=cpu, + active_states=a73_cpu_active_states, + idle_states=a73_cpu_idle_states) + +hikey960_energy = EnergyModel( + root_node=EnergyModelRoot( + children=[ + EnergyModelNode( + name="cluster_a53", + active_states=a53_cluster_active_states, + idle_states=a53_cluster_idle_states, + children=[a53_cpu_node(c) for c in a53s]), + EnergyModelNode( + name="cluster_a73", + active_states=a73_cluster_active_states, + idle_states=a73_cluster_idle_states, + children=[a73_cpu_node(c) for c in a73s])]), + root_power_domain=PowerDomain(idle_states=[], children=[ + PowerDomain( + idle_states=["cluster-sleep-0"], + children=[PowerDomain(idle_states=["WFI", "cpu-sleep-0"], cpu=c) + for c in a73s]), + PowerDomain( + idle_states=["cluster-sleep-0"], + children=[PowerDomain(idle_states=["WFI", "cpu-sleep-0"], cpu=c) + for c in a53s])]), + freq_domains=[a53s, a73s]) diff --git a/tests/eas/energy_model.py b/tests/eas/energy_model.py new file mode 100644 index 0000000000000000000000000000000000000000..484a327a0f5693a1f63b129715cef4323c2a6e8e --- /dev/null +++ b/tests/eas/energy_model.py @@ -0,0 +1,174 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (C) 2018, 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 +import tests.utils.em as em + +from unittest import TestCase +from env import TestEnv +from test import LisaTest + +from libs.utils.platforms.juno_r0_energy import juno_r0_energy +from libs.utils.platforms.pixel_energy import pixel_energy +from libs.utils.platforms.hikey_energy import hikey_energy +from libs.utils.platforms.hikey960_energy import hikey960_energy + +class TestStaticEnergyModel(TestCase): + """ + This test iterates over all the static energy model to sanity check them + and reports any anomalies/irregularities observed. + """ + energy_model = [('juno_r0', juno_r0_energy), + ('hikey', hikey_energy), + ('hikey960', hikey960_energy), + ('pixel',pixel_energy)] + + def test_is_active_states_coherent(self): + """Test coherency of the active states""" + tests_report = '' + tests_succeed = True + for (name, model) in self.energy_model: + (succeed, msg) = em.is_power_increasing(model) + if not succeed: + tests_succeed = False + tests_report += 'In platform {}: {}\n\n\t\t'.format(name, msg) + + (succeed, msg) = em.is_efficiency_decreasing(model) + if not succeed: + tests_succeed = False + tests_report += 'In platform {}: {}\n\n\t\t'.format(name, msg) + + self.assertTrue(tests_succeed, tests_report) + + def test_get_opp_overutilized(self): + """Get opp that are in overutilized zone""" + for (name, model) in self.energy_model: + opp_overutilized = em.get_opp_overutilized(model) + msg = "\n" + for i, opp in enumerate(opp_overutilized): + msg += "\tGroup {}: {}\n".format(i, opp[1]) + logging.info('\nFor platform {}: {}'.format(name, msg)) + + def test_get_avg_opp_per_group(self): + """ + Get average workload that can be run on each cpus group + """ + for (name, model) in self.energy_model: + avg_load = em.get_avg_cap(model) + msg = "\n" + for i, load in enumerate(avg_load): + msg += "\tGroup {}: {}\n".format(i, load) + logging.info('\nFor platform {}: {}'.format(name, msg)) + + def test_compare_big_little_opp(self): + """ + Compare the opp efficiency between little and big cpus + """ + tests_report = '' + tests_succeed = True + for (name, model) in self.energy_model: + (succeed, msg) = em.compare_big_little_opp(model) + if not succeed: + tests_succeed = False + tests_report += 'In platform {}: {}\n\n\t\t'.format(name, msg) + + self.assertTrue(tests_succeed, tests_report) + + def test_ideal_placements(self): + """Test placement of simple workloads""" + tests_report = '' + tests_succeed = True + for (name, model) in self.energy_model: + (succeed, msg) = em.ideal_placements(model) + if not succeed: + tests_succeed = False + tests_report += 'In platform {}: {}\n\n\t\t'.format(name, msg) + + self.assertTrue(tests_succeed, tests_report) + +class TestTargetEnergyModel(LisaTest): + """ + The goal of this test is to control the energy model of the target and + to report any anomalies/irregularities observed. + """ + TEST_CONF = { + 'modules': ['cpufreq'], + 'tools': [ + 'sysbench', + ] + } + @classmethod + def setUpClass(cls): + cls.env = TestEnv(test_conf=cls.TEST_CONF) + cls.target = cls.env.target + + if not cls.env.nrg_model: + try: + cls.env.nrg_model = EnergyModel.from_target(cls.env.target) + except Exception as e: + raise SkipTest( + 'This test requires an EnergyModel for the platform. ' + 'Either provide one manually or ensure it can be read ' + 'from the filesystem: {}'.format(e)) + + def test_is_power_coherent(self): + """Test that the power is increasing""" + (succeed, msg) = em.is_power_increasing(self.env.nrg_model) + self.assertTrue(succeed, msg) + + def test_is_energy_efficiency_coherent(self): + """Test that the energy efficiency (cap / pow) is decreasing""" + (succeed, msg) = em.is_efficiency_decreasing(self.env.nrg_model) + self.assertTrue(succeed, msg) + + def test_nb_active_states(self): + """Test the number of active states for each group of cpus""" + freqs = [] + for cluster in self.env.nrg_model.root.children: + cpu = cluster.children[0] + freqs.append(len(self.target.cpufreq.list_frequencies(cpu.cpus[0]))) + (succeed, msg) = em.check_active_states_nb(self.env.nrg_model, freqs) + self.assertTrue(succeed, msg) + + def test_get_opp_in_overutilized(self): + """Get opp that are in overutilized zone""" + opp_overutilized = em.get_opp_overutilized(self.env.nrg_model) + msg = "\n" + for i, opp in enumerate(opp_overutilized): + msg += "\tGroup {}: {}\n".format(i, opp[1]) + logging.info(msg) + + def test_get_avg_opp_per_group(self): + """ + Get average workload that can be run on each cpus group + """ + avg_load = em.get_avg_cap(self.env.nrg_model) + msg = "\n" + for i, load in enumerate(avg_load): + msg += "\tGroup {}: {}\n".format(i, load) + logging.info(msg) + + def test_compare_big_little_opp(self): + """ + Compare the opp efficiency between little and big cpus + """ + (succeed, msg) = em.compare_big_little_opp(self.env.nrg_model) + self.assertTrue(succeed, msg) + + def test_ideal_placements(self): + """Test placement of simple workloads""" + (succeed, msg) = em.ideal_placements(self.env.nrg_model) + self.assertTrue(succeed, msg) diff --git a/tests/lisa/test_energy_model.py b/tests/lisa/test_energy_model.py index bfb9c79fa6de216b83b56953ffe56454fbed93c0..8ca906c2e16700d6e769631041df8437865d8769 100644 --- a/tests/lisa/test_energy_model.py +++ b/tests/lisa/test_energy_model.py @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # - +import tests.utils.em as em_utils from collections import OrderedDict from unittest import TestCase import os @@ -29,6 +29,7 @@ from trace import Trace import libs.utils.platforms.juno_r0_energy import libs.utils.platforms.pixel_energy import libs.utils.platforms.hikey_energy +import libs.utils.platforms.hikey960_energy """ A very basic test suite for the EnergyModel class.""" @@ -62,9 +63,9 @@ little_cpu_idle_states = OrderedDict([ ]) littles=[0, 1] -def little_cpu_node(cpu): +def little_cpu_node(cpu, active_states): return EnergyModelNode(cpu=cpu, - active_states=little_cpu_active_states, + active_states=active_states, idle_states=little_cpu_idle_states) big_cluster_active_states = OrderedDict([ @@ -91,36 +92,41 @@ big_cpu_idle_states = OrderedDict([ bigs=[2, 3] -def big_cpu_node(cpu): +def big_cpu_node(cpu, active_state): return EnergyModelNode(cpu=cpu, - active_states=big_cpu_active_states, + active_states=active_state, idle_states=big_cpu_idle_states) - -em = EnergyModel( - root_node=EnergyModelRoot(children=[ - EnergyModelNode(name='cluster_little', - active_states=little_cluster_active_states, - idle_states=little_cluster_idle_states, - children=[little_cpu_node(0), - little_cpu_node(1)]), - EnergyModelNode(name='cluster_big', - active_states=big_cluster_active_states, - idle_states=big_cluster_idle_states, - children=[big_cpu_node(2), - big_cpu_node(3)]) - ]), - root_power_domain=PowerDomain(idle_states=[], children=[ - PowerDomain( - idle_states=['cluster-sleep-0'], - children=[PowerDomain(idle_states=['WFI', 'cpu-sleep-0'], cpu=c) - for c in littles]), - PowerDomain( - idle_states=['cluster-sleep-0'], - children=[PowerDomain(idle_states=['WFI', 'cpu-sleep-0'], cpu=c) - for c in bigs]), +def createEnergyModel(little_cpu=little_cpu_active_states, + big_cpu=big_cpu_active_states, + little_cluster=little_cluster_active_states, + big_cluster=big_cluster_active_states): + return EnergyModel( + root_node=EnergyModelRoot(children=[ + EnergyModelNode(name='cluster_little', + active_states=little_cluster, + idle_states=little_cluster_idle_states, + children=[little_cpu_node(0, little_cpu), + little_cpu_node(1, little_cpu)]), + EnergyModelNode(name='cluster_big', + active_states=big_cluster, + idle_states=big_cluster_idle_states, + children=[big_cpu_node(2, big_cpu), + big_cpu_node(3, big_cpu)]) ]), - freq_domains=[littles, bigs] -) + root_power_domain=PowerDomain(idle_states=[], children=[ + PowerDomain( + idle_states=['cluster-sleep-0'], + children=[PowerDomain(idle_states=['WFI', 'cpu-sleep-0'], cpu=c) + for c in littles]), + PowerDomain( + idle_states=['cluster-sleep-0'], + children=[PowerDomain(idle_states=['WFI', 'cpu-sleep-0'], cpu=c) + for c in bigs]), + ]), + freq_domains=[littles, bigs] + ) + +em = createEnergyModel() class TestInvalid(TestCase): """Test the sanity checks in EnerygModel setup""" @@ -423,3 +429,132 @@ class TestEstimateFromTrace(TestCase): row = df.iloc[i] self.assertAlmostEqual(row.name, exp_index, places=4) self.assertDictEqual(row.to_dict(), exp_values) + +class TestSanityCheckUtils(TestCase): + """ + The tests proposes some utils to sanity check the energy model. This + test controls the behaviours of these utils functions. + """ + # Good cpu active states + coherent_little_cpu_active_states = OrderedDict([ + (1000, ActiveState(capacity=100, power=100)), + (1500, ActiveState(capacity=150, power=160)), + (2000, ActiveState(capacity=200, power=800)), + ]) + coherent_big_cpu_active_states = OrderedDict([ + (3000, ActiveState(capacity=300, power=950)), + (4000, ActiveState(capacity=400, power=3000)), + ]) + + # Wrong cpu active states + wrong_little_cpu_active_states = OrderedDict([ + (1000, ActiveState(capacity=100, power=100)), + (1500, ActiveState(capacity=150, power=100)), + (2000, ActiveState(capacity=200, power=100)), + ]) + + wrong_big_cpu_active_states = OrderedDict([ + (1000, ActiveState(capacity=300, power=500)), + (1500, ActiveState(capacity=400, power=100)), + (2000, ActiveState(capacity=500, power=100)) + ]) + + # Wrong cluster active states + wrong_cluster_active_states = OrderedDict([ + (1000, ActiveState(power=30)), + (1500, ActiveState(power=30)), + (2000, ActiveState(power=30)), + ]) + + good_em = createEnergyModel(little_cpu=coherent_little_cpu_active_states, + big_cpu=coherent_big_cpu_active_states) + + bad_em = createEnergyModel(little_cpu=wrong_little_cpu_active_states, + big_cpu=wrong_big_cpu_active_states, + little_cluster=wrong_cluster_active_states, + big_cluster=wrong_cluster_active_states) + + def test_is_power_increasing(self): + """is_power_increasing correctly reports errors""" + (succeed, msg) = em_utils.is_power_increasing(self.good_em) + self.assertTrue(succeed) + self.assertEqual(msg, '') + + (succeed, msg) = em_utils.is_power_increasing(self.bad_em) + self.assertFalse(succeed) + self.assertIn(("['cluster_little', 'cluster_big', 'cpu0', 'cpu1', " + "'cpu2', 'cpu3']"), msg) + + def test_is_efficiency_decreasing(self): + """is_efficiency_decreasing correctly reports errors""" + (succeed, msg) = em_utils.is_efficiency_decreasing(self.good_em) + self.assertTrue(succeed) + self.assertEqual(msg, '') + + (succeed, msg) = em_utils.is_efficiency_decreasing(self.bad_em) + self.assertFalse(succeed) + self.assertIn("['cpu0', 'cpu1', 'cpu2', 'cpu3']", msg) + + def test_nb_active_states(self): + """check_active_states_nb correctly reports errors""" + (succeed, msg) = em_utils.check_active_states_nb(self.good_em, [3,2]) + self.assertTrue(succeed) + self.assertEqual(msg, '') + + (succeed, msg) = em_utils.check_active_states_nb(self.good_em, [5,2]) + self.assertFalse(succeed) + self.assertIn("['cpu0', 'cpu1', 'cluster_little']", msg) + (succeed, msg) = em_utils.check_active_states_nb(self.good_em, [3,5]) + self.assertFalse(succeed) + self.assertIn("['cpu2', 'cpu3', 'cluster_big']", msg) + + def test_get_opp_overutilized(self): + """get_opp_overutilized correctly reports information""" + opp_overutilized = em_utils.get_opp_overutilized(self.good_em) + expected_opp = [(160.0, [200]), (320.0, [400])] + self.assertEqual(opp_overutilized, expected_opp) + + def test_get_avg_cap(self): + """get_avg_cap correctly reports information""" + avg_load = em_utils.get_avg_cap(self.good_em) + expected_load = [130.0, 310.0] + self.assertEqual(avg_load, expected_load) + + def test_compare_big_little_opp(self): + """compare_big_little_opp correctly reports errors""" + (succeed, msg) = em_utils.compare_big_little_opp(self.good_em) + self.assertTrue(succeed) + self.assertEqual(msg, '') + + (succeed, msg) = em_utils.compare_big_little_opp(self.bad_em) + self.assertFalse(succeed) + self.assertIn("[200, 160.0]", msg) + self.assertIn("[100]", msg) + + def test_get_expected_placement(self): + """get_expected_placement returns expected value""" + util = [100, 0] + placement = em_utils._get_expected_placement(self.good_em, util) + self.assertEqual(placement, [[100, 0], [0, 0]]) + + util = [100, 200] + placement = em_utils._get_expected_placement(self.good_em, util) + self.assertEqual(placement, [[100, 0], [200, 0]]) + + def test_are_placements_equal(self): + """_are_placements_equal returns expected value""" + s1 = [[100, 0], [0, 0]] + s2 = [(100,0,0,0), (0,100,0,0)] + self.assertTrue(em_utils._are_placements_equal(self.good_em, s1, s2)) + + s2 = [(100,0,0,0)] + self.assertFalse(em_utils._are_placements_equal(self.good_em, s1, s2)) + + s2 = [(100,0,0,0), (0,100,0,0), (0,0,100,0)] + self.assertFalse(em_utils._are_placements_equal(self.good_em, s1, s2)) + + s2 = [(0,0,100,0), (0,0,0,100)] + self.assertFalse(em_utils._are_placements_equal(self.good_em, s1, s2)) + + s2 = [(200,0,0,0), (0,200,0,0)] + self.assertFalse(em_utils._are_placements_equal(self.good_em, s1, s2)) diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/utils/em.py b/tests/utils/em.py new file mode 100644 index 0000000000000000000000000000000000000000..b0fc1329db40811e5dcd8e62a6acbb3a8d4d6f06 --- /dev/null +++ b/tests/utils/em.py @@ -0,0 +1,371 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (C) 2018, 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 itertools + +# The utilization rate limit to switch to the overutilization mode +OVERUTILIZED_RATE = 0.8 + +def _get_powers_list(node): + """ + Get the list of powers for a given node. + :param node: A node of the energy model. It can be a cluster or cpu. + :type node: EnergyModelNode + + :returns: The list of powers associates with the given node. + """ + powers = [] + for freq in node.active_states.keys(): + powers.append(node.active_states[freq].power) + return powers + +def _get_capacities_list(node): + """ + Get the list of capacities for a given node. + :param node: A node of the energy model. + :type node: EnergyModelNode + + :returns: The list of capacities associates with the given node. + """ + cap = [] + for freq in node.active_states.keys(): + cap.append(node.active_states[freq].capacity) + return cap + +def _is_list_strickly_increasing(l): + for i in range(len(l)-1): + if l[i] >= l[i+1]: + return False + return True + +def is_power_increasing(energy_model): + """ + Check that for all the nodes of the energy model the power is an increasing + sequence. + :params energy_model: The energy model to control + :type energy_model: EnergyModel + + :returns: A tuple that contains True if all nodes respect the + previous assumptions, False otherwise; and an error message. + """ + powers = [] + msg = '' + failed_nodes = [] + for cluster in energy_model.root.children: + power = _get_powers_list(cluster) + # Check that clusters powers are strickly increasing + if not _is_list_strickly_increasing(power): + failed_nodes.append(cluster.name) + + for cpu in energy_model.cpu_nodes: + powers = _get_powers_list(cpu) + # Check that cpu powers are strickly increasing + if not _is_list_strickly_increasing(powers): + failed_nodes.append(cpu.name) + + if failed_nodes: + msg = 'The power lists is not increasing for {}'.format(failed_nodes) + return (False, msg) + + return (True, msg) + +def is_efficiency_decreasing(energy_model): + """ + Check that for all the cpus of the energy model the energy efficiency is + decreasing. + :params energy_model: The energy model to control + :type energy_model: EnergyModel + + :returns: A tuple that contains True if the energy_model respects + the previous assumption, False otherwise; and an error message. + """ + msg = '' + powers = [] + cap = [] + energy = [] + failed_nodes = [] + for cpu in energy_model.cpu_nodes: + powers = _get_powers_list(cpu) + cap = _get_capacities_list(cpu) + energy = [float(powers[i])/cap[i] for i in range(len(powers))] + # Check that power / cap is strickly increasing which is equivalent + # to check that the cap / power (energy efficiency) is decreasing + if not _is_list_strickly_increasing(energy): + failed_nodes.append(cpu.name) + + if failed_nodes: + msg = ('The energy efficiency is not decreasing ' + 'for {}'.format(failed_nodes)) + return (False, msg) + + return (True, msg) + +def check_active_states_nb(energy_model, freqs_nb): + """ + Control that the active states number is equal to the number of + frequencies in an energy model. + :params energy_model: The energy model to control + :type energy_model: EnergyModel + + :params freqs_nb: A list that contains the number of frequencies per cluster + :type freqs_nb: [int] + + :returns: A tuple that contains True if the number of active states + correspond to the number of frequencies for each cluster, False + otherwise; and an error message. + """ + msg = '' + succeed = True + failed_nodes = [] + for i, cluster in enumerate(energy_model.root.children): + for cpu in cluster.children: + active_state_nb = len(cpu.active_states.keys()) + if active_state_nb != freqs_nb[i]: + failed_nodes.append(cpu.name) + + active_state_nb = len(cluster.active_states.keys()) + if active_state_nb != freqs_nb[i]: + failed_nodes.append(cluster.name) + + if failed_nodes: + msg = ('The number of active states is not equal to the ' + 'frequency number for {}'.format(failed_nodes)) + return (False, msg) + + return (True, msg) + +def get_opp_overutilized(energy_model): + """ + Get the capacity limit after which the cpu enters in the overutilized mode + and indicate all the capacities for the group that are over this limit. + :params energy_model: The energy model to control + :type energy_model: EnergyModel + + :returns: a list containing a tupple per group containing the limit + capacity and an list of the opp that are in the overutilized zone + """ + opp = [] + for group in energy_model.cpu_groups: + # Get the first cpu of the group + cpu = energy_model.cpu_nodes[group[0]] + cap = _get_capacities_list(cpu) + cap_limit = cap[-1] * OVERUTILIZED_RATE + + # Get the elements that are bigger than the capacity limit + cap_overutilized = [x for x in cap if x > cap_limit] + opp.append((cap_limit, cap_overutilized)) + return opp + +def get_avg_cap(energy_model): + """ + Get a task utilization which should fits on a groups of cpus. + + :params energy_model: The energy model to get the information + :type energy_model: EnergyModel + + :returns: a list of average capacities per group of cpus. + """ + avg_cap = [] + max_cap = 0 + min_cap = 0 + for group in energy_model.cpu_groups: + cap = _get_capacities_list(energy_model.cpu_nodes[group[0]]) + + # Choose the min capacity of the task or the max capacity of the + # previous group to be sure that the task cannot fit on the previous + # group + min_cap = cap[0] if cap[0] > max_cap else max_cap + max_cap = cap[-1] + avg_cap.append((min_cap + max_cap * OVERUTILIZED_RATE) / 2) + return avg_cap + +def _are_placements_equal(energy_model, expected, placement): + """ + Given an optimal placement and the placement simulated on the energy + model controls if both placements are equal per group. + :params energy_model: The energy model from which the placement is tested + :type energy_model: EnergyModel + + :params expected: a list of group containing a list of util per cpus. For + instance a placement could be : [[0, 0, 0, 100], [0,0]] + for an energy model containing 4 little cpus and 2 big + cpus and an util of 100 is expected on one of the little + cpus. + :type expected: [[int]] + + :params placement: an exhaustive list of placements obtaining by the + function get_optimal_placements from the EnergyModel. + :type placement: [(int)] + + :returns: True if both placements are equal, False otherwise. + """ + for i, group in enumerate(energy_model.cpu_groups): + # Generate all the placements possible for this group of cpus + s1 = set(itertools.permutations(expected[i])) + # Extract the placement for the given group + s2 = set([tuple(l[g] for g in group) for l in placement]) + if s1 != s2: + return False + return True + +def _get_expected_placement(energy_model, group_util): + """ + Create the expected placement given an utilization per group. + :params energy_model: The energy model for which the expected placement is + computed + :type energy_model: EnergyModel + + :params group_util: an utilization value for each group of cpus + :type group_util: [int] + + :returns: a list for each group that contain a list of utilization per cpu. + For instance for a model that has 4 little cpus and 2 big + cpus it returns the following data: + [[group_util[0], 0, 0, 0], [group_util[1], 0]] + """ + expected = [] + for i, group in enumerate(energy_model.cpu_groups): + cpus_nb = len(group) + cpus_util = [0] * cpus_nb + cpus_util[0] = group_util[i] + expected.append(cpus_util) + return expected + +def ideal_placements(energy_model): + """ + For each group, generates a workload that should run only on it and + controls that obtained placement is equal to the expected placement + :params energy_model: The energy model for which these workloads are + generated + :type energy_model: EnergyModel + + :returns: True if the expected placement is equal to the obtained one, + False otherwise + """ + msg = '' + avg_cap = get_avg_cap(energy_model) + nb_groups = len(energy_model.cpu_groups) + for i in range(nb_groups): + # Task with a capacity that should fit on the group + one_task = {'t0': avg_cap[i]} + + # Get the placement obtained for this task on the model + placement = energy_model.get_optimal_placements(one_task) + + # Create the expected placement + exp_cap = [0] * nb_groups + exp_cap[i] = avg_cap[i] + expected = _get_expected_placement(energy_model, exp_cap) + + # Compare the expected placement and the obtained one + if not _are_placements_equal(energy_model, expected, placement): + msg = ('The expected placement for the task {} is {} but the ' + 'obtained placement \n\t\t' + 'was {}'.format(one_task, expected, placement)) + return (False, msg) + + return (True, msg) + +def _get_efficiency(util, cpu): + """ + For a given utilization, computes the energy efficiency (capacity / power) + to run at this utilization. If it does not correspond to an opp for the + cpu, the efficiency is computed by selecting the opp above the + utilization and the power is computed proportionally to it. + :params util: targeted utilization + :type util: int + + :params cpu: The cpu for which the energy efficiency is computed. + :type cpu: EnergyModelNode + + :returns: The ratio between the capacity and the power for the targeted opp + """ + + # Get the list of capacities and powers for the cpu + capacities = _get_capacities_list(cpu) + powers = _get_powers_list(cpu) + + # Compute the efficiency for the first capacity larger or equal than the + # requiered utilization + for cap in capacities: + if cap >= util: + # The power used for a specific utilization is proportional to + # the capacity used for this utilization. + power = powers[capacities.index(cap)] * (float(util) / cap) + return float(cap) / power + + raise ValueError('The utilization {} is larger than the possible ' + 'opp for {}'.format(util, cpu.name)) + +def compare_big_little_opp(energy_model): + """ + Compare the opp between the little and big cpus. The big cpus should not + have any opp that are more power-efficient than the little in the + non-overutilization zone. + And the little cpus should not have any opp that is more power-efficient + that the big opp but are skipped due to the overutilization zone. + + :params energy_model: the energy model that contains the information + for the cpus + :type energy_model: EnergyModel + + :returns: A tupple containing True if the conditions are respected, False + otherwise; and an error message. + """ + msg = '' + overutilized_failed_opp = [] + non_overutilized_failed_opp = [] + succeed = True + groups = energy_model.cpu_groups + + # This test can only be run on big.LITTLE architecture + if len(groups) < 2: + return (True, msg) + + # Get the first little and big cpus + little_cpu = energy_model.cpu_nodes[groups[0][0]] + big_cpu = energy_model.cpu_nodes[groups[1][0]] + + # Get the opp in overutilized mode for the little cpu + (limit_opp, overutilized_opp) = get_opp_overutilized(energy_model)[0] + + little_cap = _get_capacities_list(little_cpu) + # Get the opp capacities of the big cpu that overlaps with the little + # capacities + big_cap = [c for c in _get_capacities_list(big_cpu) if c < little_cap[-1]] + + # Iterates on all the opp and the overutilized point + for opp in little_cap+[limit_opp]+big_cap: + # Compute efficiency for the big and little cpus + little_efficiency = _get_efficiency(opp, little_cpu) + big_efficiency = _get_efficiency(opp, big_cpu) + if opp < limit_opp: + if big_efficiency > little_efficiency: + non_overutilized_failed_opp.append(opp) + succeed = False + else: + if little_efficiency > big_efficiency: + overutilized_failed_opp.append(opp) + succeed = False + + if overutilized_failed_opp: + msg += ('It is more energy efficient to run for the utilization {} ' + 'on the little cpu \n\t\tbut it is run on the big cpu due ' + 'to the overutilization zone'.format(overutilized_failed_opp)) + if non_overutilized_failed_opp: + msg += ('\n\t\tThe utilization {} is more power efficient to run on a ' + 'big cpu'.format(non_overutilized_failed_opp)) + return (succeed, msg)