/*
 * df2712_storm4.c -- sustained-kick unprivileged storm (v4: interactive victims) for DF-2712.
 *
 * Usage: df2712_storm3 <victim_cpu> <n_victim> <n_sender_per_cpu> <secs>
 *
 * Improvements over storm2 (whose kick traffic self-extinguishes once the
 * pure-yielding victims saturate estcpu and sort out of scan heads):
 *   - victims pin to victim_cpu and interleave short sleeps with yield
 *     bursts: the sleep decays estcpu (bsd4_recalculate_estcpu) keeping
 *     their priorities competitive at runq heads, and every wake requeues
 *     them with a fresh lwp_rebal_ticks window (usched_bsd4.c:679).
 *   - senders pin to each non-victim cpu and tight-yield with tiny sleeps:
 *     maximum chooseproc_cc scan rate -> maximum spin-held kick_helper
 *     calls (up to queue_checks per scan, usched_bsd4.c:1483).
 *
 * All operations are unprivileged: lwp_setaffinity(0,-1,mask) self-pin
 * (kern_usched.c:423 allows self always), sched_yield, usleep.
 */
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <sched.h>
#include <errno.h>

#define SYS_lwp_setaffinity 544

static int g_vcpu = 5;

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

static int pin_to_cpu(int cpu)
{
	unsigned long mask = 1UL << cpu;
	return syscall(SYS_lwp_setaffinity, 0, -1, &mask);
}

static unsigned long rstate = 1;
static unsigned long xrand(void)
{
	rstate = rstate * 6364136223846793005UL + 1442695040888963407UL;
	return (rstate >> 33);
}

static void
victim_loop(long secs, double t0)
{
	if (pin_to_cpu(g_vcpu) < 0) { }
	while (now() - t0 < secs) {
		sched_yield();
		usleep(60 + xrand() % 120);
	}
	_exit(0);
}

static void
sender_loop(int cpu, long secs, double t0)
{
	if (pin_to_cpu(cpu) < 0) { }
	while (now() - t0 < secs) {
		int i;
		for (i = 0; i < 200; ++i)
			sched_yield();
	}
	_exit(0);
}

int
main(int argc, char **argv)
{
	int ncpu = 6;
	long nvic = 160, nspc = 16, secs = 120;
	long i, c, total = 0, ok = 0, bad = 0;
	pid_t pid;
	double t0;
	size_t sz = sizeof(ncpu);

	if (argc > 1) g_vcpu = atoi(argv[1]);
	if (argc > 2) nvic = atol(argv[2]);
	if (argc > 3) nspc = atol(argv[3]);
	if (argc > 4) secs = atol(argv[4]);
	sysctlbyname("hw.ncpu", &ncpu, &sz, NULL, 0);
	if (g_vcpu >= ncpu) g_vcpu = ncpu - 1;

	t0 = now();
	for (i = 0; i < nvic; ++i) {
		pid = fork();
		if (pid == 0)
			victim_loop(secs, t0);
		if (pid < 0) break;
		++total;
	}
	for (c = 0; c < ncpu; ++c) {
		if ((int)c == g_vcpu) continue;
		for (i = 0; i < nspc; ++i) {
			pid = fork();
			if (pid == 0)
				sender_loop((int)c, secs, t0);
			if (pid < 0) break;
			++total;
		}
	}
	for (i = 0; i < total; ++i) {
		int st = 0;
		if (wait(&st) < 0) {
			if (errno == EINTR) { --i; continue; }
			break;
		}
		if (WIFEXITED(st) && WEXITSTATUS(st) == 0)
			++ok;
		else
			++bad;
	}
	printf("STORM4-DONE vcpu=%d nvic=%ld nspc=%ld ok=%ld bad=%ld secs=%ld\n",
	    g_vcpu, nvic, nspc, ok, bad, secs);
	return (bad != 0);
}
