/*
 * memsize.c - figure out how much memory we have to use.
 *
 * Usage: memsize [max_wanted_in_MB]
 *
 * Copyright (c) 1995 Larry McVoy.  Distributed under the FSF GPL with
 * additional restriction that results may published only if
 * (1) the benchmark is unmodified, and
 * (2) the version in the sccsid below is included in the report.
 */
char	*id = "$Id$\n";

#include "bench.h"

#define	CHK(x)	if ((x) == -1) { perror("x"); exit(1); }

//#ifndef	TOO_LONG
//#define	TOO_LONG	10	/* usecs */
//#endif

#define	MEMORY_SIZE_1MB (1024 * 1024)
#define	MEMORY_SIZE_8MB (8 * 1024 * 1024)

int	alarm_triggered = 0;

void	timeit(char *where, size_t size);
static	void touchRange(char *p, size_t range, ssize_t stride);
int	test_malloc(size_t size);
void	set_alarm(uint64 usecs);
void	clear_alarm();

int
main(int ac, char **av)
{
	char	*where;
	size_t	size = 0;
	size_t	max = 0;
	size_t	delta;

	if (ac == 2) {
		max = size = bytes(av[1]) * MEMORY_SIZE_1MB;
	}
	if (max < MEMORY_SIZE_1MB) {
		max = size = 1024 * MEMORY_SIZE_1MB;
	}
	/*
	 * Binary search down and then binary search up
	 */
	for (where = 0; !test_malloc(size); size >>= 1) {
		max = size;
	}
	/* delta = size / (2 * 1024 * 1024) */
	for (delta = (size >> 21); delta > 0; delta >>= 1) {
		uint64 sz = (uint64)size + (uint64)delta * MEMORY_SIZE_1MB;
		size_t check = sz;
		if (max < sz) continue;
		if (check < sz || !test_malloc(sz)) break;
		size = sz;
	}
	if (where = malloc(size)) {
		timeit(where, size);
		free(where);
	}
	exit (0);
}

void
timeit(char *where, size_t size)
{
	int	sum = 0;
	size_t	n;
	size_t	s_prev = MEMORY_SIZE_8MB;
	size_t	range;
	size_t	incr = MEMORY_SIZE_1MB;
	size_t	pagesize = getpagesize();
	size_t	time_each_page = 0;
	size_t	too_long = 0;
	unsigned long long      s;

	if (pagesize < MEMORY_SIZE_1MB)
		range = MEMORY_SIZE_1MB;
	else
		range = MEMORY_SIZE_8MB;

	incr = MEMORY_SIZE_1MB;
	
	if (size < range) {
              fprintf(stderr, "Bad size\n");
              return;
	    }

	//Touch range of memory, get the average time (usec) of operating each memory page on this system
        start(0);
        touchRange(where, range, pagesize);
        sum = stop(0, 0);

        if ((time_each_page = sum * pagesize / range) < 1)
		time_each_page = 1;
	//Set the uper limit of time spending on one page
        too_long = 10 * time_each_page;

	for (range += incr; range <= size; range += incr) {
		n = range / pagesize;
		set_alarm(n * too_long);
		touchRange(where + range - incr, incr, pagesize);
		clear_alarm();
		set_alarm(n * too_long);
		start(0);
		touchRange(where, range, pagesize);
		sum = stop(0, 0);
		clear_alarm();
		if ((sum / n) > too_long || alarm_triggered) {
			size = range - incr;
			fprintf(stderr, "Error! Memory testing timeout! Touch one page of memory needs more than %d (usecs)\n ", too_long);
			break;
		}
		for (s = s_prev; s <= range; s_prev = s, s *= 2)
			if (s < s_prev) break;
		incr = s / 8;
		if (range < size && size < range + incr) {
			incr = size - range;
		}
		fprintf(stderr, "%dMB OK\r", (int)(range/MEMORY_SIZE_1MB));
	}
	fprintf(stderr, "\n");
	printf("%d\n", (int)(size>>20));
}

static void
touchRange(char *p, size_t range, ssize_t stride)
{
	register char	*tmp = p + (stride > 0 ? 0 : range - 1);
	register size_t delta = (stride > 0 ? stride : -stride);

	while (range > delta - 1 && !alarm_triggered) {
		*tmp = 0;
		tmp += stride;
		range -= delta;
	}
}

int
test_malloc(size_t size)
{
	int	fid[2];
	int	result;
	int	status;
	void*	p;

	if (pipe(fid) < 0) {
		void* p = malloc(size);
		if (!p) return 0;
		free(p);
		return 1;
	}
	if (fork() == 0) {
		close(fid[0]);
		p = malloc(size);
		result = (p ? 1 : 0);
		write(fid[1], &result, sizeof(int));
		close(fid[1]);
		if (p) free(p);
		exit(0);
	}
	close(fid[1]);
	if (read(fid[0], &result, sizeof(int)) != sizeof(int))
		result = 0;
	close(fid[0]);
	wait(&status);
	return result;
}

void
gotalarm(int s)
{
	alarm_triggered = 1;
}

void
set_alarm(uint64 usecs)
{
	struct itimerval value;
	struct sigaction sa;

	alarm_triggered = 0;

	sa.sa_handler = gotalarm;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;
	sigaction(SIGALRM, &sa, 0);

	value.it_interval.tv_sec = 0;
	value.it_interval.tv_usec = 0;
	value.it_value.tv_sec = usecs / 1000000;
	value.it_value.tv_usec = usecs % 1000000;

	setitimer(ITIMER_REAL, &value, NULL);
}

void
clear_alarm()
{
	struct itimerval value;

	value.it_interval.tv_sec = 0;
	value.it_interval.tv_usec = 0;
	value.it_value.tv_sec = 0;
	value.it_value.tv_usec = 0;

	setitimer(ITIMER_REAL, &value, NULL);
}

