#!/bin/bash
# dbench installer
###SHELLPACK preamble dbench-install 0
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

###SHELLPACK parseargBegin
###SHELLPACK parseargEnd


###SHELLPACK git_fetch dbench-${VERSION}.tar.gz dbench-${VERSION}

# Build
if [ "$VERSION" = "4.1alpha" ]; then
	###SHELLPACK self_extract 0001-Allow-reporting-of-workload-execution-times.patch
	patch -p1 <$SHELLPACK_TEMP/0001-Allow-reporting-of-workload-execution-times.patch

	###SHELLPACK self_extract 0002-Defer-reporting-of-execution-times.patch
	patch -p1 <$SHELLPACK_TEMP/0002-Defer-reporting-of-execution-times.patch

	###SHELLPACK self_extract 0003-Include-stdint_h.patch
	patch -p1 <$SHELLPACK_TEMP/0003-Include-stdint_h.patch

	###SHELLPACK self_extract 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
	###SHELLPACK self_extract 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

###SHELLPACK build_start dbench-${VERSION}
###SHELLPACK build_autogen dbench-${VERSION}
###SHELLPACK build_configure dbench-${VERSION}
###SHELLPACK make_make_install

# 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 ====
