diff --git a/README.md b/README.md index 3ddb09a..eda6c55 100644 --- a/README.md +++ b/README.md @@ -12,3 +12,8 @@ To measure the energy consumed during the execution of the function `fun()` run sensor.stop() energy_pkg = sensor.recorded_energy(pyRAPL.Device.PKG) energy_dram = sensor.recorded_energy(pyRAPL.Device.DRAM) + + +# TODO +- [ ] add decorator +- [X] add time measure \ No newline at end of file diff --git a/pyRAPL/__init__.py b/pyRAPL/__init__.py index e16914c..65d700c 100644 --- a/pyRAPL/__init__.py +++ b/pyRAPL/__init__.py @@ -22,7 +22,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from pyRAPL.pyRAPL import PyRAPL, Device, PyRAPLException, PyRAPLNoEnergyConsumptionRecordedException +from pyRAPL.pyRAPL import PyRAPL, Device, Measure, measure, PyRAPLException, PyRAPLNoEnergyConsumptionRecordedException from pyRAPL.pyRAPL import PyRAPLNoEnergyConsumptionRecordStartedException, PyRAPLCantRecordEnergyConsumption +# from pyRAPL.pyRAPL import measure_energy __version__ = "0.1.0" diff --git a/pyRAPL/pyRAPL.py b/pyRAPL/pyRAPL.py index c84c911..bb09e7b 100644 --- a/pyRAPL/pyRAPL.py +++ b/pyRAPL/pyRAPL.py @@ -23,14 +23,17 @@ # SOFTWARE. import os import logging +import time +import functools + +from enum import Enum + try: import psutil except ImportError: logging.getLogger().info("psutil is not installed.") -from enum import Enum - class Device(Enum): PKG = 1 DRAM = 2 @@ -64,40 +67,46 @@ class PyRAPL: """ singleton that force the execution of the running process on a unique package and retrieve package power consumption """ - instance = None - already_init = False + _instance = None + _already_init = False + + def __new__(cls): + """use only one instance of PyRAPL""" + if cls._instance is None: + cls._instance = object.__new__(cls) + return cls._instance def __init__(self): - self.sys_api = { - Device.PKG: None, - Device.DRAM: None, - Device.GPU: None - } - self.is_record_running = { + + self._is_record_running = { Device.PKG: False, Device.DRAM: False, Device.GPU: False } - self.measure = { + self._measure = { Device.PKG: [None, None], Device.DRAM: [None, None], Device.GPU: [None, None] } - self.package_id = None - self.siblings_cpu = None - - if self.already_init is False: - self.already_init = True + self._package_id = None + self._siblings_cpu = None + if self._already_init is False: + self._already_init = True self._uniq_init() def _uniq_init(self): """ Initialize the PyRAPL tool """ - self.siblings_cpu = PyRAPL._get_siblings_cpu() - self.package_id = PyRAPL._get_package_id() - PyRAPL._force_cpu_execution_on(self.siblings_cpu) - self._open_rapl_files(self.package_id) + self._sys_api = { + Device.PKG: None, + Device.DRAM: None, + Device.GPU: None + } + self._siblings_cpu = PyRAPL._get_siblings_cpu() + self._package_id = PyRAPL._get_package_id() + PyRAPL._force_cpu_execution_on(self._siblings_cpu) + self._open_rapl_files(self._package_id) @staticmethod def _get_siblings_cpu(): @@ -140,17 +149,17 @@ def _open_rapl_files(self, package_id): """ pkg_dir_name, pkg_rapl_id = self._get_pkg_directory_name(package_id) if pkg_dir_name is None: - self.sys_api[Device.PKG] = None - self.sys_api[Device.DRAM] = None + self._sys_api[Device.PKG] = None + self._sys_api[Device.DRAM] = None return - self.sys_api[Device.PKG] = open(pkg_dir_name + '/energy_uj', 'r') + self._sys_api[Device.PKG] = open(pkg_dir_name + '/energy_uj', 'r') dram_dir_name = self._get_dram_directory_name(pkg_dir_name, pkg_rapl_id) if dram_dir_name is None: - self.sys_api[Device.DRAM] = None + self._sys_api[Device.DRAM] = None return - self.sys_api[Device.DRAM] = open(dram_dir_name + '/energy_uj', 'r') + self._sys_api[Device.DRAM] = open(dram_dir_name + '/energy_uj', 'r') def _get_pkg_directory_name(self, package_id): """ @@ -177,40 +186,44 @@ def _get_dram_directory_name(self, pkg_directory_name, rapl_id): sub_id += 1 return None - def __new__(cls): - """use only one instance of PyRAPL""" - if cls.instance is None: - cls.instance = object.__new__(cls) - return cls.instance - def energy(self, device): """ return the amount of consumed energy by the device given in parameter since last CPU reset :param Device device: the device to get the power consumption - :return int: the amount of consumed energy of the device since last CPU reset in mJ + :return float: the amount of consumed energy of the device since last CPU reset in J :raise PyRAPLCantRecordEnergyConsumption: if no energy consumtion metric is available for the given device - :raise TypeError: if device is not a Device parameter + :raise TypeError: if device is not a Device instance """ + if not isinstance(device, Device): raise TypeError() - if self.sys_api[device] is None: + + if self._sys_api[device] is None: raise PyRAPLCantRecordEnergyConsumption(device) - api_file = self.sys_api[device] + api_file = self._sys_api[device] api_file.seek(0, 0) - return int(api_file.readline()) + return int(api_file.readline())/1000000 - def record(self, devices): + def _begin_record(self, device): + energy = self.energy(device) + self._measure[device][0] = energy + self._is_record_running[device] = True + + def record(self, *devices): """ start recording the power consumption of the given devices :param [Device] devices: list of device to record the power consumption :raise PyRAPLCantRecordEnergyConsumption: if no energy consumtion metric is available for the given device - :raise TypeError: if a device in devices list is not a Device parameter + :raise TypeError: if a device in devices list is not a Device instance """ - for device in devices: - energy = self.energy(device) - self.measure[device][0] = energy - self.is_record_running[device] = True + if devices: + for device in devices: + self._begin_record(device) + else: + for device, api_file in self._sys_api.items(): + if api_file: + self._begin_record(device) def stop(self): """ @@ -219,31 +232,98 @@ def stop(self): """ energy_recorded = False - for device, is_running in self.is_record_running.items(): + for device, is_running in self._is_record_running.items(): if is_running: energy_recorded = True - self.is_record_running[device] = False - self.measure[device][1] = self.energy(device) + self._is_record_running[device] = False + self._measure[device][1] = self.energy(device) if not energy_recorded: raise PyRAPLNoEnergyConsumptionRecordStartedException() + def _compute_recorded_energy(self, device): - def recorded_energy(self, device): + recorded_energy = (self._measure[device][1] - self._measure[device][0]) + # print("yolo") + self._measure[device][0] = None + self._measure[device][1] = None + return recorded_energy + + def recorded_energy(self, *devices): """ get the latest energy consumption recorded by PyRAPL for the given device - :return: energy (in mJ) consumed between the last record() and stop() function call + :return: energy (in J) consumed between the last record() and stop() function call :raise PyRAPLNoEnergyConsumptionRecordedException: if no energy consumption was recorded - :raise TypeError: if device is not a Device parameter + :raise TypeError: if device is not a Device instance """ - if not isinstance(device, Device): - raise TypeError() + measures = {} + if devices: + for device in devices: + if not isinstance(device, Device): + raise TypeError() + + if self._measure[device][0] is None or self._measure[device][1] is None: + raise PyRAPLNoEnergyConsumptionRecordedException + + measures[device] = self._compute_recorded_energy(device) - if self.measure[device][0] is None or self.measure[device][1] is None: - raise PyRAPLNoEnergyConsumptionRecordedException + return measures + else: + for device in self._measure: + # print("dev {}, dev[0] {} ,dev[1] {}".format(dev,self.measure[dev][0],self.measure[dev][1]) ) + if self._measure[device][0] and self._measure[device][1]: + measures[device] = self._compute_recorded_energy(device) + if measures: + return measures + else: + raise PyRAPLNoEnergyConsumptionRecordedException - measure = self.measure[device][1] - self.measure[device][0] - self.measure[device][0] = None - self.measure[device][1] = None - return measure +class Measure: + def __init__(self, function_name, data): + self.function_name = function_name + self.data = data + + +def measure(_func=None, *, devices=None, handler=None): + """ a decorator to measure the energy consumption of a function recorded by PyRAPL + :param [Device] devices: the list of devices to monitor by pyrapl + :param function(measure) handler: traitement of the results recorded from pyrapl + """ + + def default_handler(measures): + # print("default handler") + print(f"measures got from the function {measures.function_name } :") + for mes, val in measures.data.items(): + print(f"{mes } : {val:.4}") + + def decorator_measure_energy(func): + @functools.wraps(func) + def wrapper_measure(*args, **kwargs): + sensor = PyRAPL() + sensor.record(*devices) + t1 = time.perf_counter() + val = func(*args, **kwargs) + t2 = time.perf_counter() + sensor.stop() + + data = { + device._name_: mes for device, mes in sensor.recorded_energy(*devices).items() + } + data['TIME'] = t2 - t1 + + handle(Measure(func.__name__, data)) + return val + return wrapper_measure + + # measure.sensor=PyRAPL() # to make an instance only one time + if not isinstance(devices, list): + devices = [devices] if devices else [] + + handle = default_handler if handler is None else handler + + if _func is None: + # to ensure the working system when you call it with parameters or without parameters + return decorator_measure_energy + else: + return decorator_measure_energy(_func) diff --git a/test.py b/test.py new file mode 100644 index 0000000..9d66bdf --- /dev/null +++ b/test.py @@ -0,0 +1,57 @@ +import pyRAPL +# from pyRAPL import measure +# from pyRAPL import measure_energy +from time import sleep +from sys import argv + + + +def myhandler(measures): + print("myhandler") + print(measures) + + +@pyRAPL.measure(handler=myhandler) +def testi(n): + sleep(n) + return n + +@pyRAPL.measure() +def fun2(n): + sleep(2*n) + + +@pyRAPL.measure(devices=[pyRAPL.Device.PKG, pyRAPL.Device.DRAM]) +def fun3(n): + sleep(2*n) + + +def main1(): + n = int(argv[1]) if len(argv) >1 else 5 + sensor = pyRAPL.PyRAPL() + sensor.record([pyRAPL.Device.PKG, pyRAPL.Device.DRAM]) + sleep(n) + sensor.stop() + # energy_pkg = sensor.recorded_energy(pyRAPL.Device.PKG) + # energy_dram = sensor.recorded_energy(pyRAPL.Device.DRAM) + # print("Energy PKG: %f , Energy DRAM: %f"%(energy_pkg,energy_dram)) + print(sensor.recorded_energy()) + sensor = pyRAPL.PyRAPL() + sensor.record() + sleep(n) + sensor.stop() + # energy_pkg = sensor.recorded_energy(pyRAPL.Device.PKG) + # energy_dram = sensor.recorded_energy(pyRAPL.Device.DRAM) + # print("Energy PKG: %f , Energy DRAM: %f"%(energy_pkg,energy_dram)) + print(sensor.recorded_energy()) + + +def main2(): + n = int(argv[1]) if len(argv) >1 else 5 + # testi(n) + # fun2(n) + # testi(n) + fun3(n) + +if __name__=="__main__": + main2() diff --git a/tests/test_pyRAPL.py b/tests/test_pyRAPL.py index 98e06e3..668e4bc 100644 --- a/tests/test_pyRAPL.py +++ b/tests/test_pyRAPL.py @@ -4,7 +4,7 @@ from mock import patch, Mock -from pyRAPL import PyRAPL, Device, PyRAPLCantRecordEnergyConsumption +from pyRAPL import PyRAPL, Device, measure, Measure, PyRAPLCantRecordEnergyConsumption from pyRAPL import PyRAPLNoEnergyConsumptionRecordedException, PyRAPLNoEnergyConsumptionRecordStartedException psutil.Process = Mock() @@ -49,7 +49,7 @@ def rapl_fs_with_dram(rapl_fs_with_core): """ rapl_fs_with_core.create_file('/sys/class/powercap/intel-rapl/intel-rapl:0/intel-rapl:0:1/name', contents='dram\n') rapl_fs_with_core.create_file('/sys/class/powercap/intel-rapl/intel-rapl:0/intel-rapl:0:1/energy_uj', - contents='12345\n') + contents='54321\n') return rapl_fs_with_core @@ -58,10 +58,12 @@ def mocked_pyRAPL(): """ return a PyRAPL instance with mocked psutils function """ + PyRAPL._instance = None + PyRAPL._aldready_init = False pyrapl = PyRAPL() yield pyrapl - PyRAPL.instance = None - PyRAPL.aldready_init = False + PyRAPL._instance = None + PyRAPL._aldready_init = False ############# # INIT TEST # @@ -83,7 +85,7 @@ def test_uniq_init_executed_once(mock1, mock2): def test_get_siblings_cpu_simple_list(fs): """ - create a /sys/devices/system/cpu/cpu0/topology/core_siblings_list file that contains the following value : + create a /sys/devices/system/cpu/cpu0/topology/core_siblings_list file that contains the following value : 1,2,3,20 use the PyRAPL._get_siblings_cpu to retrieve sibling cpu of cpu0 @@ -96,7 +98,7 @@ def test_get_siblings_cpu_simple_list(fs): def test_get_siblings_cpu_interval_id(fs): """ - create a /sys/devices/system/cpu/cpu0/topology/core_siblings_list file that contains the following value : + create a /sys/devices/system/cpu/cpu0/topology/core_siblings_list file that contains the following value : 1-3 use the PyRAPL._get_siblings_cpu to retrieve sibling cpu of cpu0 @@ -109,7 +111,7 @@ def test_get_siblings_cpu_interval_id(fs): def test_get_package_id(fs): """ - create a /sys/devices/system/cpu/cpu0/topology/physical_package_id file that contains the following value : + create a /sys/devices/system/cpu/cpu0/topology/physical_package_id file that contains the following value : 9 use the PyRAPL._get_package_id to retrieve the package id of cpu0 @@ -128,8 +130,8 @@ def test_open_rapl_files_no_file(base_fs, mocked_pyRAPL): - no file for package metric was found - no file for dram metric was found """ - assert mocked_pyRAPL.sys_api[Device.PKG] is None - assert mocked_pyRAPL.sys_api[Device.DRAM] is None + assert mocked_pyRAPL._sys_api[Device.PKG] is None + assert mocked_pyRAPL._sys_api[Device.DRAM] is None def test_open_rapl_files_simple(rapl_fs, mocked_pyRAPL): @@ -141,10 +143,10 @@ def test_open_rapl_files_simple(rapl_fs, mocked_pyRAPL): - the file /sys/class/powercap/intel-rapl/intel-rapl:0/energy_uj was open by the PyRAPL instance - no file for dram metric was found """ - assert mocked_pyRAPL.sys_api[Device.PKG] is not None + assert mocked_pyRAPL._sys_api[Device.PKG] is not None - assert mocked_pyRAPL.sys_api[Device.DRAM] is None - assert mocked_pyRAPL.sys_api[Device.PKG].name == '/sys/class/powercap/intel-rapl/intel-rapl:0/energy_uj' + assert mocked_pyRAPL._sys_api[Device.DRAM] is None + assert mocked_pyRAPL._sys_api[Device.PKG].name == '/sys/class/powercap/intel-rapl/intel-rapl:0/energy_uj' def test_open_rapl_files_simple_pkg1(rapl_fs, mocked_pyRAPL): @@ -157,10 +159,10 @@ def test_open_rapl_files_simple_pkg1(rapl_fs, mocked_pyRAPL): - no file for dram metric was found """ mocked_pyRAPL._open_rapl_files(1) - assert mocked_pyRAPL.sys_api[Device.PKG] is not None + assert mocked_pyRAPL._sys_api[Device.PKG] is not None - assert mocked_pyRAPL.sys_api[Device.DRAM] is None - assert mocked_pyRAPL.sys_api[Device.PKG].name == '/sys/class/powercap/intel-rapl/intel-rapl:1/energy_uj' + assert mocked_pyRAPL._sys_api[Device.DRAM] is None + assert mocked_pyRAPL._sys_api[Device.PKG].name == '/sys/class/powercap/intel-rapl/intel-rapl:1/energy_uj' def test_open_rapl_files_with_core(rapl_fs_with_core, mocked_pyRAPL): @@ -172,10 +174,10 @@ def test_open_rapl_files_with_core(rapl_fs_with_core, mocked_pyRAPL): - the file /sys/class/powercap/intel-rapl/intel-rapl:0/energy_uj was open by the PyRAPL instance - no file for dram metric was found """ - assert mocked_pyRAPL.sys_api[Device.PKG] is not None + assert mocked_pyRAPL._sys_api[Device.PKG] is not None - assert mocked_pyRAPL.sys_api[Device.DRAM] is None - assert mocked_pyRAPL.sys_api[Device.PKG].name == '/sys/class/powercap/intel-rapl/intel-rapl:0/energy_uj' + assert mocked_pyRAPL._sys_api[Device.DRAM] is None + assert mocked_pyRAPL._sys_api[Device.PKG].name == '/sys/class/powercap/intel-rapl/intel-rapl:0/energy_uj' def test_open_rapl_files_with_dram(rapl_fs_with_dram, mocked_pyRAPL): @@ -187,11 +189,11 @@ def test_open_rapl_files_with_dram(rapl_fs_with_dram, mocked_pyRAPL): - the file /sys/class/powercap/intel-rapl/intel-rapl:0/energy_uj was open by the PyRAPL instance - the file /sys/class/powercap/intel-rapl/intel-rapl:0/intel-rapl:0:1/energy_uj was open by the PyRAPL instance """ - assert mocked_pyRAPL.sys_api[Device.DRAM] is not None - assert mocked_pyRAPL.sys_api[Device.PKG] is not None + assert mocked_pyRAPL._sys_api[Device.DRAM] is not None + assert mocked_pyRAPL._sys_api[Device.PKG] is not None - assert mocked_pyRAPL.sys_api[Device.DRAM].name == '/sys/class/powercap/intel-rapl/intel-rapl:0/intel-rapl:0:1/energy_uj' - assert mocked_pyRAPL.sys_api[Device.PKG].name == '/sys/class/powercap/intel-rapl/intel-rapl:0/energy_uj' + assert mocked_pyRAPL._sys_api[Device.DRAM].name == '/sys/class/powercap/intel-rapl/intel-rapl:0/intel-rapl:0:1/energy_uj' + assert mocked_pyRAPL._sys_api[Device.PKG].name == '/sys/class/powercap/intel-rapl/intel-rapl:0/energy_uj' ####################### @@ -230,7 +232,7 @@ def test_energy(rapl_fs, mocked_pyRAPL): - the returned value is an int and is greater than 0 """ val = mocked_pyRAPL.energy(Device.PKG) - assert isinstance(val, int) + assert isinstance(val, float) assert val > 0 @@ -247,7 +249,8 @@ def test_start_record_energy_pkg_without_rapl(base_fs, mocked_pyRAPL): - a PyRAPLCantRecordEnergyConsumption is raise """ with pytest.raises(PyRAPLCantRecordEnergyConsumption): - mocked_pyRAPL.record([Device.PKG]) + mocked_pyRAPL.record(Device.PKG) + def test_record_bad_type_device(rapl_fs, mocked_pyRAPL): @@ -258,7 +261,7 @@ def test_record_bad_type_device(rapl_fs, mocked_pyRAPL): - A TypeError is raise """ with pytest.raises(TypeError): - mocked_pyRAPL.record([Device.PKG, 5]) + mocked_pyRAPL.record(Device.PKG, 5) def test_start_record_energy_pkg(rapl_fs, mocked_pyRAPL): @@ -268,15 +271,15 @@ def test_start_record_energy_pkg(rapl_fs, mocked_pyRAPL): create an instance of PyRAPL with the file system base_fs and run the start_record_energy_pkg function Test if: - - before runing the function the measure[Device.PKG][0] attribute is None and is_record_running[Device.PKG] is + - before runing the function the _measure[Device.PKG][0] attribute is None and is_record_running[Device.PKG] is False - - the measure[Device.PKG][0] attribute is not None and is_record_running[Device.PKG] is True + - the _measure[Device.PKG][0] attribute is not None and is_record_running[Device.PKG] is True """ - assert mocked_pyRAPL.measure[Device.PKG][0] is None - assert mocked_pyRAPL.is_record_running[Device.PKG] is False - mocked_pyRAPL.record([Device.PKG]) - assert mocked_pyRAPL.measure[Device.PKG][0] is not None - assert mocked_pyRAPL.is_record_running[Device.PKG] is True + assert mocked_pyRAPL._measure[Device.PKG][0] is None + assert mocked_pyRAPL._is_record_running[Device.PKG] is False + mocked_pyRAPL.record(Device.PKG) + assert mocked_pyRAPL._measure[Device.PKG][0] is not None + assert mocked_pyRAPL._is_record_running[Device.PKG] is True def test_start_record_energy_pkg_and_dram(rapl_fs_with_dram, mocked_pyRAPL): @@ -286,14 +289,48 @@ def test_start_record_energy_pkg_and_dram(rapl_fs_with_dram, mocked_pyRAPL): create an instance of PyRAPL with the file system base_fs and run the record function Test if: - - before runing the function the measure[Device.PKG][0] attribute is None - - the measure[Device.PKG][0] attribute is not None + - before runing the function the _measure[Device.PKG][0] attribute is None + - the _measure[Device.PKG][0] attribute is not None + """ + assert mocked_pyRAPL._measure[Device.PKG][0] is None + assert mocked_pyRAPL._measure[Device.DRAM][0] is None + mocked_pyRAPL.record(Device.PKG, Device.DRAM) + assert mocked_pyRAPL._measure[Device.PKG][0] is not None + assert mocked_pyRAPL._measure[Device.DRAM][0] is not None + +def test_start_record_energy_all(rapl_fs_with_dram, mocked_pyRAPL): + """ + try to start all energy consumtpion recording + + create an instance of PyRAPL with the file system base_fs and run the record function + + Test if: + - before runing the function the _measure[Device.PKG][0] and _measure[Device.DRAM][0] attribute is None + - the _measure[Device.PKG][0] and _measure[Device.DRAM][0] attribute is not None + """ + assert mocked_pyRAPL._measure[Device.PKG][0] is None + assert mocked_pyRAPL._measure[Device.DRAM][0] is None + mocked_pyRAPL.record() + assert mocked_pyRAPL._measure[Device.PKG][0] is not None + assert mocked_pyRAPL._measure[Device.DRAM][0] is not None + + +def test_start_record_energy_all(rapl_fs, mocked_pyRAPL): + """ + try to start all energy consumtpion recording in a file system without dram + + create an instance of PyRAPL with the file system base_fs and run the record function + + Test if: + - before runing the function the _measure[Device.PKG][0] and _measure[Device.DRAM][0] attribute is None + - the _measure[Device.PKG][0] attribute is not None + - the _measure[Device.PKG][0] attribute is None """ - assert mocked_pyRAPL.measure[Device.PKG][0] is None - assert mocked_pyRAPL.measure[Device.DRAM][0] is None - mocked_pyRAPL.record([Device.PKG, Device.DRAM]) - assert mocked_pyRAPL.measure[Device.PKG][0] is not None - assert mocked_pyRAPL.measure[Device.DRAM][0] is not None + assert mocked_pyRAPL._measure[Device.PKG][0] is None + assert mocked_pyRAPL._measure[Device.DRAM][0] is None + mocked_pyRAPL.record() + assert mocked_pyRAPL._measure[Device.PKG][0] is not None + assert mocked_pyRAPL._measure[Device.DRAM][0] is None ################################## # STOP RECORDING PACKAGE ENERGY # @@ -319,21 +356,21 @@ def test_stop_record_energy_pkg(rapl_fs, mocked_pyRAPL): stop_record_energy_pkg functions Test if: - - before runing the function the measure[Device.PKG][1] attribute is None and is_record_running[Device.PKG] is + - before runing the function the _measure[Device.PKG][1] attribute is None and is_record_running[Device.PKG] is True - - the measure[Device.PKG][1] attribute is not None and is_record_running[Device.PKG] is False + - the _measure[Device.PKG][1] attribute is not None and is_record_running[Device.PKG] is False """ - mocked_pyRAPL.record([Device.PKG]) + mocked_pyRAPL.record(Device.PKG) - assert mocked_pyRAPL.measure[Device.PKG][1] is None - assert mocked_pyRAPL.is_record_running[Device.PKG] is True + assert mocked_pyRAPL._measure[Device.PKG][1] is None + assert mocked_pyRAPL._is_record_running[Device.PKG] is True mocked_pyRAPL.stop() - assert mocked_pyRAPL.measure[Device.PKG][1] is not None - assert mocked_pyRAPL.is_record_running[Device.PKG] is False + assert mocked_pyRAPL._measure[Device.PKG][1] is not None + assert mocked_pyRAPL._is_record_running[Device.PKG] is False -################################# +################################ # GET RECORDED PACKAGE ENERGY # -################################# +################################ def test_get_record_pkg_result_without_measure(rapl_fs, mocked_pyRAPL): """ try to get cpu package energy consumtpion without starting recording @@ -346,6 +383,7 @@ def test_get_record_pkg_result_without_measure(rapl_fs, mocked_pyRAPL): with pytest.raises(PyRAPLNoEnergyConsumptionRecordedException): mocked_pyRAPL.recorded_energy(Device.PKG) + def test_get_recorded_energy_bad_type_device(rapl_fs, mocked_pyRAPL): """ call the PyRAPL.recorded_energy function with a non Device argument @@ -357,21 +395,282 @@ def test_get_recorded_energy_bad_type_device(rapl_fs, mocked_pyRAPL): mocked_pyRAPL.recorded_energy(5) +def test_get_recorded_energy_bad_type_device_list(rapl_fs, mocked_pyRAPL): + """ + call the PyRAPL.recorded_energy function with a non Device argument and a Device argument + + Test if: + - A TypeError is raise + """ + mocked_pyRAPL.record(Device.PKG) + mocked_pyRAPL.stop() + with pytest.raises(TypeError): + mocked_pyRAPL.recorded_energy(Device.PKG, 5) + + def test_get_record_energy_pkg(rapl_fs, mocked_pyRAPL): """ try to get cpu package energy consumtpion recording result - create an instance of PyRAPL with the file system base_fs and run the start_record_energy_pkg and the - stop_record_energy_pkg functions after that, run the get_record_pkg_result function + create an instance of PyRAPL with the file system base_fs and run the record function and the + stop functions after that, run the recorded_energy function with Device.PKG parameter Test if: - - the returned value is an integer - - the returned value is equls to th difference between the measure[Device.PKG][0] attribute and the - measure[Device.PKG][1] attribute + - the returned value is a dictionary + - the dictionary contains the recorded energy consumption measure for package """ - mocked_pyRAPL.record([Device.PKG]) + mocked_pyRAPL.record(Device.PKG) mocked_pyRAPL.stop() - correct_value = mocked_pyRAPL.measure[Device.PKG][1] - mocked_pyRAPL.measure[Device.PKG][0] + correct_value = mocked_pyRAPL._measure[Device.PKG][1] - mocked_pyRAPL._measure[Device.PKG][0] val = mocked_pyRAPL.recorded_energy(Device.PKG) - assert isinstance(val, int) - assert val == correct_value + assert isinstance(val, dict) + assert Device.PKG in val + assert val[Device.PKG] == correct_value + + +def test_get_record_energy_all(rapl_fs_with_dram, mocked_pyRAPL): + """ + try to get all the recorded energy consumtpion recording result + + create an instance of PyRAPL with the file system base_fs and run the record function and the + stop functions after that, run the recorded_energy function + + Test if: + - the returned value is a dictionary + - the dictionary contains the recorded energy consumption measure for package and dram + """ + mocked_pyRAPL.record() + mocked_pyRAPL.stop() + correct_value = { + Device.PKG: mocked_pyRAPL._measure[Device.PKG][1] - mocked_pyRAPL._measure[Device.PKG][0], + Device.DRAM: mocked_pyRAPL._measure[Device.DRAM][1] - mocked_pyRAPL._measure[Device.DRAM][0] + } + result = mocked_pyRAPL.recorded_energy() + assert isinstance(result, dict) + for device in [Device.PKG, Device.DRAM]: + assert device in result + assert result[Device.PKG] == correct_value[device] + + +def test_get_record_energy_dram_pkg(rapl_fs_with_dram, mocked_pyRAPL): + """ + try to get dram and package energy consumtpion recording result + + create an instance of PyRAPL with the file system base_fs and run the record function and the + stop functions after that, run the recorded_energy function + + Test if: + - the returned value is a dictionary + - the dictionary contains the recorded energy consumption measure for package and dram + """ + mocked_pyRAPL.record(Device.PKG, Device.DRAM) + mocked_pyRAPL.stop() + correct_value = { + Device.PKG: mocked_pyRAPL._measure[Device.PKG][1] - mocked_pyRAPL._measure[Device.PKG][0], + Device.DRAM: mocked_pyRAPL._measure[Device.DRAM][1] - mocked_pyRAPL._measure[Device.DRAM][0] + } + result = mocked_pyRAPL.recorded_energy(Device.PKG, Device.DRAM) + assert isinstance(result, dict) + for device in [Device.PKG, Device.DRAM]: + assert device in result + assert result[Device.PKG] == correct_value[device] + + +############## +# DECORATEUR # +############## +def test_function_return_value(rapl_fs): + """ + test if the decorated function return a value + """ + @measure + def fun(): + return 1 + + assert fun() == 1 + +def test_use_parameter(rapl_fs): + """ + decorate a function that take a parameter and return it + + Test if: + - the given parameter are returned by the decorated function + """ + @measure + def fun(a): + return a + + arg = 3 + assert fun(arg) == arg + + +def test_use_two_parameter(rapl_fs): + """ + decorate a function that take two parameter and return them + + Test if: + - the given parameters are returned by the decorated function + """ + @measure + def fun(a, b): + return (a, b) + + arg1 = 3 + arg2 = 7 + assert fun(arg1, arg2) == (arg1, arg2) + + +def test_use_positional_parameter(rapl_fs): + """ + decorate a function that take positional parameter and return them + + Test if: + - the given parameters are returned by the decorated function + """ + @measure + def fun(*a): + return a + + arg1 = 3 + arg2 = 7 + assert fun(arg1, arg2) == (arg1, arg2) + + +def test_use_mutable_parameter(rapl_fs): + """ + decorate a function that take mutable parameter and modify it + + Test if: + - the given parameter is changed + """ + class Mutable: + def __init__(self, val): + self.val = val + + @measure + def fun(mut): + mut.val = 0 + + mut = Mutable(5) + assert mut.val == 5 + fun(mut) + assert mut.val == 0 + + +def test_param_decorator_monitor_pkg(rapl_fs_with_dram, mocked_pyRAPL): + """ + decorate a function to monitor package power consumption + + Test if: + - the result only contains package power consumption the ellipsed time and the functio name + """ + class Res: + def __init__(self): + self.res = None + + result = Res() + def handler(res): + result.res = res + + @measure(devices=Device.PKG, handler=handler) + def fun(): + return None + + fun() + assert len(result.res.data) == 2 + assert 'PKG' in result.res.data + assert 'TIME' in result.res.data + assert result.res.function_name == fun.__name__ + + +def test_param_decorator_monitor_pkg_dram(rapl_fs_with_dram, mocked_pyRAPL): + """ + decorate a function to monitor package and dram power consumption + + Test if: + - the result only contains package and dram power consumption, the ellipsed time and the functio name + """ + class Res: + def __init__(self): + self.res = None + + result = Res() + def handler(res): + result.res = res + + @measure(devices=[Device.PKG, Device.DRAM], handler=handler) + def fun(): + return None + + fun() + assert len(result.res.data) == 3 + assert 'PKG' in result.res.data + assert 'DRAM' in result.res.data + assert 'TIME' in result.res.data + assert result.res.function_name == fun.__name__ + + +def test_param_decorator_monitor_all(rapl_fs_with_dram, mocked_pyRAPL): + """ + decorate a function to monitor all power consumption + + Test if: + - the result only contains package and dram power consumption, the ellipsed time and the functio name + """ + class Res: + def __init__(self): + self.res = None + + result = Res() + def handler(res): + result.res = res + + @measure(handler=handler) + def fun(): + return None + + fun() + assert len(result.res.data) == 3 + assert 'PKG' in result.res.data + assert 'DRAM' in result.res.data + assert 'TIME' in result.res.data + assert result.res.function_name == fun.__name__ + + +def test_handler_call(rapl_fs, mocked_pyRAPL): + """ + decorate a function an use a mocked handler to handle the measures + + Test if: + - The mocked handle function is called with a Measure parameter + """ + mocked_handler = Mock() + @measure(handler=mocked_handler) + def fun(): + return None + + assert mocked_handler.call_count == 0 + + fun() + + assert mocked_handler.call_count == 1 + assert isinstance(mocked_handler.call_args[0][0], Measure) + + +@patch('builtins.print') +def test_handler_call(mocked_print, rapl_fs, mocked_pyRAPL): + """ + decorate a function an use a the default handler + + Test if: + - the print function was called + """ + @measure() + def fun(): + return None + + assert mocked_print.call_count == 0 + + fun() + + assert mocked_print.call_count == 3