#!/bin/bash
# This bench splits into two parts. One part creates a mapping and accesses
# it linerally in a loop recording if pages were resident and how long it
# took to access the page. The second part runs dd to see how badly
# it interferes with the anonymous mapping.

###SHELLPACK preamble ddresidency-bench 0

MAPPING_SIZE=${DD_RESIDENCY_MAPPING_SIZE:=$MEMTOTAL_BYTES}
BS=1048576
MAX_DURATION=0

function shutdown_pid() {
	NAME=$1
	TPID=$2
	SIG=$3
	FORCE_TIMEOUT=$4
	SSIG=$5

	if [ "$SIG" = "" ]; then
		SIG=INT
	fi
	if [ "$FORCE_TIMEOUT" = "" ]; then
		FORCE_TIMEOUT=10
	fi

	TIMES=0
	echo -n Shutting down $NAME pid $TPID
	kill -$SIG $TPID 2> /dev/null
	while [ "`ps h --pid $TPID`" != "" ]; do
		echo -n .
		sleep 1
		TIMES=$((TIMES+1))
		if [ $TIMES -eq $FORCE_TIMEOUT ]; then
			echo
			echo -n Shutting down $NAME pid $TPID by TERM
			SIG=TERM
		fi
		if [ $TIMES -gt $((FORCE_TIMEOUT*2)) ]; then
			echo
			echo -n Shutting down $NAME pid $TPID by KILL
			SIG=KILL
		fi
		kill -$SIG $TPID 2> /dev/null
		if [ "$SSIG" != "" ]; then
			kill -$SSIG $TPID 2> /dev/null
		fi
	done
	echo
}
###SHELLPACK parseargBegin
###SHELLPACK parseargParam --mapping-size MAPPING_SIZE
###SHELLPACK parseargParam --filesize     FILESIZE
###SHELLPACK parseargParam --max-duration MAX_DURATION
###SHELLPACK parseargEnd

# Build the mapping program
echo Building mapping program
TEMPFILE=`mktemp`
LINECOUNT=`wc -l $0 | awk '{print $1}'`
CSTART=`grep -n "BEGIN C FILE" $0 | tail -1 | awk -F : '{print $1}'`
tail -$(($LINECOUNT-$CSTART)) $0 | grep -v "^###" > $TEMPFILE.c
gcc -O2 $TEMPFILE.c -o $TEMPFILE || exit -1

# Dump all existing cache for full IO effect
echo Dropping caches, inodes and dentries
echo 3 > /proc/sys/vm/drop_caches

# Start it reading in the background
EXPECT_UNBUFFER=expect_unbuffer
if [ "`which $EXPECT_UNBUFFER 2> /dev/null`" = "" ]; then
        EXPECT_UNBUFFER=unbuffer
fi
echo Starting background mapping writer
echo Logging: $LOGDIR_RESULTS/access-latency.log.gz
$EXPECT_UNBUFFER $TEMPFILE $MAPPING_SIZE | tee | gzip -c > $LOGDIR_RESULTS/access-latency.log.gz &
MAPPINGPID1=$!
MAPPINGPID2=`$SHELLPACK_TOPLEVEL/bin/piping-pid.sh $MAPPINGPID1`
MAPPINGPID3=`$SHELLPACK_TOPLEVEL/bin/piping-pid.sh $MAPPINGPID2`
sleep 3

# Make sure it actually starts
while [ "$MAPPINGPID3" = "" -o "`ps h --pid $MAPPINGPID3`" = "" ]; do
	echo WARNING: Background mapping failed to start with $MAPPING_SIZE
	zcat $LOGDIR_RESULTS/access-latency.log.gz

	MAPPING_SIZE=$((MAPPING_SIZE-(512*1048576)))
	echo Restarting with $MAPPING_SIZE
	$EXPECT_UNBUFFER $TEMPFILE $MAPPING_SIZE | tee | gzip -c > $LOGDIR_RESULTS/access-latency.log.gz &
	MAPPINGPID1=$!
	MAPPINGPID2=`$SHELLPACK_TOPLEVEL/bin/piping-pid.sh $MAPPINGPID1`
	MAPPINGPID3=`$SHELLPACK_TOPLEVEL/bin/piping-pid.sh $MAPPINGPID2`
	sleep 3
done

# DD
echo Starting dd
echo dd if=/dev/zero of=$TESTDISK_DIR/ddfile ibs=$BS count=$((FILESIZE/BS))
echo Filesize: $FILESIZE > $LOGDIR_RESULTS/dd.log
echo BS: $BS >> $LOGDIR_RESULTS/dd.log
START_TIME=`date +%s`
dd if=/dev/zero of=$TESTDISK_DIR/ddfile ibs=$BS count=$((FILESIZE/BS)) 2>> $LOGDIR_RESULTS/dd.log &
DDPID=$!

# Wait on dd completion or duration or whatever
if [ $MAX_DURATION -eq 0 ]; then
	echo -n Waiting on dd to complete
	while [ "`ps h --pid $DDPID`" != "" ]; do
		echo -n .
		sleep 10
	done
	echo
else
	echo -n Waiting on dd to complete or $MAX_DURATION seconds
	END_TIME=`date +%s`
	END_TIME=$((END_TIME+MAX_DURATION))
	while [ `date +%s` -lt $END_TIME -a "`ps h --pid $DDPID`" != "" ]; do
		echo -n .
		sleep 10
	done
	echo
fi

# Shutdown DD and record stats
SHUTDOWN_START_TIME=`date +%s`
kill -USR1 $DDPID
shutdown_pid dd $DDPID INT 90 USR1
END_TIME=`date +%s`
echo "ShutdownTime: $((END_TIME-SHUTDOWN_START_TIME)) seconds" >> $LOGDIR_RESULTS/dd.log
DIRTY=`grep "nr_dirty " /proc/vmstat | awk '{print $2}'`
DIRTY=$((DIRTY*4096))
WRITTEN=`stat $TESTDISK_DIR/ddfile | grep Size: | awk '{print $2}'`
ACTUALWRITTEN=$((WRITTEN-DIRTY))
DDRUNTIME=$((END_TIME-START_TIME))

# Shutdown mapping process
for MAPPINGPID in $MAPPINGPID3 $MAPPINGPID2 $MAPPINGPID1; do
	shutdown_pid mapping $MAPPINGPID
done

# Cleanup
rm $TEMPFILE $TEMPFILE.c
echo Deleting ddfile
DELETE_START_TIME=`date +%s`
rm $TESTDISK_DIR/ddfile
END_TIME=`date +%s`
DELETE_TIME=$((END_TIME-$DELETE_START_TIME))

# Report
echo "ReportWritten: $WRITTEN" >> $LOGDIR_RESULTS/dd.log
echo "ActualWritten: $ACTUALWRITTEN" >> $LOGDIR_RESULTS/dd.log
echo "Dirty: $DIRTY" >> $LOGDIR_RESULTS/dd.log
echo "DDRunTime: $DDRUNTIME" >> $LOGDIR_RESULTS/dd.log
echo "ReportWriteSpeed: $((WRITTEN/DDRUNTIME)) B/sec" >> $LOGDIR_RESULTS/dd.log
echo "ActualWriteSpeed: $((ACTUALWRITTEN/DDRUNTIME)) B/sec" >> $LOGDIR_RESULTS/dd.log
echo "DeleteTime: $DELETE_TIME seconds" >> $LOGDIR_RESULTS/dd.log

exit $SHELLPACK_SUCCESS
==== BEGIN C FILE ====
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/time.h>

int exiting;
void sighandle(int dummy) {
	exiting = 1;
}

#define FORCE_LOG_EVERY 131072

int main(int argc, char **argv)
{
	size_t length;
	struct timeval tv;
	char *mapping, *end_mapping, *addr;
	int stride = getpagesize();
	int count;
	struct sigaction sigact = {
		.sa_handler = sighandle
	};

	if (argc <= 1) {
		printf("Usage: mmap-anon-access <mapping-size>\n");
		exit(EXIT_FAILURE);
	}

	length = strtoull(argv[1], NULL, 10);
	length &= ~(getpagesize()-1);

	if (sigaction(SIGINT, &sigact, NULL) == -1) {
		perror("sigaction");
		exit(EXIT_FAILURE);
	}

	/* Create anonymous mapping */
	mapping = mmap(NULL, length, PROT_READ|PROT_WRITE,
			MAP_PRIVATE|MAP_ANONYMOUS, 0, 0);
	if (mapping == MAP_FAILED) {
		printf("Failed mapping size: %lu\n", length);
		perror("mmap");
		exit(EXIT_FAILURE);
	}

	memset(mapping, 1, length - stride);

	/* Loop until interrupted */
	addr = mapping;
	end_mapping = mapping + length;
	while (!exiting) {
		struct timeval start, end, diff;
		int resident;
		unsigned char vec[1];

		if (mincore(addr, 1, vec) == -1) {
			perror("mincore");
			exit(EXIT_FAILURE);
		}

		if (gettimeofday(&start, NULL) == -1) {
			perror("gettimeofday");
			exit(EXIT_FAILURE);
		}

		/* Random write */
		*addr = (char)start.tv_usec;

		if (gettimeofday(&end, NULL) == -1) {
			perror("gettimeofday");
			exit(EXIT_FAILURE);
		}

		timersub(&end, &start, &diff);

		/*
		 * Only print lines for significant delays or non-resident
		 * pages. Otherwise the volume of output would itself be
		 * very disruptive if it was being written to disk
		 */
		if (diff.tv_sec || diff.tv_usec > 1000 || !vec[0] || (count % FORCE_LOG_EVERY) == 0)
			printf("%lu.%06lu %lu.%06lu %d\n",
				start.tv_sec, start.tv_usec,
				diff.tv_sec, diff.tv_usec,
				vec[0]);
			
		count++;
		addr += stride;
		if (addr >= end_mapping)
			addr = mapping;
	}

	/* Print one dummy line to record when the test ended */
	if (gettimeofday(&tv, NULL) == -1) {
		perror("gettimeofday");
		exit(EXIT_FAILURE);
	}
	printf("%lu.%06lu 0.000000 1\n", tv.tv_sec, tv.tv_usec);
}
