/* Copyright (C) 2003,2004 Andi Kleen, SuSE Labs.

   numamon is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public
   License as published by the Free Software Foundation; version
   2.

   numamon is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should find a copy of v2 of the GNU General Public License somewhere
   on your Linux system; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

   Display some numa statistics collected by the CPU.
   Opteron specific. Also not reliable because the counters
   are not quite correct in hardware.  */

#define _LARGE_FILE_SOURCE 1
#define _GNU_SOURCE 1
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <dirent.h>
#include <getopt.h>
#include <stdarg.h>
#include <stdlib.h>
#include <sys/fcntl.h>

enum { LOCALLOCAL = 0, LOCALREMOTE = 1, REMOTELOCAL = 2 };
static int mem[] = { [LOCALLOCAL] = 0xa8, [LOCALREMOTE] = 0x98, [REMOTELOCAL] = 0x68 };
static int io[] = {  [LOCALLOCAL] = 0xa4, [LOCALREMOTE] = 0x94, [REMOTELOCAL] = 0x64 };
static int *masks = mem;

#define err(x) perror(x),exit(1)

#define PERFEVTSEL0 0xc0010000
#define PERFEVTSEL1 0xc0010001
#define PERFEVTSEL2 0xc0010002
#define PERFEVTSEL3 0xc0010003

#define PERFCTR0 0xc0010004
#define PERFCTR1 0xc0010005
#define PERFCTR2 0xc0010006
#define PERFCTR3 0xc0010007

#define EVENT 0xe9
#define PERFEVTSEL_EN (1 << 22)
#define PERFEVTSEL_OS (1 << 17)
#define PERFEVTSEL_USR (1 << 16)

#define BASE (EVENT | PERFEVTSEL_EN | PERFEVTSEL_OS | PERFEVTSEL_USR)

#define MAXCPU 8

int force = 0;
int msrfd[MAXCPU];
int delay;
int absolute;
char *cfilter;
int verbose;

static void usage(void);

static void Vprintf(char *fmt, ...)
{
	va_list ap;
	va_start(ap,fmt);
	if (verbose)
		vfprintf(stderr,fmt,ap);
	va_end(ap);
}

static unsigned long long rdmsr(int cpu, unsigned long msr)
{
	unsigned long long val;
	if (pread(msrfd[cpu], &val, 8, msr) != 8) {
		fprintf(stderr, "rdmsr of %lx failed: %s\n", msr, strerror(errno));
		exit(1);
	}
	return val;
}

static void wrmsr(int cpu, unsigned long msr, unsigned long long value)
{
	if (pwrite(msrfd[cpu], &value, 8, msr) != 8) {
		fprintf(stderr, "wdmsr of %lx failed: %s\n", msr, strerror(errno));
		exit(1);
	}
}

static int cpufilter(int cpu)
{
	long num;
	char *end;
	char *s;

	if (!cfilter)
		return 1;
	for (s = cfilter;;) {
		num = strtoul(s, &end, 0);
		if (end == s)
			usage();
		if (cpu == num)
			return 1;
		if (*end == ',')
			s = end+1;
		else if (*end == 0)
			break;
		else
			usage();
	}
	return 0;
}

static void checkcounter(int cpu, int clear)
{
	int i;
	for (i = 1; i < 4; i++) {
		int clear_this = clear;
		unsigned long long evtsel = rdmsr(cpu, PERFEVTSEL0 + i);
		Vprintf("%d: %x %Lx\n", cpu, PERFEVTSEL0 + i, evtsel);
		if (!(evtsel & PERFEVTSEL_EN)) {
			Vprintf("reinit %d\n", cpu);
			wrmsr(cpu, PERFEVTSEL0 + i, BASE | masks[i - 1]);
			clear_this = 1;
		} else if (evtsel == (BASE | (masks[i-1] << 8))) {
			/* everything fine */
		} else if (force) {
			Vprintf("reinit force %d\n", cpu);
			wrmsr(cpu, PERFEVTSEL0 + i, BASE | (masks[i - 1] << 8));
			clear_this = 1;
		} else {
			fprintf(stderr, "perfctr %d cpu %d already used with %Lx\n",
				i, cpu, evtsel);
			fprintf(stderr, "Consider using -f if you know what you're doing.\n");
			exit(1);
		}
		if (clear_this) {
			Vprintf("clearing %d\n", cpu);
			wrmsr(cpu, PERFCTR0 + i, 0);
		}
	}
}

static void setup(int clear)
{
	DIR *dir;
	struct dirent *d;
	int numcpus = 0;

	memset(msrfd, -1, sizeof(msrfd));
	dir = opendir("/dev/cpu");
	if (!dir)
		err("cannot open /dev/cpu");
	while ((d = readdir(dir)) != NULL) {
		char buf[64];
		char *end;
		long cpunum = strtoul(d->d_name, &end, 0);
		if (*end != 0)
			continue;
		if (cpunum > MAXCPU) {
			fprintf(stderr, "too many cpus %ld %s\n", cpunum, d->d_name);
			continue;
		}
		if (!cpufilter(cpunum))
			continue;
		snprintf(buf, 63, "/dev/cpu/%ld/msr", cpunum);
		msrfd[cpunum] = open64(buf, O_RDWR);
		if (msrfd[cpunum] < 0)
			continue;
		numcpus++;
		checkcounter(cpunum, clear);
	}
	closedir(dir);
	if (numcpus == 0) {
		fprintf(stderr, "No CPU found using MSR driver.\n");
		exit(1);
	}
}

static void printf_padded(int pad, char *fmt, ...)
{
	char buf[pad + 1];
	va_list ap;
	va_start(ap, fmt);
	vsnprintf(buf, pad, fmt, ap);
	printf("%-*s", pad, buf);
	va_end(ap);
}

static void print_header(void)
{
	printf_padded(4, "CPU ");
	printf_padded(16, "LOCAL");
	printf_padded(16, "LOCAL->REMOTE");
	printf_padded(16, "REMOTE->LOCAL");
	putchar('\n');
}

static void print_cpu(int cpu)
{
	int i;
	static unsigned long long lastval[4];
	printf_padded(4, "%d", cpu);
	for (i = 1; i < 4; i++) {
		unsigned long long val = rdmsr(cpu, PERFCTR0 + i);
		if (absolute)
			printf_padded(16, "%Lu", val);
		else
			printf_padded(16, "%Lu", val - lastval[i]);
		lastval[i] = val;
	}
	putchar('\n');
}

static void dumpall(void)
{
	int cnt = 0;
	int cpu;
	print_header();
	for (;;) {
		for (cpu = 0; cpu < MAXCPU; ++cpu) {
			if (msrfd[cpu] < 0)
				continue;
			print_cpu(cpu);
		}
		if (!delay)
			break;
		sleep(delay);
		if (++cnt > 40) {
			cnt = 0;
			print_header();
		}
	}
}

static void checkk8(void)
{
	char *line = NULL;
	size_t size = 0;
	int bad = 0;
	FILE *f = fopen("/proc/cpuinfo", "r");
	if (!f)
		return;
	while (getline(&line, &size, f) > 0) {
		if (!strncmp("vendor_id", line, 9)) {
			if (!strstr(line, "AMD"))
				bad++;
		}
		if (!strncmp("cpu family", line, 10)) {
			char *s = line + strcspn(line,":");
			int family;
			if (*s == ':') ++s;
			family = strtoul(s, NULL, 0);
			if (family != 15)
				bad++;
		}
	}
	if (bad) {
		printf("not an opteron cpu\n");
		exit(1);
	}
	free(line);
	fclose(f);
}

static void usage(void)
{
	fprintf(stderr, "usage: numamon [args] [delay]\n");
	fprintf(stderr, "       -f forcibly overwrite counters\n");
	fprintf(stderr, "       -i count IO (default memory)\n");
	fprintf(stderr, "       -a print absolute counter values (with delay)\n");
	fprintf(stderr, "       -s setup counters and exit\n");
	fprintf(stderr, "       -c clear counters and exit\n");
	fprintf(stderr, "       -m Print memory traffic (default)\n");
	fprintf(stderr, "       -C cpu{,cpu} only print for cpus\n");
	fprintf(stderr, "       -v Be verbose\n");
	exit(1);
}

int main(int ac, char **av)
{
	int opt;
	checkk8();
	while ((opt = getopt(ac,av,"ifscmaC:v")) != -1) {
		switch (opt) {
		case 'f':
			force = 1;
			break;
		case 'c':
			setup(1);
			exit(0);
		case 's':
			setup(0);
			exit(0);
		case 'm':
			masks = mem;
			break;
		case 'i':
			masks = io;
			break;
		case 'a':
			absolute = 1;
			break;
		case 'C':
			cfilter = optarg;
			break;
		case 'v':
			verbose = 1;
			break;
		default:
			usage();
		}
	}
	if (av[optind]) {
		char *end;
		delay = strtoul(av[optind], &end, 10);
		if (*end)
			usage();
		if (av[optind+1])
			usage();
	}

	setup(0);
	dumpall();
	return 0;
}
