power-model: Add perf events recorder

The recorder periodically samples perf counters and records
the counts in a CSV file.

BUG=chromium:638746
TEST=Compile datalog-recorder.c and run on test device

Change-Id: If20684352f5d4a997c62341abfc4375f61379ef2
diff --git a/power-model/experiment/datalog/Makefile b/power-model/experiment/datalog/Makefile
new file mode 100644
index 0000000..198306d
--- /dev/null
+++ b/power-model/experiment/datalog/Makefile
@@ -0,0 +1,26 @@
+CC = $(CROSS_COMPILE)gcc
+CXX = $(CROSS_COMPILE)g++
+
+SRCS = $(wildcard *.c)
+OBJS = $(patsubst %.c,%.o,$(SRCS))
+
+CFLAGS = -g
+
+all: datalog-recorder
+
+datalog-recorder: datalog-recorder.o perf.o
+	$(CC) -o $@ $^ -lrt
+
+power-monitor: power-monitor.o perf.o dvfs.o
+	$(CC) -o $@ $^ -lrt
+
+perf_test: perf_test.o perf.o
+	$(CC) -o $@ $^
+
+test: perf_test
+	taskset -c 0 ./perf_test 0
+
+clean:
+	rm -f $(OBJS) datalog-recorder perf_test power-monitor
+
+.PHONY: clean
diff --git a/power-model/experiment/datalog/datalog-recorder.c b/power-model/experiment/datalog/datalog-recorder.c
new file mode 100644
index 0000000..c0eb8c1
--- /dev/null
+++ b/power-model/experiment/datalog/datalog-recorder.c
@@ -0,0 +1,324 @@
+#define __STDC_FORMAT_MACROS
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <time.h>
+#include <signal.h>
+#include <unistd.h>
+#include <sched.h>
+
+#include "perf.h"
+
+#if !defined(__arm__)
+#error This code can only be compiled for ARM architecture.
+#endif
+
+#define LOG_HEADER "datalog     : "
+
+/* Maximum number of custom-defined events supported by ARM PMUv3 */
+#define MAX_CUSTOM_EVENTS 6
+
+/* Events to record for a PMU */
+typedef struct {
+  int nr_events;
+  int nr_unique_events;
+  int events[MAX_CUSTOM_EVENTS];
+  int unique_events[MAX_CUSTOM_EVENTS];
+  int event_pointer[MAX_CUSTOM_EVENTS];
+} pmu_events_t;
+
+/* 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;
+}
+
+/* Print event codes */
+static void print_events(int n_events, int events[]) {
+  int i;
+  for (i = 0; i < n_events; i++) {
+    printf("0x%02x", events[i]);
+    if (i != n_events - 1) printf(", ");
+  }
+  putchar('\n');
+}
+
+/* Read perf counters for a core and append to output file. */
+static void appendCounts(FILE *out, struct perf_struct *pf,
+                         int nr_events, int nr_unique, int *event_ptr) {
+  struct perf_read_format data;
+  uint64_t counts[MAX_CUSTOM_EVENTS + 1];
+
+  int i;
+  double dutyCycle;
+
+  static int warned;
+
+  for (i = 0; i < nr_unique + 1; i++) {
+    perf_readcounter(pf, i, &data);
+    if (data.time_running < data.time_enabled) {
+      if (warned == 0) {
+        fprintf(stderr, "warn: PMU is overcommitted and multiplexing "
+                "is happening\n");
+        warned = 1;
+      }
+      dutyCycle = (double) data.time_running / (double) data.time_enabled;
+      counts[i] = (uint64_t)((double) data.value / dutyCycle);
+    } else {
+      counts[i] = data.value;
+    }
+  }
+
+  for (i = 0; i < nr_events + 1; i++) {
+    fprintf(out, "\t%" PRIu64, i == 0 ? counts[0] : counts[event_ptr[i-1]+1]);
+  }
+}
+
+/* Dedup the set of events to count.
+ *
+ * The CPU cycles event is treated as an duplicate of the always-on
+ * cycle counter.
+ */
+static int dedupEvents(int nr_events, int *events,
+                       int *unique_events, int *event_ptr) {
+  int i, j, k = 0;
+  for (i = 0; i < nr_events; i++) {
+    /* cycle counter */
+    if (events[i] == 0x11) {
+      event_ptr[i] = -1;
+      continue;
+    }
+    for (j = 0; j < k; j++) {
+      if (events[i] == unique_events[j])
+        break;
+    }
+    if (j < k) {
+      event_ptr[i] = j;
+    } else {
+      event_ptr[i] = k;
+      unique_events[k++] = events[i];
+    }
+  }
+  return k;
+}
+
+/* Initialize the performance counters to count a set of events.
+ *
+ * The cycle counter is always enabled.
+ */
+static void setupPerfEvents(struct perf_struct *pfs[], int cpu_begin,
+                            int cpu_end, int n_events, int *events) {
+  int i;
+  for (i = cpu_begin; i < cpu_end; i++)
+    pfs[i] = perf_initialize(i, n_events, events);
+}
+
+/* Write output CSV file header */
+void writeCSVHeader(FILE *out, int n_cores, pmu_events_t events[]) {
+  int i, j;
+  fprintf(out, "Sample Count\tDelta Time\tTime Stamp\tTime Milliseconds");
+  for (i = 0; i < n_cores; i++) {
+    fprintf(out, "\tCore %d CycleCount", i);
+    for (j = 0; j < events[i].nr_events; j++)
+      fprintf(out, "\tCore %d Event 0x%02x", i, events[i].events[j]);
+  }
+  fprintf(out, "\n");
+  fflush(out);
+}
+
+/* Signal handler */
+static int done;
+
+void signalHandler(int signo)
+{
+  if (signo == SIGINT || signo == SIGTERM)
+    done = 1;
+}
+
+/* Show usage information */
+static void showUsage()
+{
+  printf("\nUsage:\n"
+         " datalog-recorder [options] [[<N_EVENTS> <E0> <E1> ...] ...]\n\n"
+         "Options:\n"
+         "  -o OUT_FILENAME   Output file name\n"
+         "  -i INTERVAL       Interval between measurements (microseconds)\n");
+}
+
+/* Main */
+int main(int argc, char *argv[]) {
+  char *out_filename = "data.csv";
+  int interval = 1000000;
+  int c;
+  int i, j;
+
+  /* detect the number of CPUs */
+  long n_cores = sysconf(_SC_NPROCESSORS_ONLN);
+
+  pmu_events_t *events = calloc(n_cores, sizeof(pmu_events_t));
+  if (events == NULL) {
+    perror("calloc failed");
+    exit(EXIT_FAILURE);
+  }
+
+  while ((c = getopt(argc, argv, "o:i:")) != -1)
+    switch (c)
+      {
+      case 'o':
+        out_filename = optarg;
+        break;
+      case 'i':
+        interval = atoi(optarg);
+        break;
+      case '?':
+        showUsage();
+        return 1;
+      default:
+        abort();
+      }
+
+  /* Parse events from command line */
+  i = optind;
+  int core_id = 0;
+  while (i < argc) {
+    if (core_id >= n_cores) {
+      fprintf(stderr, "More cores specified than in the system.\n");
+      exit(EXIT_FAILURE);
+    }
+    int nr_events = atoi(argv[i]);
+    if (nr_events < 0 || nr_events > MAX_CUSTOM_EVENTS ||
+        i + nr_events >= argc) {
+      fprintf(stderr, "Incorrect number of events specified.\n");
+      exit(EXIT_FAILURE);
+    }
+    events[core_id].nr_events = nr_events;
+    for (j = 0; j < nr_events; j++)
+      events[core_id].events[j] = (int) strtol(argv[i + 1 + j], NULL, 16);
+    core_id++;
+    i += nr_events + 1;
+  }
+
+  printf(LOG_HEADER "Output file: %s\n", out_filename);
+  printf(LOG_HEADER "Sampling interval: %d\n", interval);
+
+  /* dedup events */
+  for (core_id = 0; core_id < n_cores; core_id++) {
+    events[core_id].nr_unique_events = dedupEvents(
+        events[core_id].nr_events,
+        events[core_id].events,
+        events[core_id].unique_events,
+        events[core_id].event_pointer);
+    printf(LOG_HEADER "Found %d unique events for core %d\n",
+           events[core_id].nr_unique_events, core_id);
+  }
+
+  /* write output CSV header */
+  FILE *out = fopen(out_filename, "w");
+  if (!out) {
+    perror(LOG_HEADER "Fail to open output file");
+    exit(EXIT_FAILURE);
+  }
+  writeCSVHeader(out, n_cores, events);
+
+  printf(LOG_HEADER "Starting recording...\n");
+  printf(LOG_HEADER "Setting up PMU events...\n");
+  struct perf_struct **pfs =
+      check_malloc(n_cores * sizeof(struct perf_struct *));
+
+  for (i = 0; i < n_cores; i++) {
+    printf(LOG_HEADER "Setting events for core %d: ", i);
+    print_events(events[i].nr_unique_events, events[i].unique_events);
+    setupPerfEvents(pfs, i, i + 1, events[i].nr_unique_events,
+                    events[i].unique_events);
+  }
+
+  unsigned long n = 0;
+
+  struct timeval currentTime, lastTime;
+  double elapsedTime;
+  gettimeofday(&lastTime, NULL);
+
+  /* enable performance counters */
+  for (i = 0; i < n_cores; i++) {
+    int b = events[i].nr_unique_events;
+    for (j = 0; j < b + 1; j++) {
+      perf_resetcounter(pfs[i], j);
+      perf_enablecounter(pfs[i], j);
+    }
+  }
+
+  printf(LOG_HEADER "Start measurements...\n");
+
+  /* Register signal handler */
+  if (signal(SIGINT, signalHandler) == SIG_ERR ||
+      signal(SIGTERM, signalHandler) == SIG_ERR) {
+    perror("Cannot register signal handler");
+    exit(EXIT_FAILURE);
+  }
+
+  /* main loop */
+  while (!done) {
+    /* get time stamp */
+    time_t t = time(NULL);
+    struct tm tm = *localtime(&t);
+    char timeStamp[64];
+    sprintf(timeStamp, "%d-%d-%d %d:%d:%d", tm.tm_year + 1900, tm.tm_mon + 1,
+            tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
+
+    /* get sample time for last period */
+    gettimeofday(&currentTime, NULL);
+    elapsedTime = (currentTime.tv_sec - lastTime.tv_sec) * 1000.0;  /* sec to ms */
+    elapsedTime +=
+        (currentTime.tv_usec - lastTime.tv_usec) / 1000.0;  /* us to ms */
+    elapsedTime /= 1000.0;
+    lastTime = currentTime;
+
+    struct timespec milt;
+    clock_gettime(CLOCK_REALTIME, &milt);
+    int64_t millitime = milt.tv_sec * INT64_C(1000) + milt.tv_nsec / 1000000;
+
+    fprintf(out, "%lu\t%f\t%s\t%f", n, elapsedTime, timeStamp,
+            (double)millitime);
+
+    /* get perf counts */
+    for (i = 0; i < n_cores; i++)
+      appendCounts(out, pfs[i], events[i].nr_events,
+                   events[i].nr_unique_events, events[i].event_pointer);
+
+    fprintf(out, "\n");
+    fflush(out);
+
+    if (n % 100 == 0) printf(LOG_HEADER "Sample count: %lu\n", n);
+    n++;
+
+    usleep(interval);
+  }
+
+  /* disable performance counters */
+  for (i = 0; i < n_cores; i++) {
+    int b = events[i].nr_unique_events;
+    for (j = 0; j < b + 1; j++) {
+      perf_disablecounter(pfs[i], j);
+    }
+  }
+
+  /* close performance counters and free data structures */
+  for (i = 0; i < n_cores; i++)
+    perf_close(pfs[i]);
+  free(pfs);
+  free(events);
+
+  fclose(out);
+
+  printf(LOG_HEADER "Finished :)\n");
+}