diff --git a/wa/framework/configuration/parsers.py b/wa/framework/configuration/parsers.py
index b537abc6..34c3f362 100644
--- a/wa/framework/configuration/parsers.py
+++ b/wa/framework/configuration/parsers.py
@@ -186,7 +186,7 @@ def pop_aliased_param(cfg_point, d, default=None):
     aliases = [cfg_point.name] + cfg_point.aliases
     alias_map = [a for a in aliases if a in d]
     if len(alias_map) > 1:
-        raise ConfigError(DUPLICATE_ENTRY_ERROR.format(aliases))
+        raise ConfigError('Duplicate entry: {}'.format(aliases))
     elif alias_map:
         return d.pop(alias_map[0])
     else:
diff --git a/wa/instrumentation/energy_measurement.py b/wa/instrumentation/energy_measurement.py
new file mode 100644
index 00000000..5c63b7b2
--- /dev/null
+++ b/wa/instrumentation/energy_measurement.py
@@ -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')
diff --git a/wa/instrumentation/misc.py b/wa/instrumentation/misc.py
index ea6fda39..a5e455b9 100644
--- a/wa/instrumentation/misc.py
+++ b/wa/instrumentation/misc.py
@@ -85,18 +85,18 @@ class SysfsExtractor(Instrument):
     ]
 
     def initialize(self, context):
-        if not self.device.is_rooted and self.use_tmpfs:  # pylint: disable=access-member-before-definition
+        if not self.target.is_rooted and self.use_tmpfs:  # pylint: disable=access-member-before-definition
             raise ConfigError('use_tempfs must be False for an unrooted device.')
         elif self.use_tmpfs is None:  # pylint: disable=access-member-before-definition
-            self.use_tmpfs = self.device.is_rooted
+            self.use_tmpfs = self.target.is_rooted
 
         if self.use_tmpfs:
-            self.on_device_before = self.device.path.join(self.tmpfs_mount_point, 'before')
-            self.on_device_after = self.device.path.join(self.tmpfs_mount_point, 'after')
+            self.on_device_before = self.target.path.join(self.tmpfs_mount_point, 'before')
+            self.on_device_after = self.target.path.join(self.tmpfs_mount_point, 'after')
 
-            if not self.device.file_exists(self.tmpfs_mount_point):
-                self.device.execute('mkdir -p {}'.format(self.tmpfs_mount_point), as_root=True)
-                self.device.execute(self.mount_command.format(self.tmpfs_size, self.tmpfs_mount_point),
+            if not self.target.file_exists(self.tmpfs_mount_point):
+                self.target.execute('mkdir -p {}'.format(self.tmpfs_mount_point), as_root=True)
+                self.target.execute(self.mount_command.format(self.tmpfs_size, self.tmpfs_mount_point),
                                     as_root=True)
 
     def setup(self, context):
@@ -116,62 +116,62 @@ class SysfsExtractor(Instrument):
 
         if self.use_tmpfs:
             for d in self.paths:
-                before_dir = self.device.path.join(self.on_device_before,
-                                                   self.device.path.dirname(as_relative(d)))
-                after_dir = self.device.path.join(self.on_device_after,
-                                                  self.device.path.dirname(as_relative(d)))
-                if self.device.file_exists(before_dir):
-                    self.device.execute('rm -rf  {}'.format(before_dir), as_root=True)
-                self.device.execute('mkdir -p {}'.format(before_dir), as_root=True)
-                if self.device.file_exists(after_dir):
-                    self.device.execute('rm -rf  {}'.format(after_dir), as_root=True)
-                self.device.execute('mkdir -p {}'.format(after_dir), as_root=True)
+                before_dir = self.target.path.join(self.on_device_before,
+                                                   self.target.path.dirname(as_relative(d)))
+                after_dir = self.target.path.join(self.on_device_after,
+                                                  self.target.path.dirname(as_relative(d)))
+                if self.target.file_exists(before_dir):
+                    self.target.execute('rm -rf  {}'.format(before_dir), as_root=True)
+                self.target.execute('mkdir -p {}'.format(before_dir), as_root=True)
+                if self.target.file_exists(after_dir):
+                    self.target.execute('rm -rf  {}'.format(after_dir), as_root=True)
+                self.target.execute('mkdir -p {}'.format(after_dir), as_root=True)
 
     def slow_start(self, context):
         if self.use_tmpfs:
             for d in self.paths:
-                dest_dir = self.device.path.join(self.on_device_before, as_relative(d))
+                dest_dir = self.target.path.join(self.on_device_before, as_relative(d))
                 if '*' in dest_dir:
-                    dest_dir = self.device.path.dirname(dest_dir)
-                self.device.execute('{} cp -Hr {} {}'.format(self.device.busybox, d, dest_dir),
+                    dest_dir = self.target.path.dirname(dest_dir)
+                self.target.execute('{} cp -Hr {} {}'.format(self.target.busybox, d, dest_dir),
                                     as_root=True, check_exit_code=False)
         else:  # not rooted
             for dev_dir, before_dir, _, _ in self.device_and_host_paths:
-                self.device.pull(dev_dir, before_dir)
+                self.target.pull(dev_dir, before_dir)
 
     def slow_stop(self, context):
         if self.use_tmpfs:
             for d in self.paths:
-                dest_dir = self.device.path.join(self.on_device_after, as_relative(d))
+                dest_dir = self.target.path.join(self.on_device_after, as_relative(d))
                 if '*' in dest_dir:
-                    dest_dir = self.device.path.dirname(dest_dir)
-                self.device.execute('{} cp -Hr {} {}'.format(self.device.busybox, d, dest_dir),
+                    dest_dir = self.target.path.dirname(dest_dir)
+                self.target.execute('{} cp -Hr {} {}'.format(self.target.busybox, d, dest_dir),
                                     as_root=True, check_exit_code=False)
         else:  # not using tmpfs
             for dev_dir, _, after_dir, _ in self.device_and_host_paths:
-                self.device.pull(dev_dir, after_dir)
+                self.target.pull(dev_dir, after_dir)
 
     def update_result(self, context):
         if self.use_tmpfs:
-            on_device_tarball = self.device.path.join(self.device.working_directory, self.tarname)
-            on_host_tarball = self.device.path.join(context.output_directory, self.tarname)
-            self.device.execute('{} tar czf {} -C {} .'.format(self.device.busybox,
+            on_device_tarball = self.target.path.join(self.target.working_directory, self.tarname)
+            on_host_tarball = self.target.path.join(context.output_directory, self.tarname)
+            self.target.execute('{} tar czf {} -C {} .'.format(self.target.busybox,
                                                                on_device_tarball,
                                                                self.tmpfs_mount_point),
                                 as_root=True)
-            self.device.execute('chmod 0777 {}'.format(on_device_tarball), as_root=True)
-            self.device.pull(on_device_tarball, on_host_tarball)
+            self.target.execute('chmod 0777 {}'.format(on_device_tarball), as_root=True)
+            self.target.pull(on_device_tarball, on_host_tarball)
             with tarfile.open(on_host_tarball, 'r:gz') as tf:
                 tf.extractall(context.output_directory)
-            self.device.remove(on_device_tarball)
+            self.target.remove(on_device_tarball)
             os.remove(on_host_tarball)
 
         for paths in self.device_and_host_paths:
             after_dir = paths[self.AFTER_PATH]
             dev_dir = paths[self.DEVICE_PATH].strip('*')  # remove potential trailing '*'
             if (not os.listdir(after_dir) and
-                    self.device.file_exists(dev_dir) and
-                    self.device.list_directory(dev_dir)):
+                    self.target.file_exists(dev_dir) and
+                    self.target.list_directory(dev_dir)):
                 self.logger.error('sysfs files were not pulled from the device.')
                 self.device_and_host_paths.remove(paths)  # Path is removed to skip diffing it
         for _, before_dir, after_dir, diff_dir in self.device_and_host_paths:
@@ -183,19 +183,19 @@ class SysfsExtractor(Instrument):
     def finalize(self, context):
         if self.use_tmpfs:
             try:
-                self.device.execute('umount {}'.format(self.tmpfs_mount_point), as_root=True)
+                self.target.execute('umount {}'.format(self.tmpfs_mount_point), as_root=True)
             except (TargetError, CalledProcessError):
                 # assume a directory but not mount point
                 pass
-            self.device.execute('rm -rf {}'.format(self.tmpfs_mount_point),
+            self.target.execute('rm -rf {}'.format(self.tmpfs_mount_point),
                                 as_root=True, check_exit_code=False)
 
     def validate(self):
         if not self.tmpfs_mount_point:  # pylint: disable=access-member-before-definition
-            self.tmpfs_mount_point = self.device.path.join(self.device.working_directory, 'temp-fs')
+            self.tmpfs_mount_point = self.target.path.join(self.target.working_directory, 'temp-fs')
 
     def _local_dir(self, directory):
-        return os.path.dirname(as_relative(directory).replace(self.device.path.sep, os.sep))
+        return os.path.dirname(as_relative(directory).replace(self.target.path.sep, os.sep))
 
 
 class ExecutionTimeInstrument(Instrument):
diff --git a/wa/instrumentation/trace-cmd.py b/wa/instrumentation/trace-cmd.py
index 2f44db41..06e883ca 100644
--- a/wa/instrumentation/trace-cmd.py
+++ b/wa/instrumentation/trace-cmd.py
@@ -40,13 +40,13 @@ class TraceCmdInstrument(Instrument):
 
     name = 'trace-cmd'
     description = """
-    trace-cmd is an instrument which interacts with Ftrace Linux kernel internal
+    trace-cmd is an instrument which interacts with ftrace Linux kernel internal
     tracer
 
     From trace-cmd man page:
 
-    trace-cmd command interacts with the Ftrace tracer that is built inside the
-    Linux kernel. It interfaces with the Ftrace specific files found in the
+    trace-cmd command interacts with the ftrace tracer that is built inside the
+    Linux kernel. It interfaces with the ftrace specific files found in the
     debugfs file system under the tracing directory.
 
     trace-cmd reads a list of events it will trace, which can be specified in
@@ -54,13 +54,8 @@ class TraceCmdInstrument(Instrument):
 
         trace_events = ['irq*', 'power*']
 
-    If no event is specified in the config file, trace-cmd traces the following
-    events:
-
-        - sched*
-        - irq*
-        - power*
-        - cpufreq_interactive*
+    If no event is specified, a default set of events that are generally considered useful
+    for debugging/profiling purposes will be enabled.
 
     The list of available events can be obtained by rooting and running the
     following command line on the device ::
@@ -93,13 +88,17 @@ class TraceCmdInstrument(Instrument):
               is happening in each case from trace-cmd documentation:
               https://lwn.net/Articles/341902/.
 
-    This instrument comes with an Android trace-cmd binary that will be copied
-    and used on the device, however post-processing will be done on-host and
-    you must have trace-cmd installed and in your path. On Ubuntu systems, this
-    may be done with::
+    This instrument comes with an trace-cmd binary that will be copied and used
+    on the device, however post-processing will be, by default, done on-host and you must
+    have trace-cmd installed and in your path. On Ubuntu systems, this may be
+    done with::
 
         sudo apt-get install trace-cmd
 
+    Alternatively, you may set ``report_on_target`` parameter to ``True`` to enable on-target
+    processing (this is useful when running on non-Linux hosts, but is likely to take longer
+    and may fail on particularly resource-constrained targets).
+
     """
 
     parameters = [
@@ -114,7 +113,7 @@ class TraceCmdInstrument(Instrument):
         Parameter('functions', kind=list_of_strings,
                   global_alias='trace_functions',
                   description="""
-                  Specifies the list of functions to be traced. 
+                  Specifies the list of functions to be traced.
                   """),
         Parameter('buffer_size', kind=int, default=None,
                   global_alias='trace_buffer_size',