#include <unistd.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>

/* DAPM 9 May 99
 * do some benchmarks to compare mmap() and write() speeds.
 * Use a SIGALARM to stop the test after PERIOD seconds.
 *
 * Some approx results:
 *	A	sparcstation 4  70Mhz,  48Mb, running Solaris 2.4
 *	B	sparcstation 4 110Mhz, 128Mb, running Solaris 2.7
 *	C	Ultra 2    1 x 167Mhz, 768Mb, running Solaris 2.5.1
 *
 *				 A	B	C
 * 64K USEMM			2.5ms  2.2-2.6ms 0.3ms
 * 64K USEMM UNMAP		4.2ms  3-4ms	0.6ms
 * 64K USEMM UNMAP DOTRUNC	13ms   13ms	2.3ms
 * 64K using write()		24ms   10ms	9.2ms
 *
 * Note that the ultra was an order of magnitude faster than the others
 * on the mmap-related stuff - not sure whether this is due to the large
 * amount of RAM, the faster processor, or a different MMU architecture.
 *
 * NB I checked, and Solaris 2.7 *doesnt* allow a file to grow by
 * accessing pages mapped belond the length of the file
 *
 * Under the terms of the Mitchell-Allison treaty, the second-best
 * programmer dedicates this code to the second-best system administrator.
 *
 */

#define USEMM	/* if defined, use mmap() rather than write() */
#define UNMAP	/* if defined, do munmap() after each memcpy() */
#define DOTRUNC	/* truncate file before each memcpy() */
#define SOL_TRUNC /* use the syscall directly that solaris uses for ftruncate */
#define PERIOD 2 /* how many seconds to run the test for */

/* file to write to. NB need to do an 'mkfile 14M' before using 
 * one the permutations that doesnt automatically grow the file
 */

/*#define TFILE "/tmp/tfile"*/
#define TFILE "tfile"

/* block size, and number of blocks that make up the file */

#define BUFSIZE 65536
#define NBLOCKS 200


char buf[BUFSIZE];

int count;

struct timeval starttp;

/* our signal handler - called when the time is up */

void alrm(int n) {
	float f;
	struct timeval tp2;
	float period;
	gettimeofday(&tp2, (void *) 0);
	period = (tp2.tv_sec - starttp.tv_sec)
					+ (tp2.tv_usec - starttp.tv_usec)/1E6;
	f = 1E6 / (count/ period );
	printf("single op took %6.2fus period=%6.2fs count=%d\n", f,period,count);
	exit(0);
}

/* wrapper for mmap - offset is in units of blocks */

char * mymmap(fd,offset,nblocks) {
	char *a;
	a = mmap((char *) 0,BUFSIZE*nblocks,
			PROT_READ|PROT_WRITE, MAP_SHARED, fd, BUFSIZE*offset);
	if (a == MAP_FAILED) {
		perror("mmap failed");
		exit(1);
	}
	return a;
}

main() {
	buf[0] = 'A';
	buf[1] = 'B';
	buf[2] = 'C';
	buf[3] = 'D';
	signal(SIGALRM, alrm);
	alarm(PERIOD);
	gettimeofday(&starttp, (void *) 0);
	timeit();
}


int timeit() {
	int fd,offset;
	char *addr;

#ifdef SOL_TRUNC
	struct flock fl;
	fl.l_whence = 0;
	fl.l_len = 0;
	fl.l_type = F_UNLCK;
#endif

	fd = open(TFILE, O_RDWR|O_CREAT, 0644);
	if (fd==-1) {
		perror("failed to open file");
		exit(1);
	}

#ifndef UNMAP
	addr = mymmap(fd,0,NBLOCKS);
#endif
	
	offset=0;
	for(;;) {
		(int) *buf = count;
#ifdef USEMM
	#ifdef DOTRUNC
		#ifdef SOL_TRUNC
			fl.l_start = (offset+1)*BUFSIZE;
			if (fcntl(fd,F_FREESP,&fl) == -1) {
				perror("fcntl");
				exit(1);
			}
		#else
			ftruncate(fd,(offset+1)*BUFSIZE);
		#endif
	#endif
	#ifdef UNMAP
		addr = mymmap(fd,offset,1);
		memcpy(addr,buf,BUFSIZE);
		munmap(addr,BUFSIZE);
	#else
		memcpy(addr+BUFSIZE*offset,buf,BUFSIZE);
	#endif

#else
		write(fd,buf,BUFSIZE);
#endif
		count++;
		offset++;
		if (offset >= NBLOCKS) offset=0;
	}
}
