power-model: Add library for reading perf counters

BUG=chromium:638746
TEST=Compile perf_test.c and run on test device

Change-Id: Ib415d8149fa3e18f7157d42704349946195d7526
diff --git a/power-model/experiment/datalog/perf.c b/power-model/experiment/datalog/perf.c
new file mode 100644
index 0000000..86454aa
--- /dev/null
+++ b/power-model/experiment/datalog/perf.c
@@ -0,0 +1,165 @@
+/*
+ * Library for reading performance counters using the kernel perf_event_open
+ * system call interface.
+ *
+ */
+
+#include <asm/unistd.h>
+#include <linux/perf_event.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <assert.h>
+
+#include "perf.h"
+
+#define PERF_MAX_COUNTERS 7
+
+struct perf_struct {
+  int cpu;
+  int n_counters;
+  int fds[PERF_MAX_COUNTERS];
+  struct perf_event_attr *attrs;
+};
+
+/* perf_event_open syscall wrapper */
+static long perf_event_open(struct perf_event_attr *hw_event, pid_t pid,
+                            int cpu, int group_fd, unsigned long flags) {
+  int ret;
+
+  ret = syscall(__NR_perf_event_open, hw_event, pid, cpu, group_fd, flags);
+  return ret;
+}
+
+/* malloc wrapper that checks for failure */
+static void *check_malloc(size_t size)
+{
+  void *p = malloc(size);
+
+  if (p == NULL) {
+    perror("malloc");
+    exit(EXIT_FAILURE);
+  }
+
+  return p;
+}
+
+/* Initialize the performance counters to count a set of events.
+ *
+ * The cycle counter is always enabled.
+ */
+struct perf_struct *perf_initialize(int cpu, int n_events, int *events) {
+  int n_counters = n_events + 1;
+
+  if (n_counters > PERF_MAX_COUNTERS) {
+    perror("Exceeded the maximum number of counters!");
+    exit(EXIT_FAILURE);
+  }
+
+  int i;
+
+  struct perf_struct *pf = check_malloc(sizeof(struct perf_struct));
+
+  pf->cpu = cpu;
+  pf->n_counters = n_counters;
+
+  for (i = 0; i < PERF_MAX_COUNTERS; i++)
+    pf->fds[i] = -1;
+
+  struct perf_event_attr *attrs =
+      check_malloc(n_counters * sizeof(struct perf_event_attr));
+  memset(attrs, 0, n_counters * sizeof(struct perf_event_attr));
+  pf->attrs = attrs;
+
+  /* Set up cycle counter */
+  attrs[0].type = PERF_TYPE_HARDWARE;
+  attrs[0].size = sizeof(struct perf_event_attr);
+  attrs[0].config = PERF_COUNT_HW_CPU_CYCLES;
+  attrs[0].read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING;
+  attrs[0].inherit = 1;
+  attrs[0].disabled = 1;
+
+  /* Set up custom counters */
+  for (i = 1; i < n_counters; i++) {
+    attrs[i].type = PERF_TYPE_RAW;
+    attrs[i].size = sizeof(struct perf_event_attr);
+    attrs[i].config = (uint64_t) events[i-1];
+    attrs[i].read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING;
+    attrs[i].inherit = 1;
+    attrs[i].disabled = 1;
+  }
+
+  for (i = 0; i < n_counters; i++) {
+    pf->fds[i] = perf_event_open(&attrs[i], -1, cpu, -1, 0);
+    if (pf->fds[i] < 0) {
+      fprintf(stderr, "At event %d/%d\n", i, n_counters);
+      perror("perf_event_open");
+      exit(EXIT_FAILURE);
+    }
+  }
+
+  return pf;
+}
+
+
+/* Close performance counters. */
+void perf_close(struct perf_struct *pf) {
+  int i;
+
+  int *fds = pf->fds;
+
+  for (i = 0; i < pf->n_counters; i++) {
+    assert(fds[i] >= 0);
+    close(fds[i]);
+  }
+
+  free(pf->attrs);
+  free(pf);
+}
+
+/* perf_event ioctl calls */
+static int perf_ioctl(struct perf_struct *pf, int counter, int request)
+{
+  assert(counter >= 0 && counter < pf->n_counters);
+  if (pf->fds[counter] == -1) {
+    fprintf(stderr, "Counter %d has not been set up.\n", counter);
+    return -1;
+  }
+
+  return ioctl(pf->fds[counter], request);
+}
+
+/* Enable performance counters. */
+int perf_enablecounter(struct perf_struct *pf, int counter) {
+  return perf_ioctl(pf, counter, PERF_EVENT_IOC_ENABLE);
+}
+
+/* Disable performance counters. */
+int perf_disablecounter(struct perf_struct *pf, int counter) {
+  return perf_ioctl(pf, counter, PERF_EVENT_IOC_DISABLE);
+}
+
+/* Reset performance counters. */
+int perf_resetcounter(struct perf_struct *pf, int counter) {
+  return perf_ioctl(pf, counter, PERF_EVENT_IOC_RESET);
+}
+
+/* Read performance counters. */
+void perf_readcounter(struct perf_struct *pf, int counter,
+                      struct perf_read_format *data) {
+  assert(counter >= 0 && counter < pf->n_counters);
+
+  assert(read(pf->fds[counter], data, sizeof(struct perf_read_format)) ==
+         sizeof(struct perf_read_format));
+}
+
+/* Read performance counters (only value field). */
+uint64_t perf_readcountervalue(struct perf_struct *pf, int counter) {
+  struct perf_read_format data;
+  perf_readcounter(pf, counter, &data);
+
+  return data.value;
+}
diff --git a/power-model/experiment/datalog/perf.h b/power-model/experiment/datalog/perf.h
new file mode 100644
index 0000000..757f764
--- /dev/null
+++ b/power-model/experiment/datalog/perf.h
@@ -0,0 +1,117 @@
+/*
+ * Library for reading performance counters using the kernel perf_event_open
+ * system call interface.
+ *
+ */
+
+#ifndef PERF_H_
+#define PERF_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+
+/* perf data structure */
+struct perf_struct;
+
+/* perf read data format */
+struct perf_read_format {
+  uint64_t value;        /* The value of the event */
+  uint64_t time_enabled; /* if PERF_FORMAT_TOTAL_TIME_ENABLED */
+  uint64_t time_running; /* if PERF_FORMAT_TOTAL_TIME_RUNNING */
+};
+
+/* perf_initialize
+ *
+ * Initialize the performance counters to count a set of events.
+ * The cycle counter is always enabled.
+ *
+ * Params:
+ *  cpu: the cpu id to monitor
+ *  n_events: number of events to monitor
+ *  events: an array of event IDs
+ *
+ * Return:
+ *  a pointer to a perf_struct data structure
+ */
+struct perf_struct *perf_initialize(int cpu, int n_events, int *events);
+
+/* perf_close
+ *
+ * Close and clean up the performance counters.
+ *
+ * Params:
+ *  pf: a pointer to perf_struct data structure
+ */
+void perf_close(struct perf_struct *pf);
+
+/* perf_enablecounter
+ *
+ * Enable performance counters.
+ *
+ * Params:
+ *  pf: a pointer to perf_struct data structure
+ *  counter: the index of the counter to enable
+ *
+ * Return:
+ *  0 on success
+ */
+int perf_enablecounter(struct perf_struct *pf, int counter);
+
+/* perf_disablecounter
+ *
+ * Disable performance counters.
+ *
+ * Params:
+ *  pf: a pointer to perf_struct data structure
+ *  counter: the index of the counter to disable
+ *
+ * Return:
+ *  0 on success
+ */
+int perf_disablecounter(struct perf_struct *pf, int counter);
+
+/* perf_resetcounter
+ *
+ * Reset performance counters.
+ *
+ * Params:
+ *  pf: a pointer to perf_struct data structure
+ *  counter: the index of the counter to reset
+ *
+ * Return:
+ *  0 on success
+ */
+int perf_resetcounter(struct perf_struct *pf, int counter);
+
+/* perf_readcounter
+ *
+ * Read performance counters.
+ *
+ * Params:
+ *  pf: a pointer to perf_struct data structure
+ *  counter: the index of the counter to read
+ */
+void perf_readcounter(struct perf_struct *pf, int counter,
+                      struct perf_read_format *data);
+
+/* perf_readcountervalue
+ *
+ * Read performance counters (only value field).
+ *
+ * Params:
+ *  pf: a pointer to perf_struct data structure
+ *  counter: the index of the counter to read
+ *
+ * Return:
+ *  counter value
+ */
+uint64_t perf_readcountervalue(struct perf_struct *pf, int counter);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PERF_H_ */
diff --git a/power-model/experiment/datalog/perf_test.c b/power-model/experiment/datalog/perf_test.c
new file mode 100644
index 0000000..1bb11d7
--- /dev/null
+++ b/power-model/experiment/datalog/perf_test.c
@@ -0,0 +1,58 @@
+#define __STDC_FORMAT_MACROS
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "perf.h"
+
+#if !defined(__arm__)
+#error This code can only be compiled for ARM architecture.
+#endif
+
+#define NR_EVENTS 4
+
+int main(int argc, char *argv[]) {
+  int cpu;
+
+  if (argc < 2)
+    cpu = 0;
+  else
+    cpu = atoi(argv[1]);
+
+  int events[NR_EVENTS] = { 0x08, 0x14, 0x04, 0x03 };
+
+  char* event_names[NR_EVENTS + 1] = {
+    "Cycle count",
+    "INST_RETIRED",
+    "L1I_CACHE",
+    "L1D_CACHE",
+    "L1D_CACHE_REFILL"
+  };
+
+  struct perf_struct *pf = perf_initialize(cpu, NR_EVENTS, events);
+
+  int i;
+  for (i = 0; i < NR_EVENTS + 1; i++) perf_resetcounter(pf, i);
+
+  for (i = 0; i < NR_EVENTS + 1; i++) perf_enablecounter(pf, i);
+
+  // some computation
+  char *x = malloc(128 * 1024 * 1024);
+  for (i = 0; i < 128 * 1024 * 1024; i++) x[i] = (char)i;
+
+  for (i = 0; i < NR_EVENTS + 1; i++) perf_disablecounter(pf, i);
+
+  struct perf_read_format data;
+
+  printf("                             Count    Time Enabled  Time Running\n");
+  for (i = 0; i < NR_EVENTS + 1; i++) {
+    perf_readcounter(pf, i, &data),
+    printf("%24s%12" PRIu64 " %12" PRIu64 " %12" PRIu64 "\n",
+            event_names[i], data.value, data.time_enabled, data.time_running);
+  }
+
+  perf_close(pf);
+  free(x);
+
+  return 0;
+}