DerivedMeasurements: Add DerivedEnergyMeasurments
Adds `DerivedMeasurements` which are designed to perform post processing on
a provided MeasurementCsv.
Currently only a `DerivedEnergyMeasurements` class has been added which
has 2 purposes:
- Calculate energy from power results if not present using recorded timestamps,
falling back to a provided sample rate
- Calculate cumulative energy and average power from a specified MeasurementCSV
file.
diff --git a/devlib/__init__.py b/devlib/__init__.py
index 2f50632..b1b4fa3 100644
--- a/devlib/__init__.py
+++ b/devlib/__init__.py
@@ -19,6 +19,9 @@
from devlib.instrument.netstats import NetstatsInstrument
from devlib.instrument.gem5power import Gem5PowerInstrument
+from devlib.derived import DerivedMeasurements
+from devlib.derived.derived_measurements import DerivedEnergyMeasurements
+
from devlib.trace.ftrace import FtraceCollector
from devlib.host import LocalConnection
diff --git a/devlib/derived/__init__.py b/devlib/derived/__init__.py
new file mode 100644
index 0000000..5689a58
--- /dev/null
+++ b/devlib/derived/__init__.py
@@ -0,0 +1,19 @@
+# Copyright 2015 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.
+#
+class DerivedMeasurements(object):
+
+ @staticmethod
+ def process(measurements_csv):
+ raise NotImplementedError()
diff --git a/devlib/derived/derived_measurements.py b/devlib/derived/derived_measurements.py
new file mode 100644
index 0000000..770db88
--- /dev/null
+++ b/devlib/derived/derived_measurements.py
@@ -0,0 +1,97 @@
+# Copyright 2013-2015 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.
+#
+from __future__ import division
+from collections import defaultdict
+
+from devlib import DerivedMeasurements
+from devlib.instrument import Measurement, MEASUREMENT_TYPES, InstrumentChannel
+
+
+class DerivedEnergyMeasurements(DerivedMeasurements):
+
+ @staticmethod
+ def process(measurements_csv):
+
+ should_calculate_energy = []
+ use_timestamp = False
+
+ # Determine sites to calculate energy for
+ channel_map = defaultdict(list)
+ for channel in measurements_csv.channels:
+ channel_map[channel].append(channel.kind)
+ for channel, kinds in channel_map.iteritems():
+ if 'power' in kinds and not 'energy' in kinds:
+ should_calculate_energy.append(channel.site)
+ if channel.site == 'timestamp':
+ use_timestamp = True
+ time_measurment = channel.measurement_type
+
+ if measurements_csv.sample_rate_hz is None and not use_timestamp:
+ msg = 'Timestamp data is unavailable, please provide a sample rate'
+ raise ValueError(msg)
+
+ if use_timestamp:
+ # Find index of timestamp column
+ ts_index = [i for i, chan in enumerate(measurements_csv.channels)
+ if chan.site == 'timestamp']
+ if len(ts_index) > 1:
+ raise ValueError('Multiple timestamps detected')
+ ts_index = ts_index[0]
+
+ row_ts = 0
+ last_ts = 0
+ energy_results = defaultdict(dict)
+ power_results = defaultdict(float)
+
+ # Process data
+ for count, row in enumerate(measurements_csv.itermeasurements()):
+ if use_timestamp:
+ last_ts = row_ts
+ row_ts = time_measurment.convert(float(row[ts_index].value), 'time')
+ for entry in row:
+ channel = entry.channel
+ site = channel.site
+ if channel.kind == 'energy':
+ if count == 0:
+ energy_results[site]['start'] = entry.value
+ else:
+ energy_results[site]['end'] = entry.value
+
+ if channel.kind == 'power':
+ power_results[site] += entry.value
+
+ if site in should_calculate_energy:
+ if count == 0:
+ energy_results[site]['start'] = 0
+ energy_results[site]['end'] = 0
+ elif use_timestamp:
+ energy_results[site]['end'] += entry.value * (row_ts - last_ts)
+ else:
+ energy_results[site]['end'] += entry.value * (1 /
+ measurements_csv.sample_rate_hz)
+
+ # Calculate final measurements
+ derived_measurements = []
+ for site in energy_results:
+ total_energy = energy_results[site]['end'] - energy_results[site]['start']
+ instChannel = InstrumentChannel('cum_energy', site, MEASUREMENT_TYPES['energy'])
+ derived_measurements.append(Measurement(total_energy, instChannel))
+
+ for site in power_results:
+ power = power_results[site] / (count + 1) #pylint: disable=undefined-loop-variable
+ instChannel = InstrumentChannel('avg_power', site, MEASUREMENT_TYPES['power'])
+ derived_measurements.append(Measurement(power, instChannel))
+
+ return derived_measurements