// clockbench: cost of clock_gettime(CLOCK_MONOTONIC), one thread and all CPUs at once.
// Runs as a normal program on the host, or as /init inside a throwaway KVM guest.
#define _GNU_SOURCE
#include <pthread.h>
#include <sched.h>
#include <stdio.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/reboot.h>
#include <time.h>
#include <unistd.h>

#define SECS 2.0

static double now(void)
{
	struct timespec t;
	clock_gettime(CLOCK_MONOTONIC, &t);
	return t.tv_sec + t.tv_nsec / 1e9;
}

/* calls per second for SECS seconds, checking the time every 1000 calls */
static double hammer(void)
{
	struct timespec t;
	unsigned long n = 0;
	double start = now(), el;
	do {
		for (int i = 0; i < 1000; i++)
			clock_gettime(CLOCK_MONOTONIC, &t);
		n += 1000;
	} while ((el = now() - start) < SECS);
	return el * 1e9 / n;
}

static pthread_barrier_t bar;

static void *worker(void *arg)
{
	cpu_set_t s;
	CPU_ZERO(&s);
	CPU_SET((int)(long)arg, &s);
	pthread_setaffinity_np(pthread_self(), sizeof s, &s);
	pthread_barrier_wait(&bar);
	static double res[256];
	res[(long)arg] = hammer();
	return &res[(long)arg];
}

static void file(const char *p, char *buf, size_t n)
{
	FILE *f = fopen(p, "r");
	buf[0] = 0;
	if (f) {
		if (!fgets(buf, n, f))
			buf[0] = 0;
		fclose(f);
	}
	buf[strcspn(buf, "\n")] = 0;
}

int main(void)
{
	int init = getpid() == 1;
	char cs[64];

	if (init) {
		mount("proc", "/proc", "proc", 0, NULL);
		mount("sysfs", "/sys", "sysfs", 0, NULL);
	}
	int ncpu = sysconf(_SC_NPROCESSORS_ONLN);
	file("/sys/devices/system/clocksource/clocksource0/current_clocksource", cs, sizeof cs);

	double one = hammer();
	pthread_t th[256];
	pthread_barrier_init(&bar, NULL, ncpu);
	double sum = 0, worst = 0;
	for (long c = 0; c < ncpu; c++)
		pthread_create(&th[c], NULL, worker, (void *)c);
	for (int c = 0; c < ncpu; c++) {
		double *r;
		pthread_join(th[c], (void **)&r);
		sum += *r;
		if (*r > worst)
			worst = *r;
	}
	printf("RESULT %s clocksource=%s cpus=%d  1 thread: %.1f ns/call  all %d CPUs: %.1f ns/call avg, %.1f worst\n",
	       init ? "guest" : "host", cs, ncpu, one, ncpu, sum / ncpu, worst);
	fflush(stdout);

	if (init) {
		sync();
		reboot(RB_POWER_OFF);
	}
	return 0;
}
