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