#!/bin/bash
# This benchmark simualtes a simple workload. One part uses a lot of anonymous
# memory, a second measures mmap latency and a third copies a large file. In
# an ideal world the latency application would never notice but there are times
# when this regresses and the benchmark catches that

###SHELLPACK preamble stutter-bench 0

MEMFAULT_SIZE=${STUTTER_MEMFAULT_SIZE:=$MEMTOTAL_BYTES}
MEMFAULT_TMPFS=no
DD_FILESIZE=${STUTTER_FILESIZE:=$MEMTOTAL_BYTES}
DD_BLOCKSIZE=${STUTTER_BLOCKSIZE:=4096}
ITERATIONS=5
USE_DD_ZERO=no
USE_LOCAL=no

###SHELLPACK parseargBegin
###SHELLPACK parseargParam --memfault-size  MEMFAULT_SIZE
###SHELLPACK parseargParam --memfault-tmpfs MEMFAULT_TMPFS
###SHELLPACK parseargParam --filesize       DD_FILESIZE
###SHELLPACK parseargParam --blocksize      DD_BLOCKSIZE
###SHELLPACK parseargYes   --use-dd         USE_DD_ZERO
###SHELLPACK parseargYes   --source-local   USE_LOCAL
###SHELLPACK parseargEnd
###SHELLPACK monitor_hooks
###SHELLPACK init_complete

# Align the fault size
MEMFAULT_SIZE=$((MEMFAULT_SIZE&~1048575))

# Build the latency program
cd $SHELLPACK_TEMP || die Failed to change to temporary directory
echo Building latency program
LATENCY_FILE=`mktemp`
if [ "$LATENCY_FILE" = "" ]; then
	die Failed to create temporary latency file
fi
CSTART=`grep -n "BEGIN LATENCY FILE" $0 | tail -1 | awk -F : '{print $1}'`
CEND=`grep -n "END LATENCY FILE" $0 | tail -1 | awk -F : '{print $1}'`
sed -n $((CSTART+1)),$((CEND-1))p $0 > ${LATENCY_FILE}.c
gcc ${MMTESTS_BUILD_CFLAGS:--O2} -lm ${LATENCY_FILE}.c -o ${LATENCY_FILE}
if [ $? -ne 0 ]; then
	echo Rebuilding latency program with lrt
	gcc ${MMTESTS_BUILD_CFLAGS:--O2} -lm -lrt ${LATENCY_FILE}.c -o ${LATENCY_FILE} || exit $SHELLPACK_ERROR
fi

# Build the memhog program
echo Building memhog program
MEMHOG_FILE=`mktemp`
if [ "$MEMHOG_FILE" = "" ]; then
	die Failed to create temporary memhog file
fi
CSTART=`grep -n "BEGIN MEMHOG FILE" $0 | tail -1 | awk -F : '{print $1}'`
CEND=`grep -n "END MEMHOG FILE" $0 | tail -1 | awk -F : '{print $1}'`
sed -n $((CSTART+1)),$((CEND-1))p $0 > ${MEMHOG_FILE}.c
gcc -DMEMFAULT_SIZE=$MEMFAULT_SIZE ${MMTESTS_BUILD_CFLAGS:--O2} ${MEMHOG_FILE}.c -o ${MEMHOG_FILE} || exit $SHELLPACK_ERROR

# Prepare the tmpfs mount if requested
MEMFAULT_FILE=
if [ "$MEMFAULT_TMPFS" = "yes" ]; then
	mkdir mnt
	mount -t tmpfs none mnt -o size=$((MEMFAULT_SIZE+(2*1048576)))
	MEMFAULT_FILE="mnt/tmpfs_file"
fi

# Calibrate the expected time to complete
echo Calibrating IO speeds
$TIME_CMD -o $LOGDIR_RESULTS/calibrate.time \
	dd if=/dev/zero of=$SHELLPACK_DATA/ddfile ibs=$DD_BLOCKSIZE count=$((1024*1048576/DD_BLOCKSIZE)) conv=fdatasync 2>&1 | tee $LOGDIR_RESULTS/calibrate.log
rm $SHELLPACK_DATA/ddfile

# Determine where the source file should be
SOURCEFILE_TOPLEVEL=
DISK_FREE=`df --block-size=1 $SHELLPACK_TOPLEVEL | tail -1 | awk '{print $4}'`
DISK_NEEDED=$((DD_FILESIZE/10*11))
if [ "$USE_LOCAL" = "yes" ]; then
	SOURCEFILE_TOPLEVEL=$SHELLPACK_TOPLEVEL
	if [ $DISK_NEEDED -ge $DISK_FREE ]; then
		die "Insufficient space free on local partition for test to run"
	fi
else
	if [ $DISK_NEEDED -lt $DISK_FREE ]; then
		echo Using local disk: $((DISK_FREE/1048576))MB free for $((DISK_NEEDED/1048576))MB required
		SOURCEFILE_TOPLEVEL=$SHELLPACK_TOPLEVEL
	else
		echo Using test partion for source file
		SOURCEFILE_TOPLEVEL=$SHELLPACK_DATA
	fi
fi

# Create source file if necessary
if [ "$USE_DD_ZERO" = "no" ]; then
	echo Creating source file: $SOURCEFILE_TOPLEVEL/stutter-source-file
	dd if=/dev/zero of=$SOURCEFILE_TOPLEVEL/stutter-source-file bs=$DD_BLOCKSIZE count=$((DD_FILESIZE/DD_BLOCKSIZE)) conv=fdatasync
fi

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

# Start the latency monitor
echo Starting mapping latency monitor
echo Logging: $LOGDIR_RESULTS/mmap-latency.log
$LATENCY_FILE > $LOGDIR_RESULTS/mmap-latency.log &
LATENCY_PID=$!
monitor_pre_hook $LOGDIR_RESULTS $P

# Begin the copy or dd
echo $DD_BLOCKSIZE > $LOGDIR_RESULTS/dd.blocksize
echo $DD_FILESIZE > $LOGDIR_RESULTS/dd.filesize
for ITERATION in `seq 1 $ITERATIONS`; do
	echo Iteration $ITERATION/$ITERATIONS

	# Start the memhog
	RUNNING=-1
	while [ $RUNNING -ne 0 ]; do
		echo Starting memhog
		$MEMHOG_FILE $MEMFAULT_FILE &
		MEMHOG_PID=$!

		sleep 10

		# Make sure it's running
	        ps -p $MEMHOG_PID > /dev/null
		RUNNING=$?
        	if [ $RUNNING -ne 0 ]; then
			sync
                	echo memhog exited abnormally, retrying
        	fi
	done


	if [ "$USE_DD_ZERO" = "no" ]; then
		# CP
		echo Starting cp $SOURCEFILE_TOPLEVEL/stutter-source-file $SHELLPACK_DATA/ddfile
		echo "#!/bin/bash
cp $SOURCEFILE_TOPLEVEL/stutter-source-file $SHELLPACK_DATA/ddfile
sync" > cp-script.sh
		chmod u+x cp-script.sh
		$TIME_CMD -o $LOGDIR_RESULTS/time.$ITERATION \
			./cp-script.sh 2>&1 | tee $LOGDIR_RESULTS/dd-$ITERATION.log
		rm cp-script.sh
	else
		# DD
		echo Starting dd if=/dev/zero of=$SHELLPACK_DATA/ddfile ibs=$DD_BLOCKSIZE count=$((DD_FILESIZE/DD_BLOCKSIZE)) conv=fdatasync
		$TIME_CMD -o $LOGDIR_RESULTS/time.$ITERATION \
			dd if=/dev/zero of=$SHELLPACK_DATA/ddfile ibs=$DD_BLOCKSIZE count=$((DD_FILESIZE/DD_BLOCKSIZE)) conv=fdatasync 2>&1 | tee $LOGDIR_RESULTS/dd-$ITERATION.log
	fi

	shutdown_pid memhog $MEMHOG_PID
done
monitor_post_hook $LOGDIR_RESULTS $P
rm $SOURCEFILE_TOPLEVEL/stutter-source-file $SHELLPACK_DATA/ddfile

# Shutdown monitors
kill $LATENCY_PID

# Cleanup
cd /
if [ "$MEMFAULT_TMPFS" = "yes" ]; then
	umount $SHELLPACK_TEMP/mnt
fi
rm -rf $SHELLPACK_TEMP

exit $SHELLPACK_SUCCESS
==== BEGIN LATENCY FILE ====
#include <math.h>

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <sys/mman.h>

#ifndef NR_MAP_PAGES
#define NR_MAP_PAGES	32768
#endif
#ifndef MSECS_DELAY
#define MSECS_DELAY	5
#endif
#define NSECS_DELAY	(MSECS_DELAY * 1000000)
#define PRINT_THRESHOLD	(5000 / MSECS_DELAY)

int exiting = 0;
void sigterm_handler(int sig)
{
	exiting = 1;
}

int main(int argc, char **argv)
{
	struct timespec intv_ts = {
		.tv_sec  = MSECS_DELAY / 1000,
		.tv_nsec =  (MSECS_DELAY % 1000) * 1000000,
	};
	struct timespec ts;
	unsigned long long time0, time1;
	unsigned long pagesize = getpagesize();
	const size_t map_size = NR_MAP_PAGES * pagesize;
	int c = 0;
	unsigned long long sum_latency = 0;

	clock_gettime(CLOCK_MONOTONIC, &ts);
	time1 = ts.tv_sec * 1000000000LLU + ts.tv_nsec;

	signal(SIGTERM, sigterm_handler);
	while (!exiting) {
		void *map, *p;
		unsigned long latency;

		nanosleep(&intv_ts, NULL);
		map = mmap(NULL, map_size, PROT_READ | PROT_WRITE,
			   MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
		if (map == MAP_FAILED) {
			perror("mmap");
			exit(EXIT_FAILURE);
		}

		for (p = map; p < map + map_size; p += pagesize)
			*(volatile unsigned long long *)p = time1;

		munmap(map, map_size);

		time0 = time1;
		clock_gettime(CLOCK_MONOTONIC, &ts);
		time1 = ts.tv_sec * 1000000000LLU + ts.tv_nsec;

		latency = (time1 - time0);
		if (latency < NSECS_DELAY)
			latency = 0;
		else
			latency -= NSECS_DELAY;
		sum_latency += latency;
		if (latency < NSECS_DELAY) {
			if (++c == PRINT_THRESHOLD) {
				printf("%d %llu\n", c, (sum_latency / c));
				c = 0;
				sum_latency = 0;
			}
		} else {
			if (c)
				printf("%d %llu\n", c, (sum_latency / c));
			printf("1 %lu\n", latency);
			c = 0;
			sum_latency = 0;
		}
	}

	fflush(NULL);
	exit(EXIT_SUCCESS);
}
==== END LATENCY FILE ====
==== BEGIN MEMHOG FILE ====
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char **argv)
{
	void *p, *map;
	int flags, fd = 0;
	int stride = getpagesize();

	flags = MAP_ANONYMOUS | MAP_PRIVATE;
	if (argc >= 2) {
		fd = open(argv[1], O_CREAT|O_TRUNC|O_RDWR, S_IRWXU);
		if (fd < 0) {
			perror("open");
			exit(EXIT_FAILURE);
		}

		if (ftruncate(fd, MEMFAULT_SIZE)) {
			perror("ftruncate");
			exit(EXIT_FAILURE);
		}
		flags = MAP_SHARED;
		unlink(argv[1]);
	}

	map = mmap(NULL, MEMFAULT_SIZE, PROT_READ | PROT_WRITE,
		   flags, fd, 0);
	if (map == MAP_FAILED) {
		perror("mmap");
		exit(EXIT_FAILURE);
	}

	for (p = map; p < (map + MEMFAULT_SIZE); p += stride)
		*(volatile unsigned long *)p = (unsigned long)p;

	pause();
	exit(EXIT_SUCCESS);
}
==== END MEMHOG FILE ====
