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(¤tTime, 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");
+}