diff --git a/wa/instrumentation/poller/Makefile b/wa/instrumentation/poller/Makefile
new file mode 100644
index 00000000..4821cba2
--- /dev/null
+++ b/wa/instrumentation/poller/Makefile
@@ -0,0 +1,17 @@
+# CROSS_COMPILE=aarch64-linux-gnu- make
+#
+CC=gcc
+
+ifdef DEBUG
+	CFLAGS=-static -lc -g
+else
+	CFLAGS=-static -lc -O2
+endif
+
+poller: poller.c
+	$(CROSS_COMPILE)$(CC) $(CFLAGS) poller.c -o poller
+
+clean:
+	rm -rf poller
+
+.PHONY: clean
diff --git a/wa/instrumentation/poller/__init__.py b/wa/instrumentation/poller/__init__.py
new file mode 100644
index 00000000..06538cba
--- /dev/null
+++ b/wa/instrumentation/poller/__init__.py
@@ -0,0 +1,122 @@
+#    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.
+# pylint: disable=access-member-before-definition,attribute-defined-outside-init,unused-argument
+import os
+
+from wa import Instrument, Parameter, Executable
+from wa.framework.exception import ConfigError, InstrumentError
+from wa.utils.types import list_or_string
+
+
+class FilePoller(Instrument):
+    name = 'file_poller'
+    description = """
+    Polls the given files at a set sample interval. The values are output in CSV format.
+
+    This instrument places a file called poller.csv in each iterations result directory.
+    This file will contain a timestamp column which will be in uS, the rest of the columns
+    will be the contents of the polled files at that time.
+
+    This instrument will strip any commas or new lines for the files' values
+    before writing them.
+    """
+
+    parameters = [
+        Parameter('sample_interval', kind=int, default=1000,
+                  description="""The interval between samples in mS."""),
+        Parameter('files', kind=list_or_string, mandatory=True,
+                  description="""A list of paths to the files to be polled"""),
+        Parameter('labels', kind=list_or_string,
+                  description="""A list of lables to be used in the CSV output for
+                                 the corresponding files. This cannot be used if
+                                 a `*` wildcard is used in a path."""),
+        Parameter('as_root', kind=bool, default=False,
+                  description="""
+                  Whether or not the poller will be run as root. This should be
+                  used when the file you need to poll can only be accessed by root.
+                  """),
+    ]
+
+    def validate(self):
+        if not self.files:
+            raise ConfigError('You must specify atleast one file to poll')
+        if self.labels and any(['*' in f for f in self.files]):
+            raise ConfigError('You cannot used manual labels with `*` wildcards')
+
+    def initialize(self, context):
+        if not self.target.is_rooted and self.as_root:
+            raise ConfigError('The target is not rooted, cannot run poller as root.')
+        host_poller = context.resolver.get(Executable(self, self.target.abi,
+                                                      "poller"))
+        target_poller = self.target.install(host_poller)
+
+        expanded_paths = []
+        for path in self.files:
+            if "*" in path:
+                for p in self.target.list_directory(path):
+                    expanded_paths.append(p)
+            else:
+                expanded_paths.append(path)
+        self.files = expanded_paths
+        if not self.labels:
+            self.labels = self._generate_labels()
+
+        self.target_output_path = self.target.path.join(self.target.working_directory, 'poller.csv')
+        self.target_log_path = self.target.path.join(self.target.working_directory, 'poller.log')
+        self.command = '{} -t {} -l {} {} > {} 2>{}'.format(target_poller,
+                                                            self.sample_interval * 1000,
+                                                            ','.join(self.labels),
+                                                            ' '.join(self.files),
+                                                            self.target_output_path,
+                                                            self.target_log_path)
+
+    def start(self, context):
+        self.target.kick_off(self.command, as_root=self.as_root)
+
+    def stop(self, context):
+        self.target.killall('poller', signal='TERM', as_root=self.as_root)
+
+    def update_result(self, context):
+        host_output_file = os.path.join(context.output_directory, 'poller.csv')
+        self.target.pull(self.target_output_path, host_output_file)
+        context.add_artifact('poller_output', host_output_file, kind='data')
+        host_log_file = os.path.join(context.output_directory, 'poller.log')
+        self.target.pull(self.target_log_path, host_log_file)
+        context.add_artifact('poller_log', host_log_file, kind='log')
+
+        with open(host_log_file) as fh:
+            for line in fh:
+                if 'ERROR' in line:
+                    raise InstrumentError(line.strip())
+                if 'WARNING' in line:
+                    self.logger.warning(line.strip())
+
+    def teardown(self, context):
+        self.target.remove(self.target_output_path)
+        self.target.remove(self.target_log_path)
+
+    def _generate_labels(self):
+        # Split paths into their parts
+        path_parts = [f.split(self.target.path.sep) for f in self.files]
+        # Identify which parts differ between at least two of the paths
+        differ_map = [len(set(x)) > 1 for x in zip(*path_parts)]
+
+        # compose labels from path parts that differ
+        labels = []
+        for pp in path_parts:
+            label_parts = [p for i, p in enumerate(pp[:-1])
+                           if i >= len(differ_map) or differ_map[i]]
+            label_parts.append(pp[-1])  # always use file name even if same for all
+            labels.append('-'.join(label_parts))
+        return labels
diff --git a/wa/instrumentation/poller/bin/arm64/poller b/wa/instrumentation/poller/bin/arm64/poller
new file mode 100755
index 00000000..e10898e5
Binary files /dev/null and b/wa/instrumentation/poller/bin/arm64/poller differ
diff --git a/wa/instrumentation/poller/bin/armeabi/poller b/wa/instrumentation/poller/bin/armeabi/poller
new file mode 100755
index 00000000..fb659251
Binary files /dev/null and b/wa/instrumentation/poller/bin/armeabi/poller differ
diff --git a/wa/instrumentation/poller/poller.c b/wa/instrumentation/poller/poller.c
new file mode 100644
index 00000000..b42fb79a
--- /dev/null
+++ b/wa/instrumentation/poller/poller.c
@@ -0,0 +1,163 @@
+#include <fcntl.h>
+#include <stdio.h>
+#include <sys/poll.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <string.h>
+#include <stdlib.h>
+
+volatile sig_atomic_t done = 0;
+void term(int signum)
+{
+    done = 1;
+}
+
+void strip(char *s) {
+    char *stripped_s = s;
+    while(*s != '\0') {
+        if(*s != ',' && *s != '\n') {
+            *stripped_s++ = *s++;
+        } else {
+            ++s;
+        }
+    }
+    *stripped_s = '\0';
+}
+
+typedef struct {
+        int fd;
+        char *path;
+} poll_source_t;
+
+int main(int argc, char ** argv) {
+
+    extern char *optarg;
+    extern int optind;
+    int c = 0;
+    int show_help = 0;
+    useconds_t interval = 1000000;
+    char buf[1024];
+    memset(buf, 0, sizeof(buf));
+    struct timeval current_time;
+    double time_float;
+    char *labels;
+    int labelCount = 0;
+
+    static char usage[] = "usage: %s [-h] [-t INTERVAL] FILE [FILE ...]\n"
+                          "polls FILE(s) every INTERVAL microseconds and outputs\n"
+                          "the results in CSV format including a timestamp to STDOUT\n"
+                          "\n"
+                          "    -h     Display this message\n"
+                          "    -t     The polling sample interval in microseconds\n"
+                          "           Defaults to 1000000 (1 second)\n"
+                          "    -l     Comma separated list of labels to use in the CSV\n"
+                          "           output. This should match the number of files\n";
+
+
+    //Handling command line arguments
+    while ((c = getopt(argc, argv, "ht:l:")) != -1)
+    {
+        switch(c) {
+            case 'h':
+            case '?':
+            default:
+                show_help = 1;
+                break;
+            case 't':
+                interval = (useconds_t)atoi(optarg);
+                break;
+            case 'l':
+                labels = optarg;
+                labelCount = 1;
+                int i;
+                for (i=0; labels[i]; i++)
+                    labelCount += (labels[i] == ',');
+        }
+    }
+
+    if (show_help) {
+        fprintf(stderr, usage, argv[0]);
+        exit(1);
+    }
+
+    if (optind >= argc) {
+        fprintf(stderr, "ERROR: %s: missing file path(s)\n", argv[0]);
+        fprintf(stderr, usage, argv[0]);
+        exit(1);
+    }
+
+    int num_files = argc - optind;
+    poll_source_t files_to_poll[num_files];
+
+    if (labelCount && labelCount != num_files)
+    {
+        fprintf(stderr, "ERROR: %s: %d labels specified but %d files specified\n",
+                argv[0], labelCount, num_files);
+        fprintf(stderr, usage, argv[0]);
+        exit(1);
+    }
+
+    //Print headers and open files to poll
+    printf("time");
+    if(labelCount)
+    {
+        printf(",%s", labels);
+    }
+    int i;
+    for (i = 0; i < num_files; i++)
+    {
+        files_to_poll[i].path = argv[optind + i];
+        files_to_poll[i].fd = open(files_to_poll[i].path, O_RDONLY);
+        if (files_to_poll[i].fd == -1) {
+            fprintf(stderr, "ERROR: Could not open \"%s\", got: %s\n",
+                    files_to_poll[i].path, strerror(errno));
+            exit(2);
+        }
+
+        if(!labelCount) {
+            printf(",%s", argv[optind + i]);
+        }
+    }
+    printf("\n");
+
+    //Setup SIGTERM handler
+    struct sigaction action;
+    memset(&action, 0, sizeof(struct sigaction));
+    action.sa_handler = term;
+    sigaction(SIGTERM, &action, NULL);
+
+    //Poll files 
+    int bytes_read = 0;
+    while (!done) {
+        gettimeofday(&current_time, NULL);
+        time_float = (double)current_time.tv_sec;
+        time_float += ((double)current_time.tv_usec)/1000/1000;
+        printf("%f", time_float);
+        for (i = 0; i < num_files; i++) {
+            lseek(files_to_poll[i].fd, 0, SEEK_SET);
+            bytes_read = read(files_to_poll[i].fd, buf, 1024);
+
+            if (bytes_read < 0) {
+                fprintf(stderr, "WARNING: Read nothing from \"%s\"\n",
+                        files_to_poll[i].path);
+                printf(",");
+                continue;
+            }
+
+            strip(buf);
+            printf(",%s", buf);
+            buf[0] = '\0'; // "Empty" buffer
+        }
+        printf("\n");
+        usleep(interval);
+    }
+
+    //Close files
+    for (i = 0; i < num_files; i++)
+    {
+        close(files_to_poll[i].fd);
+    }
+    exit(0);
+}