mirror of
				https://github.com/ARM-software/workload-automation.git
				synced 2025-10-30 22:54:18 +00:00 
			
		
		
		
	EnergyMeasurement: Adds a wrapper for devlib instrumentation
The instrument allows for various devlib instruments (currently only daq and energy_probe) to be used in WA to collect energy measurements.
This commit is contained in:
		
							
								
								
									
										249
									
								
								wa/instrumentation/energy_measurement.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										249
									
								
								wa/instrumentation/energy_measurement.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,249 @@ | ||||
| #    Copyright 2013-2017 ARM Limited | ||||
| # | ||||
| # 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. | ||||
| # | ||||
|  | ||||
|  | ||||
| # pylint: disable=W0613,E1101 | ||||
| from __future__ import division | ||||
| import os | ||||
| from collections import defaultdict | ||||
|  | ||||
| from devlib.instrument import CONTINUOUS | ||||
| from devlib.instrument.energy_probe import EnergyProbeInstrument | ||||
| from devlib.instrument.daq import DaqInstrument | ||||
|  | ||||
| from wa import Instrument, Parameter | ||||
| from wa.framework import pluginloader | ||||
| from wa.framework.plugin import Plugin | ||||
| from wa.framework.exception import ConfigError | ||||
| from wa.utils.types import list_of_strings, list_of_ints, list_or_string | ||||
|  | ||||
|  | ||||
| class EnergyInstrumentBackend(Plugin): | ||||
|  | ||||
|     name = None | ||||
|     kind = 'energy_instrument_backend' | ||||
|     parameters = [] | ||||
|  | ||||
|     instrument = None | ||||
|  | ||||
|     def get_parameters(self): | ||||
|         return {p.name : p for p in self.parameters} | ||||
|  | ||||
|     def validate_parameters(self, params): | ||||
|         pass | ||||
|  | ||||
|  | ||||
| class DAQBackend(EnergyInstrumentBackend): | ||||
|  | ||||
|     name = 'daq' | ||||
|  | ||||
|     parameters = [ | ||||
|         Parameter('resistor_values', kind=list_of_ints, | ||||
|                   description=""" | ||||
|                   The values of resistors (in Ohms) across which the voltages | ||||
|                   are measured on. | ||||
|                   """), | ||||
|         Parameter('labels', kind=list_of_strings, | ||||
|                   description=""" | ||||
|                   'List of port labels. If specified, the length of the list | ||||
|                   must match the length of ``resistor_values``. | ||||
|                   """), | ||||
|         Parameter('host', kind=str, default='localhost', | ||||
|                   description=""" | ||||
|                   The host address of the machine that runs the daq Server which | ||||
|                   the instrument communicates with. | ||||
|                   """), | ||||
|         Parameter('port', kind=int, default=45677, | ||||
|                   description=""" | ||||
|                   The port number for daq Server in which daq instrument | ||||
|                   communicates with. | ||||
|                   """), | ||||
|         Parameter('device_id', kind=str, default='Dev1', | ||||
|                   description=""" | ||||
|                   The ID under which the DAQ is registered with the driver. | ||||
|                   """), | ||||
|         Parameter('v_range', kind=str, default=2.5, | ||||
|                   description=""" | ||||
|                   Specifies the voltage range for the SOC voltage channel on the | ||||
|                   DAQ (please refer to :ref:`daq_setup` for details). | ||||
|                   """), | ||||
|         Parameter('dv_range', kind=str, default=0.2, | ||||
|                   description=""" | ||||
|                   Specifies the voltage range for the resistor voltage channel | ||||
|                   on the DAQ (please refer to :ref:`daq_setup` for details). | ||||
|                   """), | ||||
|         Parameter('sample_rate_hz', kind=str, default=10000, | ||||
|                   description=""" | ||||
|                   Specify the sample rate in Hz. | ||||
|                   """), | ||||
|         Parameter('channel_map', kind=list_of_ints, | ||||
|                   default=(0, 1, 2, 3, 4, 5, 6, 7, 16, 17, 18, 19, 20, 21, 22, 23), | ||||
|                   description=""" | ||||
|                   Represents mapping from  logical AI channel number to physical | ||||
|                   connector on the DAQ (varies between DAQ models). The default | ||||
|                   assumes DAQ 6363 and similar with AI channels on connectors | ||||
|                   0-7 and 16-23. | ||||
|                   """) | ||||
|         ] | ||||
|  | ||||
|     instrument = DaqInstrument | ||||
|  | ||||
|     def validate_parameters(self, params): | ||||
|         if not params.get('resistor_values'): | ||||
|             raise ConfigError('Mandatory parameter "resistor_values" is not set.') | ||||
|         if params.get('labels'): | ||||
|             if len(params.get('labels')) != len(params.get('resistor_values')): | ||||
|                 msg = 'Number of DAQ port labels does not match the number of resistor values.' | ||||
|                 raise ConfigError(msg) | ||||
|  | ||||
|  | ||||
| class EnergyProbeBackend(EnergyInstrumentBackend): | ||||
|  | ||||
|     name = 'energy_probe' | ||||
|  | ||||
|     parameters = [ | ||||
|         Parameter('resistor_values', kind=list_of_ints, | ||||
|                   description=""" | ||||
|                   The values of resistors (in Ohms) across which the voltages | ||||
|                   are measured on. | ||||
|                   """), | ||||
|         Parameter('labels', kind=list_of_strings, | ||||
|                   description=""" | ||||
|                   'List of port labels. If specified, the length of the list | ||||
|                   must match the length of ``resistor_values``. | ||||
|                   """), | ||||
|         Parameter('device_entry', kind=str, default='/dev/ttyACM0', | ||||
|                   description=""" | ||||
|                   Path to /dev entry for the energy probe (it should be /dev/ttyACMx) | ||||
|                   """), | ||||
|     ] | ||||
|  | ||||
|     instrument = EnergyProbeInstrument | ||||
|  | ||||
|     def validate_parameters(self, params): | ||||
|         if not params.get('resistor_values'): | ||||
|             raise ConfigError('Mandatory parameter "resistor_values" is not set.') | ||||
|         if params.get('labels'): | ||||
|             if len(params.get('labels')) != len(params.get('resistor_values')): | ||||
|                 msg = 'Number of Energy Probe port labels does not match the number of resistor values.' | ||||
|                 raise ConfigError(msg) | ||||
|  | ||||
|  | ||||
| class EnergyMeasurement(Instrument): | ||||
|  | ||||
|     name = 'energy_measurement' | ||||
|  | ||||
|     description = """ | ||||
|     This instrument is designed to be used as an interface to the various | ||||
|     energy measurement instruments located in devlib. | ||||
|     """ | ||||
|  | ||||
|     parameters = [ | ||||
|         Parameter('instrument', kind=str, mandatory=True, | ||||
|                   allowed_values=['daq', 'energy_probe'], | ||||
|                   description=""" | ||||
|                   Specify the energy instrumentation to be enabled. | ||||
|                   """), | ||||
|         Parameter('instrument_parameters', kind=dict, default={}, | ||||
|                    description=""" | ||||
|                    Specify the parameters used to initialize the desired | ||||
|                    instrumentation. | ||||
|                    """), | ||||
|         Parameter('sites', kind=list_or_string, default=[], | ||||
|                   description=""" | ||||
|                   Specify which sites measurements should be collected | ||||
|                   from, if not specified the measurements will be | ||||
|                   collected for all available sites. | ||||
|                   """), | ||||
|         Parameter('kinds', kind=list_or_string, default=[], | ||||
|                   description=""" | ||||
|                   Specify the kinds of measurements should be collected, | ||||
|                   if not specified measurements will be | ||||
|                   collected for all available kinds. | ||||
|                   """), | ||||
|         Parameter('channels', kind=list_or_string, default=[], | ||||
|                   description=""" | ||||
|                   Specify the channels to be collected, | ||||
|                   if not specified the measurements will be | ||||
|                   collected for all available channels. | ||||
|                   """), | ||||
|     ] | ||||
|  | ||||
|     def __init__(self, target, loader=pluginloader, **kwargs): | ||||
|         super(EnergyMeasurement, self).__init__(target, **kwargs) | ||||
|         self.instrumentation = None | ||||
|         self.measurement_csv = None | ||||
|         self.loader = loader | ||||
|         self.backend = self.loader.get_plugin(self.instrument) | ||||
|         self.params = {} | ||||
|  | ||||
|         if self.backend.instrument.mode != CONTINUOUS: | ||||
|             msg = '{} instrument does not support continuous measurement collection' | ||||
|             raise ConfigError(msg.format(self.instrument)) | ||||
|  | ||||
|         supported_params = self.backend.get_parameters() | ||||
|         for name, value in supported_params.iteritems(): | ||||
|             if name in self.instrument_parameters: | ||||
|                 self.params[name] = self.instrument_parameters[name] | ||||
|             elif value.default: | ||||
|                 self.params[name] = value.default | ||||
|         self.backend.validate_parameters(self.params) | ||||
|  | ||||
|     def initialize(self, context): | ||||
|         self.instrumentation = self.backend.instrument(self.target, **self.params) | ||||
|  | ||||
|         for channel in self.channels: | ||||
|             if not self.instrumentation.get_channels(channel): | ||||
|                 raise ConfigError('No channels found for "{}"'.format(channel)) | ||||
|  | ||||
|     def setup(self, context): | ||||
|         self.instrumentation.reset(sites=self.sites, | ||||
|                                    kinds=self.kinds, | ||||
|                                    channels=self.channels) | ||||
|  | ||||
|     def start(self, context): | ||||
|         self.instrumentation.start() | ||||
|  | ||||
|     def stop(self, context): | ||||
|         self.instrumentation.stop() | ||||
|  | ||||
|     def update_result(self, context): | ||||
|         outfile = os.path.join(context.output_directory, 'energy_instrument_output.csv') | ||||
|         self.measurement_csv = self.instrumentation.get_data(outfile) | ||||
|         context.add_artifact('energy_instrument_output', outfile, 'data') | ||||
|         self.extract_metrics(context) | ||||
|  | ||||
|     def extract_metrics(self, context): | ||||
|         measurements = self.measurement_csv.itermeasurements() | ||||
|         energy_results = defaultdict(dict) | ||||
|         power_results = defaultdict(int) | ||||
|  | ||||
|         for count, row in enumerate(measurements): | ||||
|             for entry in row: | ||||
|                 channel = entry.channel | ||||
|                 if channel.kind == 'energy': | ||||
|                     if count == 0: | ||||
|                         energy_results[channel.site]['start'] = entry.value | ||||
|                     else: | ||||
|                         energy_results[channel.site]['end'] = entry.value | ||||
|                 elif channel.kind == 'power': | ||||
|                     power_results[channel.site] += entry.value | ||||
|  | ||||
|         for site in energy_results: | ||||
|             total_energy = energy_results[site]['end'] - energy_results[site]['start'] | ||||
|             context.add_metric('{}_energy'.format(site), total_energy, 'joules') | ||||
|         for site in power_results: | ||||
|             power = power_results[site] / count + 1  #pylint: disable=undefined-loop-variable | ||||
|             context.add_metric('{}_power'.format(site), power, 'watts') | ||||
		Reference in New Issue
	
	Block a user