#!/bin/bash
# dbench installer
P=dbench-install
DEFAULT_VERSION=0
. $SHELLPACK_INCLUDE/common.sh
TIME_CMD=`which time`
if [ "$TIME_CMD" = "" ]; then
        TIMEFORMAT="%2Uuser %2Ssystem %Relapsed %P%%CPU"
        TIME_CMD="time"
fi
GIT_LOCATION=git://git.samba.org/sahlberg/dbench.git
WEB_LOCATION=http://samba.org/ftp/tridge/dbench/
MIRROR_LOCATION="$WEBROOT/dbench/"

install-depends popt-devel zlib-devel

# Basic argument parser
TASKSET_SERVER=
TASKSET_CLIENT=
TASKSET_ALL=
SERVERSIDE_COMMAND=none
SERVERSIDE_NAME=`date +%Y%m%d-%H%M-%S`

while [ "$1" != "" ]; do
	case "$1" in
	-v)
		VERSION=$2
		shift 2
		;;
	--serverside-command)
		SERVERSIDE_COMMAND=$2
		shift 2
		;;
	--serverside-name)
		SERVERSIDE_NAME=$2
		shift 2
		;;
	*)
		echo Unrecognised option: $1
		shift
	esac
done
if [ "$TASKSET_SERVER" != "" ]; then
	echo TASKSET_SERVER: $TASKSET_SERVER
	echo TASKSET_CLIENT: $TASKSET_CLIENT
fi
if [ -z "$VERSION" ]; then
	VERSION=$DEFAULT_VERSION
fi


# Unconditionally fetch the tar to find out the real version number
TARFILE=dbench-${VERSION}.tar.gz
git_fetch $GIT_LOCATION dbench-${VERSION} $MIRROR_LOCATION/$TARFILE $SHELLPACK_SOURCES/$TARFILE $VERSION
cd $SHELLPACK_SOURCES
tar -xf $TARFILE
if [ $? -ne 0 ]; then
	error "$P: tar xf dbench-${VERSION}.tar.gz failed"
	popd > /dev/null
	exit $SHELLPACK_ERROR
fi

# Rename directory to something we expect.
DST_DIR=`tar tf $TARFILE | head -n 1 | awk -F / '{print $1}'`
mv $DST_DIR dbench-${VERSION}
pushd dbench-${VERSION} > /dev/null || die Failed to rename tar

# Build
if [ "$VERSION" = "4.1alpha" ]; then
LINESTART=`grep -n "==== BEGIN 0001-Allow-reporting-of-workload-execution-times.patch" $0 | tail -1 | awk -F : '{print $1}'`
LINEEND=`grep -n "==== END 0001-Allow-reporting-of-workload-execution-times.patch" $0 | tail -1 | awk -F : '{print $1}'`
if [ "$LINEEND" = "" ]; then
	LINECOUNT=`wc -l $0 | awk '{print $1}'`
fi
if [ "$LINESTART" = "" ]; then
	die Failed to find start of file 0001-Allow-reporting-of-workload-execution-times.patch
fi
echo Extracting $SHELLPACK_TEMP/0001-Allow-reporting-of-workload-execution-times.patch
sed -n $((LINESTART+1)),$((LINEEND-1))p $0 > $SHELLPACK_TEMP/0001-Allow-reporting-of-workload-execution-times.patch
	patch -p1 <$SHELLPACK_TEMP/0001-Allow-reporting-of-workload-execution-times.patch

LINESTART=`grep -n "==== BEGIN 0002-Defer-reporting-of-execution-times.patch" $0 | tail -1 | awk -F : '{print $1}'`
LINEEND=`grep -n "==== END 0002-Defer-reporting-of-execution-times.patch" $0 | tail -1 | awk -F : '{print $1}'`
if [ "$LINEEND" = "" ]; then
	LINECOUNT=`wc -l $0 | awk '{print $1}'`
fi
if [ "$LINESTART" = "" ]; then
	die Failed to find start of file 0002-Defer-reporting-of-execution-times.patch
fi
echo Extracting $SHELLPACK_TEMP/0002-Defer-reporting-of-execution-times.patch
sed -n $((LINESTART+1)),$((LINEEND-1))p $0 > $SHELLPACK_TEMP/0002-Defer-reporting-of-execution-times.patch
	patch -p1 <$SHELLPACK_TEMP/0002-Defer-reporting-of-execution-times.patch

LINESTART=`grep -n "==== BEGIN 0003-Include-stdint_h.patch" $0 | tail -1 | awk -F : '{print $1}'`
LINEEND=`grep -n "==== END 0003-Include-stdint_h.patch" $0 | tail -1 | awk -F : '{print $1}'`
if [ "$LINEEND" = "" ]; then
	LINECOUNT=`wc -l $0 | awk '{print $1}'`
fi
if [ "$LINESTART" = "" ]; then
	die Failed to find start of file 0003-Include-stdint_h.patch
fi
echo Extracting $SHELLPACK_TEMP/0003-Include-stdint_h.patch
sed -n $((LINESTART+1)),$((LINEEND-1))p $0 > $SHELLPACK_TEMP/0003-Include-stdint_h.patch
	patch -p1 <$SHELLPACK_TEMP/0003-Include-stdint_h.patch

LINESTART=`grep -n "==== BEGIN 0004-Check-if-parent-is-alive-once-per-loadfile.patch" $0 | tail -1 | awk -F : '{print $1}'`
LINEEND=`grep -n "==== END 0004-Check-if-parent-is-alive-once-per-loadfile.patch" $0 | tail -1 | awk -F : '{print $1}'`
if [ "$LINEEND" = "" ]; then
	LINECOUNT=`wc -l $0 | awk '{print $1}'`
fi
if [ "$LINESTART" = "" ]; then
	die Failed to find start of file 0004-Check-if-parent-is-alive-once-per-loadfile.patch
fi
echo Extracting $SHELLPACK_TEMP/0004-Check-if-parent-is-alive-once-per-loadfile.patch
sed -n $((LINESTART+1)),$((LINEEND-1))p $0 > $SHELLPACK_TEMP/0004-Check-if-parent-is-alive-once-per-loadfile.patch
	patch -p1 <$SHELLPACK_TEMP/0004-Check-if-parent-is-alive-once-per-loadfile.patch
else
LINESTART=`grep -n "==== BEGIN 0005-Check-if-parent-is-alive-once-per-loadfile.patch" $0 | tail -1 | awk -F : '{print $1}'`
LINEEND=`grep -n "==== END 0005-Check-if-parent-is-alive-once-per-loadfile.patch" $0 | tail -1 | awk -F : '{print $1}'`
if [ "$LINEEND" = "" ]; then
	LINECOUNT=`wc -l $0 | awk '{print $1}'`
fi
if [ "$LINESTART" = "" ]; then
	die Failed to find start of file 0005-Check-if-parent-is-alive-once-per-loadfile.patch
fi
echo Extracting $SHELLPACK_TEMP/0005-Check-if-parent-is-alive-once-per-loadfile.patch
sed -n $((LINESTART+1)),$((LINEEND-1))p $0 > $SHELLPACK_TEMP/0005-Check-if-parent-is-alive-once-per-loadfile.patch
	patch -p1 <$SHELLPACK_TEMP/0005-Check-if-parent-is-alive-once-per-loadfile.patch
fi

# Versions 4.0 and 4.1alpha both need to stop being kill-happy

pushd $SHELLPACK_SOURCES/dbench-${VERSION} || die Failed to change to source directory
for FILE in `find -name "*"`; do
	touch $FILE
done
./autogen.sh || die Failed to run autogen
export CFLAGS="-O2 $CFLAGS_MMTESTS_EXTRA"
eval ./configure --prefix=$SHELLPACK_SOURCES/dbench-${VERSION}-installed 
if [ $? -ne 0 ]; then
	cp /usr/share/automake*/config.guess .
	cp /usr/share/automake*/config.sub .
	eval ./configure --prefix=$SHELLPACK_SOURCES/dbench-${VERSION}-installed 
	if [ $? -ne 0 ]; then
		error "$P: configure failed"
		popd > /dev/null
		exit $SHELLPACK_ERROR
	fi
fi
unset CFLAGS
make -j$NUMCPUS 
if [ $? -ne 0 ]; then
	error "$P: make failed"
	popd > /dev/null
	exit $SHELLPACK_ERROR
fi
make install
if [ $? -ne 0 ]; then
	error "$P: make install failed"
	popd > /dev/null
	exit $SHELLPACK_ERROR
fi

# Fixups
if [ ! -e $SHELLPACK_SOURCES/dbench-${VERSION}-installed/share/client.txt ]; then
	cp $SHELLPACK_SOURCES/dbench-${VERSION}-installed/share/doc/dbench/loadfiles/client.txt $SHELLPACK_SOURCES/dbench-${VERSION}-installed/share
fi

# Extract our loadfiles from the original one
head -n 600 $SHELLPACK_SOURCES/dbench-${VERSION}-installed/share/client.txt >$SHELLPACK_SOURCES/dbench-${VERSION}-installed/share/client-warmup.txt
head -n 4173 $SHELLPACK_SOURCES/dbench-${VERSION}-installed/share/client.txt | tail -n 3573 >$SHELLPACK_SOURCES/dbench-${VERSION}-installed/share/client-tiny.txt

echo dbench installed successfully
exit $SHELLPACK_SUCCESS

==== BEGIN 0001-Allow-reporting-of-workload-execution-times.patch ====
From 6707ee487c57493f2e668bad05f3800b124a484c Mon Sep 17 00:00:00 2001
From: Jan Kara <jack@suse.cz>
Date: Thu, 9 Jun 2016 14:46:19 +0200
Subject: [PATCH] Allow reporting of workload execution times

Add option --show-execution-time which reports time it took to each
client to execute the given workload. This allows for better statistics
to be done with dbench results.

Signed-off-by: Jan Kara <jack@suse.cz>
---
 child.c  | 18 ++++++++++++++++++
 dbench.c |  3 +++
 dbench.h |  1 +
 3 files changed, 22 insertions(+)

diff --git a/child.c b/child.c
index aea81065d464..e4ae2304f040 100644
--- a/child.c
+++ b/child.c
@@ -326,6 +326,7 @@ void child_run(struct child_struct *child0, const char *loadfile)
 	int have_random = 0;
 	unsigned loop_count = 0;
 	z_off_t loop_start = 0;
+	struct timeval start;
 
 	gzf = gzopen(loadfile, "r");
 	if (gzf == NULL) {
@@ -349,6 +350,8 @@ again:
 		nb_time_reset(child);
 	}
 
+	gettimeofday(&start, NULL);
+
 	while (gzgets(gzf, line, sizeof(line)-1)) {
 		unsigned repeat_count = 1;
 
@@ -529,6 +532,21 @@ loop_again:
 		}
 	}
 
+	if (options.show_execute_time) {
+		struct timeval end;
+		unsigned int duration;
+
+		gettimeofday(&end, NULL);
+		duration = (end.tv_sec - start.tv_sec) * 1000 +
+			   (end.tv_usec - start.tv_usec) / 1000;
+		if (options.machine_readable)
+			printf("@E@%d@%u\n", child0->id, duration);
+		else {
+			printf("%4d completed in %u ms\n", child0->id,
+			       duration);
+		}
+	}
+
 	if (options.run_once) {
 		goto done;
 	}
diff --git a/dbench.c b/dbench.c
index 3f2c6c21e482..f01730ef8ea5 100644
--- a/dbench.c
+++ b/dbench.c
@@ -50,6 +50,7 @@ struct options options = {
 	.trunc_io            = 0,
 	.iscsi_initiatorname = "iqn.2011-09.org.samba.dbench:client",
 	.machine_readable    = 0,
+	.show_execute_time   = 0,
 };
 
 static struct timeval tv_start;
@@ -435,6 +436,8 @@ static void process_opts(int argc, const char **argv)
 		  "How many seconds of warmup to run", NULL },
 		{ "machine-readable", 0, POPT_ARG_NONE, &options.machine_readable, 0,
 		  "Print data in more machine-readable friendly format", NULL},
+		{ "show-execute-time", 0, POPT_ARG_NONE, &options.show_execute_time, 0,
+		  "Print time to execute passed workload", NULL},
 #ifdef HAVE_LIBSMBCLIENT
 		{ "smb-share",  0, POPT_ARG_STRING, &options.smb_share, 0, 
 		  "//SERVER/SHARE to use", NULL },
diff --git a/dbench.h b/dbench.h
index 14a5a702650c..465cf3b18002 100644
--- a/dbench.h
+++ b/dbench.h
@@ -159,6 +159,7 @@ struct options {
 	const char *iscsi_device;
 	const char *iscsi_initiatorname;
 	int machine_readable;
+	int show_execute_time;
 	const char *smb_share;
 	const char *smb_user;
 };
==== END 0001-Allow-reporting-of-workload-execution-times.patch ====

==== BEGIN 0002-Defer-reporting-of-execution-times.patch ====
From 09bfb355b1bac2b27b24e5b9969005389da1dccc Mon Sep 17 00:00:00 2001
From: Mel Gorman <mgorman@techsingularity.net>
Date: Thu, 10 Aug 2017 10:37:24 +0100
Subject: [PATCH] Defer reporting of execution times

If loadfiles are completed rapidly, there is a large amount of data
sent to stddout and then recorded which generates IO in itself. This
patch buffers durations and runtimes for a time. In some cases, it'll
be buffered until the end of the benchmark.

Signed-off-by: Mel Gorman <mgorman@techsingularity.net>
---
 child.c | 38 ++++++++++++++++++++++++++++++++++----
 1 file changed, 34 insertions(+), 4 deletions(-)

diff --git a/child.c b/child.c
index e4ae2304f040..828295d2aad9 100644
--- a/child.c
+++ b/child.c
@@ -309,9 +309,19 @@ static int parse_randomstring(char *line)
 	return 0;
 }
 
+void dump_samples(int id, unsigned int *duration, unsigned int *runtime, unsigned int nr_samples)
+{
+	unsigned int i;
+
+	for (i = 0; i < nr_samples; i++) {
+		printf("%4d completed in %u ms at time %u ms\n", id,
+		       duration[i], runtime[i]);
+	}
+}
 
 /* run a test that simulates an approximate netbench client load */
 #define MAX_PARM_LEN 1024
+#define MAX_SAMPLES 130672
 void child_run(struct child_struct *child0, const char *loadfile)
 {
 	int i;
@@ -326,7 +336,16 @@ void child_run(struct child_struct *child0, const char *loadfile)
 	int have_random = 0;
 	unsigned loop_count = 0;
 	z_off_t loop_start = 0;
-	struct timeval start;
+	struct timeval start, begin;
+	unsigned nr_samples = 0;
+
+	unsigned int *sample_duration = malloc(sizeof(unsigned int) * (MAX_SAMPLES + 1));
+	unsigned int *sample_runtime  = malloc(sizeof(unsigned int) * (MAX_SAMPLES + 1));
+
+	if (!sample_duration || !sample_runtime) {
+		printf("ENOMEM for samples\n");
+		exit(1);
+	}
 
 	gzf = gzopen(loadfile, "r");
 	if (gzf == NULL) {
@@ -345,6 +364,8 @@ void child_run(struct child_struct *child0, const char *loadfile)
 		memset(sparams[i], 0, MAX_PARM_LEN);
 	}
 
+	gettimeofday(&begin, NULL);
+
 again:
 	for (child=child0;child<child0+options.clients_per_process;child++) {
 		nb_time_reset(child);
@@ -534,16 +555,23 @@ void child_run(struct child_struct *child0, const char *loadfile)
 
 	if (options.show_execute_time) {
 		struct timeval end;
-		unsigned int duration;
+		unsigned int duration, runtime;
 
 		gettimeofday(&end, NULL);
 		duration = (end.tv_sec - start.tv_sec) * 1000 +
 			   (end.tv_usec - start.tv_usec) / 1000;
+		runtime = (end.tv_sec - begin.tv_sec) * 1000 +
+			   (end.tv_usec - begin.tv_usec) / 1000;
 		if (options.machine_readable)
 			printf("@E@%d@%u\n", child0->id, duration);
 		else {
-			printf("%4d completed in %u ms\n", child0->id,
-			       duration);
+			sample_duration[nr_samples] = duration;
+			sample_runtime[nr_samples] = runtime;
+			nr_samples++;
+			if (nr_samples == MAX_SAMPLES) {
+				dump_samples(child0->id, sample_duration, sample_runtime, nr_samples);
+				nr_samples = 0;
+			}
 		}
 	}
 
@@ -556,6 +584,8 @@ void child_run(struct child_struct *child0, const char *loadfile)
 
 done:
 	gzclose(gzf);
+	usleep(child0->id * 5000);
+	dump_samples(child0->id, sample_duration, sample_runtime, nr_samples);
 	for (child=child0;child<child0+options.clients_per_process;child++) {
 		child->cleanup = 1;
 		fflush(stdout);
==== END 0002-Defer-reporting-of-execution-times.patch ====

==== BEGIN 0003-Include-stdint_h.patch ====
From f637b5e1af49c8dcb062e812ca47a0d800cbc2a2 Mon Sep 17 00:00:00 2001
From: Mel Gorman <mgorman@techsingularity.net>
Date: Tue, 26 Sep 2017 13:26:33 +0100
Subject: [PATCH] libnfs: Include stdint.h

Changes in kernel headers break build unless this is included.

Signed-off-by: Mel Gorman <mgorman@techsingularity.net>
---
 libnfs.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/libnfs.c b/libnfs.c
index 1f60ef0b5af1..92711e2b9bcd 100644
--- a/libnfs.c
+++ b/libnfs.c
@@ -20,6 +20,7 @@
 #include "mount.h"
 #include "nfs.h"
 #include "libnfs.h"
+#include <stdint.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
==== END 0003-Include-stdint_h.patch ====

==== BEGIN 0004-Check-if-parent-is-alive-once-per-loadfile.patch ====
From 67638b44b9300a5ebc7bc1695a8f2ce2fc138e67 Mon Sep 17 00:00:00 2001
From: Mel Gorman <mgorman@techsingularity.net>
Date: Thu, 10 Aug 2017 10:49:24 +0100
Subject: [PATCH] Check if parent is alive once per loadfile processed

strace reports that a high percentage of time is spent calling kill()
with 12,000,000 calls in 3 minutes. Check if the parent is alive once per
load file processed. With later versions of apparmor, kill() is permission
checked which is very expensive in itself and unnecessary.  Instead use
the ligher getppid() call and check against the cached value.

Signed-off-by: Mel Gorman <mgorman@techsingularity.net>
---
 child.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/child.c b/child.c
index 828295d2aad9..7abb238ef5bd 100644
--- a/child.c
+++ b/child.c
@@ -371,6 +371,10 @@ void child_run(struct child_struct *child0, const char *loadfile)
 		nb_time_reset(child);
 	}
 
+	if (getppid() != parent) {
+		exit(1);
+	}
+
 	gettimeofday(&start, NULL);
 
 	while (gzgets(gzf, line, sizeof(line)-1)) {
@@ -384,10 +388,6 @@ void child_run(struct child_struct *child0, const char *loadfile)
 
 		params = sparams;
 
-		if (kill(parent, 0) == -1) {
-			exit(1);
-		}
-
 loop_again:
 		/* if this is a "LOOP <xxx>" line, 
 		 * remember the current file position and move to the next line
==== END 0004-Check-if-parent-is-alive-once-per-loadfile.patch ====

==== BEGIN 0005-Check-if-parent-is-alive-once-per-loadfile.patch ====
From 67638b44b9300a5ebc7bc1695a8f2ce2fc138e67 Mon Sep 17 00:00:00 2001
From: Mel Gorman <mgorman@techsingularity.net>
Date: Thu, 10 Aug 2017 10:49:24 +0100
Subject: [PATCH] Check if parent is alive once per loadfile processed

strace reports that a high percentage of time is spent calling kill()
with 12,000,000 calls in 3 minutes. Check if the parent is alive once per
load file processed. With later versions of apparmor, kill() is permission
checked which is very expensive in itself and unnecessary.  Instead use
the ligher getppid() call and check against the cached value.

Signed-off-by: Mel Gorman <mgorman@techsingularity.net>
---
 child.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

--- a/child.c
+++ b/child.c
@@ -193,13 +193,13 @@
 		nb_time_reset(child);
 	}
 
+	if (getppid() != parent) {
+		exit(1);
+	}
+
 	while (fgets(line, sizeof(line)-1, f)) {
 		params = sparams;
 
-		if (kill(parent, 0) == -1) {
-			exit(1);
-		}
-
 		for (child=child0;child<child0+options.clients_per_process;child++) {
 			if (child->done) goto done;
 			child->line++;
==== END 0005-Check-if-parent-is-alive-once-per-loadfile.patch ====
#### Description dbench
#### Details dbench 43
