Add support for different weights for reads and writes with bssplit

You can now do:

	bssplit=4k/20:8k/80,1k/50:4k/50

To have 20% 4k reads and 80% 8k reads, while having 50% 1k writes
and 50% 4k writes. This is identical to how the bs= option treats
reads and writes.

Signed-off-by: Jens Axboe <jens.axboe@oracle.com>
diff --git a/HOWTO b/HOWTO
index 999f777..15f576d 100644
--- a/HOWTO
+++ b/HOWTO
@@ -367,6 +367,15 @@
 		always add up to 100, if bssplit is given a range that adds
 		up to more, it will error out.
 
+		bssplit also supports giving separate splits to reads and
+		writes. The format is identical to what bs= accepts. You
+		have to separate the read and write parts with a comma. So
+		if you want a workload that has 50% 2k reads and 50% 4k reads,
+		while having 90% 4k writes and 10% 8k writes, you would
+		specify:
+
+		bssplit=2k/50:4k/50,4k/90,8k/10
+
 blocksize_unaligned
 bs_unaligned	If this option is given, any byte size value within bsrange
 		may be used as a block range. This typically wont work with
diff --git a/fio.h b/fio.h
index ff0068c..f5786ea 100644
--- a/fio.h
+++ b/fio.h
@@ -432,8 +432,8 @@
 	unsigned int ba[2];
 	unsigned int min_bs[2];
 	unsigned int max_bs[2];
-	struct bssplit *bssplit;
-	unsigned int bssplit_nr;
+	struct bssplit *bssplit[2];
+	unsigned int bssplit_nr[2];
 
 	unsigned int nr_files;
 	unsigned int open_files;
diff --git a/io_u.c b/io_u.c
index 7108939..d92d940 100644
--- a/io_u.c
+++ b/io_u.c
@@ -248,7 +248,7 @@
 		buflen = minbs;
 	else {
 		r = os_random_long(&td->bsrange_state);
-		if (!td->o.bssplit_nr) {
+		if (!td->o.bssplit_nr[ddir]) {
 			buflen = 1 + (unsigned int) ((double) maxbs *
 					(r / (OS_RAND_MAX + 1.0)));
 			if (buflen < minbs)
@@ -257,8 +257,8 @@
 			long perc = 0;
 			unsigned int i;
 
-			for (i = 0; i < td->o.bssplit_nr; i++) {
-				struct bssplit *bsp = &td->o.bssplit[i];
+			for (i = 0; i < td->o.bssplit_nr[ddir]; i++) {
+				struct bssplit *bsp = &td->o.bssplit[ddir][i];
 
 				buflen = bsp->bs;
 				perc += bsp->perc;
diff --git a/options.c b/options.c
index 9700110..21de222 100644
--- a/options.c
+++ b/options.c
@@ -40,21 +40,16 @@
 	return bsp1->perc < bsp2->perc;
 }
 
-static int str_bssplit_cb(void *data, const char *input)
+static int bssplit_ddir(struct thread_data *td, int ddir, char *str)
 {
-	struct thread_data *td = data;
-	char *fname, *str, *p;
+	struct bssplit *bssplit;
 	unsigned int i, perc, perc_missing;
 	unsigned int max_bs, min_bs;
 	long long val;
+	char *fname;
 
-	p = str = strdup(input);
-
-	strip_blank_front(&str);
-	strip_blank_end(str);
-
-	td->o.bssplit_nr = 4;
-	td->o.bssplit = malloc(4 * sizeof(struct bssplit));
+	td->o.bssplit_nr[ddir] = 4;
+	bssplit = malloc(4 * sizeof(struct bssplit));
 
 	i = 0;
 	max_bs = 0;
@@ -68,10 +63,9 @@
 		/*
 		 * grow struct buffer, if needed
 		 */
-		if (i == td->o.bssplit_nr) {
-			td->o.bssplit_nr <<= 1;
-			td->o.bssplit = realloc(td->o.bssplit,
-						td->o.bssplit_nr
+		if (i == td->o.bssplit_nr[ddir]) {
+			td->o.bssplit_nr[ddir] <<= 1;
+			bssplit = realloc(bssplit, td->o.bssplit_nr[ddir]
 						  * sizeof(struct bssplit));
 		}
 
@@ -98,19 +92,19 @@
 		if (val < min_bs)
 			min_bs = val;
 
-		td->o.bssplit[i].bs = val;
-		td->o.bssplit[i].perc = perc;
+		bssplit[i].bs = val;
+		bssplit[i].perc = perc;
 		i++;
 	}
 
-	td->o.bssplit_nr = i;
+	td->o.bssplit_nr[ddir] = i;
 
 	/*
 	 * Now check if the percentages add up, and how much is missing
 	 */
 	perc = perc_missing = 0;
-	for (i = 0; i < td->o.bssplit_nr; i++) {
-		struct bssplit *bsp = &td->o.bssplit[i];
+	for (i = 0; i < td->o.bssplit_nr[ddir]; i++) {
+		struct bssplit *bsp = &bssplit[i];
 
 		if (bsp->perc == (unsigned char) -1)
 			perc_missing++;
@@ -120,7 +114,7 @@
 
 	if (perc > 100) {
 		log_err("fio: bssplit percentages add to more than 100%%\n");
-		free(td->o.bssplit);
+		free(bssplit);
 		return 1;
 	}
 	/*
@@ -128,24 +122,58 @@
 	 * them.
 	 */
 	if (perc_missing) {
-		for (i = 0; i < td->o.bssplit_nr; i++) {
-			struct bssplit *bsp = &td->o.bssplit[i];
+		for (i = 0; i < td->o.bssplit_nr[ddir]; i++) {
+			struct bssplit *bsp = &bssplit[i];
 
 			if (bsp->perc == (unsigned char) -1)
 				bsp->perc = (100 - perc) / perc_missing;
 		}
 	}
 
-	td->o.min_bs[DDIR_READ] = td->o.min_bs[DDIR_WRITE] = min_bs;
-	td->o.max_bs[DDIR_READ] = td->o.max_bs[DDIR_WRITE] = max_bs;
+	td->o.min_bs[ddir] = min_bs;
+	td->o.max_bs[ddir] = max_bs;
 
 	/*
 	 * now sort based on percentages, for ease of lookup
 	 */
-	qsort(td->o.bssplit, td->o.bssplit_nr, sizeof(struct bssplit), bs_cmp);
+	qsort(bssplit, td->o.bssplit_nr[ddir], sizeof(struct bssplit), bs_cmp);
+	td->o.bssplit[ddir] = bssplit;
+	return 0;
+
+}
+
+static int str_bssplit_cb(void *data, const char *input)
+{
+	struct thread_data *td = data;
+	char *str, *p, *odir;
+	int ret = 0;
+
+	p = str = strdup(input);
+
+	strip_blank_front(&str);
+	strip_blank_end(str);
+
+	odir = strchr(str, ',');
+	if (odir) {
+		ret = bssplit_ddir(td, DDIR_WRITE, odir + 1);
+		if (!ret) {
+			*odir = '\0';
+			ret = bssplit_ddir(td, DDIR_READ, str);
+		}
+	} else {
+		char *op;
+
+		op = strdup(str);
+
+		ret = bssplit_ddir(td, DDIR_READ, str);
+		if (!ret)
+			ret = bssplit_ddir(td, DDIR_WRITE, op);
+
+		free(op);
+	}
 
 	free(p);
-	return 0;
+	return ret;
 }
 
 static int str_rw_cb(void *data, const char *str)