get_perf_stats

Leeched from https://mazzo.li/archive.html

#include <asm/unistd.h>
#include <immintrin.h>
#include <linux/hw_breakpoint.h>
#include <linux/perf_event.h>
#include <sched.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#include <unistd.h>

#include <cmath>

// --------------------------------------------------------------------
// Pin to CPU

static void pin_to_cpu(uint32_t cpu) {
  cpu_set_t cpu_mask;
  CPU_ZERO(&cpu_mask);
  CPU_SET(0, &cpu_mask);
  if (sched_setaffinity(cpu, sizeof(cpu_mask), &cpu_mask) != 0) {
    fprintf(stderr, "Could not set CPU affinity\n");
    exit(EXIT_FAILURE);
  }
}

// --------------------------------------------------------------------
// perf instrumentation -- a mixture of man 3 perf_event_open and
// <https://stackoverflow.com/a/42092180>

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;
}

static void setup_perf_event(struct perf_event_attr *evt, int *fd, uint64_t *id,
                             uint32_t evt_type, uint64_t evt_config,
                             int group_fd) {
  memset(evt, 0, sizeof(struct perf_event_attr));
  evt->type = evt_type;
  evt->size = sizeof(struct perf_event_attr);
  evt->config = evt_config;
  evt->disabled = 1;
  evt->exclude_kernel = 1;
  evt->exclude_hv = 1;
  evt->read_format = PERF_FORMAT_GROUP | PERF_FORMAT_ID;

  *fd = perf_event_open(evt, 0, -1, group_fd, 0);
  if (*fd == -1) {
    fprintf(stderr, "Error opening leader %llx\n", evt->config);
    exit(EXIT_FAILURE);
  }

  ioctl(*fd, PERF_EVENT_IOC_ID, id);
}

static struct perf_event_attr perf_cycles_evt;
static int perf_cycles_fd;
static uint64_t perf_cycles_id;

static struct perf_event_attr perf_clock_evt;
static int perf_clock_fd;
static uint64_t perf_clock_id;

static struct perf_event_attr perf_instrs_evt;
static int perf_instrs_fd;
static uint64_t perf_instrs_id;

static struct perf_event_attr perf_cache_misses_evt;
static int perf_cache_misses_fd;
static uint64_t perf_cache_misses_id;

static struct perf_event_attr perf_cache_references_evt;
static int perf_cache_references_fd;
static uint64_t perf_cache_references_id;

static struct perf_event_attr perf_branch_misses_evt;
static int perf_branch_misses_fd;
static uint64_t perf_branch_misses_id;

static struct perf_event_attr perf_branch_instructions_evt;
static int perf_branch_instructions_fd;
static uint64_t perf_branch_instructions_id;

static void perf_init(void) {
  // Cycles
  setup_perf_event(&perf_cycles_evt, &perf_cycles_fd, &perf_cycles_id,
                   PERF_TYPE_HARDWARE, PERF_COUNT_HW_CPU_CYCLES, -1);
  // Clock
  setup_perf_event(&perf_clock_evt, &perf_clock_fd, &perf_clock_id,
                   PERF_TYPE_SOFTWARE, PERF_COUNT_SW_TASK_CLOCK,
                   perf_cycles_fd);
  // Instructions
  setup_perf_event(&perf_instrs_evt, &perf_instrs_fd, &perf_instrs_id,
                   PERF_TYPE_HARDWARE, PERF_COUNT_HW_INSTRUCTIONS,
                   perf_cycles_fd);
  // Cache misses
  setup_perf_event(&perf_cache_misses_evt, &perf_cache_misses_fd,
                   &perf_cache_misses_id, PERF_TYPE_HARDWARE,
                   PERF_COUNT_HW_CACHE_MISSES, perf_cycles_fd);
  // Cache references
  setup_perf_event(&perf_cache_references_evt, &perf_cache_references_fd,
                   &perf_cache_references_id, PERF_TYPE_HARDWARE,
                   PERF_COUNT_HW_CACHE_REFERENCES, perf_cycles_fd);
  // Branch misses
  setup_perf_event(&perf_branch_misses_evt, &perf_branch_misses_fd,
                   &perf_branch_misses_id, PERF_TYPE_HARDWARE,
                   PERF_COUNT_HW_BRANCH_MISSES, perf_cycles_fd);
  // Branch instructions
  setup_perf_event(&perf_branch_instructions_evt, &perf_branch_instructions_fd,
                   &perf_branch_instructions_id, PERF_TYPE_HARDWARE,
                   PERF_COUNT_HW_BRANCH_INSTRUCTIONS, perf_cycles_fd);
}

static void perf_close(void) {
  close(perf_clock_fd);
  close(perf_cycles_fd);
  close(perf_instrs_fd);
  close(perf_cache_misses_fd);
  close(perf_cache_references_fd);
}

static void disable_perf_count(void) {
  ioctl(perf_cycles_fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP);
}

static void enable_perf_count(void) {
  ioctl(perf_cycles_fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP);
}

static void reset_perf_count(void) {
  ioctl(perf_cycles_fd, PERF_EVENT_IOC_RESET, PERF_IOC_FLAG_GROUP);
}

struct perf_read_value {
  uint64_t value;
  uint64_t id;
};

struct perf_read_format {
  uint64_t nr;
  struct perf_read_value values[];
};

static char perf_read_buf[4096];

struct perf_count {
  uint64_t cycles;
  double seconds;
  uint64_t instructions;
  uint64_t cache_misses;
  uint64_t cache_references;
  uint64_t branch_misses;
  uint64_t branch_instructions;

  perf_count()
      : cycles(0),
        seconds(0.0),
        instructions(0),
        cache_misses(0),
        cache_references(0),
        branch_misses(0),
        branch_instructions(0) {}
};

static void read_perf_count(struct perf_count *count) {
  if (!read(perf_cycles_fd, perf_read_buf, sizeof(perf_read_buf))) {
    fprintf(stderr, "Could not read cycles from perf\n");
    exit(EXIT_FAILURE);
  }
  struct perf_read_format *rf = (struct perf_read_format *)perf_read_buf;
  if (rf->nr != 7) {
    fprintf(stderr, "Bad number of perf events\n");
    exit(EXIT_FAILURE);
  }
  for (int i = 0; i < static_cast<int>(rf->nr); i++) {
    struct perf_read_value *value = &rf->values[i];
    if (value->id == perf_cycles_id) {
      count->cycles = value->value;
    } else if (value->id == perf_clock_id) {
      count->seconds = ((double)(value->value / 1000ull)) / 1000000.0;
    } else if (value->id == perf_instrs_id) {
      count->instructions = value->value;
    } else if (value->id == perf_cache_misses_id) {
      count->cache_misses = value->value;
    } else if (value->id == perf_cache_references_id) {
      count->cache_references = value->value;
    } else if (value->id == perf_branch_misses_id) {
      count->branch_misses = value->value;
    } else if (value->id == perf_branch_instructions_id) {
      count->branch_instructions = value->value;
    } else {
      fprintf(stderr, "Spurious value in perf read (%ld)\n", value->id);
      exit(EXIT_FAILURE);
    }
  }
}

template <typename T>
perf_count get_perf_stats(T&& lambda) {
  struct perf_count counts;
  static auto once = []() {
    // init perf once in program
    perf_init();
    return 0;
  }();
  (void)once;

  disable_perf_count();
  reset_perf_count();
  enable_perf_count();

  lambda();

  disable_perf_count();
  read_perf_count(&counts);

  return counts;
}

void do_something() {
    // stuff
}

int main() {
  pin_to_cpu(0);

  perf_count counts = get_perf_stats([]() { do_something(); });

  printf("cycles= %lu\n", counts.cycles);
  printf("seconds= %f\n", counts.seconds);
  printf("counts.instructions= %lu\n", counts.instructions);
  printf("cache_misses= %lu\n", counts.cache_misses);
  printf("cache_references= %lu\n", counts.cache_references);
  printf("branch_misses= %lu\n", counts.branch_misses);
  printf("branch_instructions= %lu\n", counts.branch_instructions);
}