From 664950ca6e87f88b879d8a455aced4adadca32c1 Mon Sep 17 00:00:00 2001 From: Patrick Bellasi Date: Tue, 3 May 2016 10:59:57 +0100 Subject: [PATCH 1/4] libs/utils/energy: add support to specify a results folder Some energy probes can collect data, e.g. power samples, which are than usually used to compute the energy consumption. Collected power samples can be useful for example to plot the power consumption over time. This patch adds the required support to specify a results folder and emeter can use to collect samples data. Signed-off-by: Patrick Bellasi --- libs/utils/energy.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/libs/utils/energy.py b/libs/utils/energy.py index 150ce7fa8..3250ab2dd 100644 --- a/libs/utils/energy.py +++ b/libs/utils/energy.py @@ -19,6 +19,7 @@ import devlib import json import logging import time +import os # Default energy measurements for each board DEFAULT_ENERGY_METER = { @@ -66,11 +67,14 @@ class EnergyMeter(object): _meter = None - def __init__(self, target): + def __init__(self, target, res_dir=None): self._target = target + self._res_dir = res_dir + if not self._res_dir: + self._res_dir = '/tmp' @staticmethod - def getInstance(target, conf, force=False): + def getInstance(target, conf, force=False, res_dir=None): if not force and EnergyMeter._meter: return EnergyMeter._meter @@ -85,9 +89,11 @@ class EnergyMeter(object): return None if emeter['instrument'] == 'hwmon': - EnergyMeter._meter = HWMon(target, emeter) + EnergyMeter._meter = HWMon(target, emeter, res_dir) elif emeter['instrument'] == 'aep': - EnergyMeter._meter = Aep(target) + EnergyMeter._meter = Aep(target, res_dir) + + logging.debug('%14s - Results dir: %s', 'EnergyMeter', res_dir) return EnergyMeter._meter def sample(self): @@ -101,8 +107,8 @@ class EnergyMeter(object): class HWMon(EnergyMeter): - def __init__(self, target, hwmon_conf=None): - super(HWMon, self).__init__(target) + def __init__(self, target, hwmon_conf=None, res_dir=None): + super(HWMon, self).__init__(target, res_dir) # The HWMon energy meter self._hwmon = None @@ -214,8 +220,8 @@ class HWMon(EnergyMeter): class Aep(EnergyMeter): - def __init__(self, target): - super(Aep, self).__init__(target) + def __init__(self, target, res_dir): + super(Aep, self).__init__(target, res_dir) # Energy readings self.readings = {} -- GitLab From 704a24e2eff9bbe9abe564d96579561542fe7001 Mon Sep 17 00:00:00 2001 From: Patrick Bellasi Date: Tue, 3 May 2016 16:49:48 +0100 Subject: [PATCH 2/4] libs/utils/energy: add support for user-defined configurations The configuration of an energy meter can requires the specification of a custom set of parameters. For example, using the ARM EnergyProbe (AEP) we may want to specify the values of a shunt resistor or which channels to use. This patch allows to specify an energy meter configuration, for example: "emeter" : { "instrument" : "aep", "conf" : { 'labels' : ['LITTLE', 'BIG'], 'resistor_values' : [0.099, 0.033], 'device_entry' : '/dev/ttyACM0', } Which configures two channels with different shunt values. If an "emeter" configuration section is not specified by the user, the default one is used, when hardcoded for the specifed board. Signed-off-by: Patrick Bellasi --- libs/utils/energy.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/libs/utils/energy.py b/libs/utils/energy.py index 3250ab2dd..5da8bf47d 100644 --- a/libs/utils/energy.py +++ b/libs/utils/energy.py @@ -59,6 +59,11 @@ DEFAULT_ENERGY_METER = { # Hikey: by default use AEP 'hikey' : { 'instrument' : 'aep', + 'conf' : { + 'labels' : ['LITTLE'], + 'resistor_values' : [0.033], + 'device_entry' : '/dev/ttyACM0', + } } } @@ -79,8 +84,14 @@ class EnergyMeter(object): if not force and EnergyMeter._meter: return EnergyMeter._meter + # Initialize energy meter based on configuration + if 'emeter' in conf: + emeter = conf['emeter'] + logging.debug('%14s - using user-defined configuration', + 'EnergyMeter') + # Initialize energy probe to board default - if 'board' in conf and \ + elif 'board' in conf and \ conf['board'] in DEFAULT_ENERGY_METER: emeter = DEFAULT_ENERGY_METER[conf['board']] logging.debug('%14s - using default energy meter for [%s]', @@ -91,7 +102,7 @@ class EnergyMeter(object): if emeter['instrument'] == 'hwmon': EnergyMeter._meter = HWMon(target, emeter, res_dir) elif emeter['instrument'] == 'aep': - EnergyMeter._meter = Aep(target, res_dir) + EnergyMeter._meter = AEP(target, emeter['conf'], res_dir) logging.debug('%14s - Results dir: %s', 'EnergyMeter', res_dir) return EnergyMeter._meter @@ -218,20 +229,19 @@ class HWMon(EnergyMeter): return (clusters_nrg, nrg_file) -class Aep(EnergyMeter): - def __init__(self, target, res_dir): - super(Aep, self).__init__(target, res_dir) +class AEP(EnergyMeter): - # Energy readings - self.readings = {} + def __init__(self, target, aep_conf, res_dir): + super(AEP, self).__init__(target, res_dir) # Time (start and diff) for power measurment self.time = {} - # Initialize instrument - # Only one channel (first AEP channel: pc1 ... probe channel 1) is used - self._aep = devlib.EnergyProbeInstrument(self._target, labels=["pc1"], resistor_values=[0.033]) + # Configure channels for energy measurements + logging.info('%14s - AEP configuration', 'EnergyMeter') + logging.info('%14s - %s', 'EnergyMeter', aep_conf) + self._aep = devlib.EnergyProbeInstrument(self._target, **aep_conf) # Configure channels for energy measurements logging.debug('EnergyMeter - Enabling channels') -- GitLab From 7b56cef545b03a60c2a9ff63f651663be2107974 Mon Sep 17 00:00:00 2001 From: Patrick Bellasi Date: Tue, 3 May 2016 16:57:59 +0100 Subject: [PATCH 3/4] libs/utils/energy: AEP: add multi-channel support The ARM EnergyProbe (AEP) allows to monitor up to 3 channels but the current integration in LISA allows to sample only one channel. This patch removes that limitation and it allows to sample and report energy for all the available channels which have been configured by the user. A new EnergyCounter named tuple is used to keep track of the power and energy values computed for each channel. Moreover, the collected CSV file containing all the collected samples is saved in the results folder, named "samples.csv". This file can be used for example to plot and/or post process the collected energy values. Signed-off-by: Patrick Bellasi --- libs/utils/energy.py | 72 ++++++++++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/libs/utils/energy.py b/libs/utils/energy.py index 5da8bf47d..c0332e838 100644 --- a/libs/utils/energy.py +++ b/libs/utils/energy.py @@ -21,6 +21,8 @@ import logging import time import os +from collections import namedtuple + # Default energy measurements for each board DEFAULT_ENERGY_METER = { @@ -229,12 +231,16 @@ class HWMon(EnergyMeter): return (clusters_nrg, nrg_file) +EnergyCounter = namedtuple('EnergyCounter', ['site', 'pwr_total' , 'pwr_samples', 'pwr_avg', 'time', 'nrg']) class AEP(EnergyMeter): def __init__(self, target, aep_conf, res_dir): super(AEP, self).__init__(target, res_dir) + # Energy channels + self.channels = [] + # Time (start and diff) for power measurment self.time = {} @@ -250,67 +256,73 @@ class AEP(EnergyMeter): # Logging enabled channels logging.info('%14s - Channels selected for energy sampling:\n%s', 'EnergyMeter', str(self._aep.active_channels)) + logging.debug('%14s - Results dir: %s', 'EnergyMeter', self._res_dir) - def __calc_nrg(self, samples): - - power = {'sum' : 0, 'count' : 0, 'avg' : 0} - - for s in samples: - power['sum'] += s[1].value # s[1] ... power value of channel 1 - power['count'] += 1 + def _get_energy(self, samples, time, idx, site): + pwr_total = 0 + pwr_samples = 0 + for sample in samples: + pwr_total += sample[idx].value + pwr_samples += 1 - power['avg'] = power['sum'] / power['count'] + pwr_avg = pwr_total / pwr_samples + nrg = pwr_avg * time - nrg = power['avg'] * self.time['diff'] + ec = EnergyCounter(site, pwr_total, pwr_samples, pwr_avg, time, nrg) + return ec - logging.debug('avg power: %.6f count: %s time: %.6f nrg: %.6f', - power['avg'], power['count'], self.time['diff'] , nrg) - return nrg - - def sample(self): + def _sample(self, csv_file): if self._aep is None: return self.time['diff'] = time.time() - self.time['start'] self._aep.stop() - csv_data = self._aep.get_data("/tmp/aep.csv") + csv_data = self._aep.get_data(csv_file) samples = csv_data.measurements() - value = self.__calc_nrg(samples) - - self.readings['last'] = value - self.readings['delta'] = value - self.readings['total'] = value + # Calculate energy for each channel + for idx,channel in enumerate(csv_data.channels): + if channel.kind is not 'power': + continue + ec = self._get_energy(samples, self.time['diff'], idx, channel.site) + logging.debug('%14s - CH[%s] Power: %.6f (samples: %d, time: %.6f), avg: %.f6', + 'EnergyMeter', channel.site, ec.pwr_total, + ec.pwr_samples, ec.time, ec.pwr_avg) + logging.debug('%14s - CH[%s] Estimated energy: %.6f', + 'EnergyMeter', channel.site, ec.nrg) + self.channels.append(ec) - logging.debug('SAMPLE: %s', self.readings) - return self.readings + logging.debug('%14s - SAMPLE: %s', 'EnergyMeter', self.channels) def reset(self): if self._aep is None: return - logging.debug('RESET: %s', self.readings) + self.channels = [] + logging.debug('RESET: %s', self.channels) self._aep.start() self.time['start'] = time.time() - def report(self, out_dir, out_file='energy.json'): + def report(self, out_dir, out_energy='energy.json', out_samples='samples.csv'): if self._aep is None: return # Retrieve energy consumption data - nrg = self.sample() + csv_file = '{}/{}'.format(out_dir, out_samples) + self._sample(csv_file) # Reformat data for output generation - clusters_nrg = {} - clusters_nrg['LITTLE'] = '{:.6f}'.format(self.readings['total']) + channels_nrg = {} + for channel in self.channels: + channels_nrg[channel.site] = '{:.6f}'.format(channel.nrg) # Dump data as JSON file - nrg_file = '{}/{}'.format(out_dir, out_file) + nrg_file = '{}/{}'.format(out_dir, out_energy) with open(nrg_file, 'w') as ofile: - json.dump(clusters_nrg, ofile, sort_keys=True, indent=4) + json.dump(channels_nrg, ofile, sort_keys=True, indent=4) - return (clusters_nrg, nrg_file) + return (channels_nrg, nrg_file) # vim :set tabstop=4 shiftwidth=4 expandtab -- GitLab From d7735b2f858f38e28e7b1410a7eb580a44520fd3 Mon Sep 17 00:00:00 2001 From: Patrick Bellasi Date: Tue, 3 May 2016 11:02:59 +0100 Subject: [PATCH 4/4] libs/utils/env: init the result folder for energy probes Energy probes can collect temporary data, this patch ensure that the test environment results folder is configured as output folder for all the energy probes. We do that by using the internal _init_energy() function. Signed-off-by: Patrick Bellasi --- libs/utils/env.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libs/utils/env.py b/libs/utils/env.py index aec553a9c..e99d5adc8 100644 --- a/libs/utils/env.py +++ b/libs/utils/env.py @@ -184,10 +184,6 @@ class TestEnv(ShareState): # Initialize FTrace events collection self._init_ftrace(True) - # Initialize energy probe instrument - self.emeter = EnergyMeter.getInstance( - self.target, self.conf, force=True) - # Initialize RT-App calibration values self.calibration() @@ -215,6 +211,9 @@ class TestEnv(ShareState): os.remove(res_lnk) os.symlink(self.res_dir, res_lnk) + # Initialize energy probe instrument + self._init_energy(True) + logging.info('%14s - Set results folder to:', 'TestEnv') logging.info('%14s - %s', 'TestEnv', self.res_dir) logging.info('%14s - Experiment results available also in:', 'TestEnv') @@ -525,7 +524,8 @@ class TestEnv(ShareState): def _init_energy(self, force): # Initialize energy probe to board default - self.emeter = EnergyMeter.getInstance(self.target, self.conf, force) + self.emeter = EnergyMeter.getInstance(self.target, self.conf, force, + self.res_dir) def _init_platform_bl(self): self.platform = { @@ -767,7 +767,7 @@ class TestEnv(ShareState): self._init_ftrace(force) # Initialize energy probe instrument - self.emeter = EnergyMeter.getInstance(self.target, self.conf, force) + self._init_energy(force) def install_kernel(self, tc, reboot=False): -- GitLab