/*
 * bloat - waste memory.
 *
 * This test allocates memory a page at a time and keeps going until
 * it runs out. It gets the memory directly with sbrk to avoid malloc-
 * related overheads, which as long as OS/161 has a dumb userlevel
 * malloc is important for performance.
 */

#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <err.h>

/* OS/161 doesn't currently have a way to get this from the kernel. */
#define PAGE_SIZE 4096

/* the memory we've gotten */
static void *firstpage;
static void *lastpage;

/* number of page allocations per cycle */
static unsigned allocs;

/* number of pages to touch every cycle */
static unsigned touchpages;

/* when touching pages, the extent to which we favor the middle of the range */
static unsigned bias;


static
void
moremem(void)
{
	static unsigned totalpages;

	void *ptr;
	unsigned i;

	for (i=0; i<allocs; i++) {
		ptr = sbrk(PAGE_SIZE);
		if (ptr == (void *)-1) {
			err(1, "After %u pages: sbrk", totalpages);
		}
		totalpages++;
		lastpage = ptr;
		if (firstpage == NULL) {
			firstpage = ptr;
		}
	}
}

static
void
touchpage(unsigned pagenum)
{
	int *ptr;

	ptr = (void *)((uintptr_t)firstpage + PAGE_SIZE * pagenum);
	*ptr = pagenum;
}

static
unsigned
pickpage(unsigned numpages)
{
	unsigned mnum, moffset;
	unsigned span, val, i;

	/* take 1 in 1000 pages uniformly from the entire space */
	if (random() % 1000 == 0) {
		return random() % numpages;
	}

	/* the rest is taken from the middle 1% */

	mnum = numpages / 100;
	if (mnum < touchpages * 2) {
		mnum = touchpages * 2;
	}
	if (mnum >= numpages) {
		mnum = numpages;
	}
	moffset = numpages / 2 - mnum / 2;

	assert(bias >= 1);
	span = (mnum + bias - 1) / bias;

	do {
		val = 0;
		for (i=0; i<bias; i++) {
			val += random() % span;
		}
	} while (val >= mnum);
	return moffset + val;
}

static
void
touchmem(void)
{
	unsigned i, num;

	num = (((uintptr_t)lastpage - (uintptr_t)firstpage) / PAGE_SIZE) + 1;

	if (num % 256 == 0) {
		warnx("%u pages", num);
	}

	for (i=0; i<touchpages; i++) {
		touchpage(pickpage(num));
	}
}

static
void
run(void)
{
	while (1) {
		moremem();
		touchmem();
	}
}

static
void
printsettings(void)
{
	tprintf("Page size: %u\n", PAGE_SIZE);
	tprintf("Allocating %u pages and touching %u pages on each cycle.\n",
	       allocs, touchpages);
	tprintf("Page selection bias: %u\n", bias);
	tprintf("\n");
}

static
void
usage(void)
{
	warnx("bloat [-a allocs] [-b bias] [-p pages]");
	warnx("   allocs: number of pages allocated per cycle (default 4)");
	warnx("   bias: number of dice rolled to touch pages (default 8)");
	warnx("   pages: pages touched per cycle (default 8)");
	exit(1);
}

int
main(int argc, char *argv[])
{
	int i;

	/* default mode */
	allocs = 4;
	touchpages = 8;
	bias = 8;

	srandom(1234);

	for (i=1; i<argc; i++) {
		if (!strcmp(argv[i], "-a")) {
			i++;
			if (i == argc) {
				errx(1, "-a: option requires argument");
			}
			allocs = atoi(argv[i]);
			if (allocs == 0) {
				errx(1, "-a: must not be zero");
			}
		}
		else if (!strcmp(argv[i], "-b")) {
			i++;
			if (i == argc) {
				errx(1, "-b: option requires argument");
			}
			bias = atoi(argv[i]);
			if (bias == 0) {
				errx(1, "-b: must not be zero");
			}
		}
		else if (!strcmp(argv[i], "-h")) {
			usage();
		}
		else if (!strcmp(argv[i], "-p")) {
			i++;
			if (i == argc) {
				errx(1, "-p: option requires argument");
			}
			touchpages = atoi(argv[i]);
		}
		else {
			errx(1, "Argument %s not recognized", argv[i]);
			usage();
		}
	}

	printsettings();
	run();
	return 0;
}
