Add support for 'bssplit' option, fine grained block size contrl

From the HOWTO addition:

bssplit=str	Sometimes you want even finer grained control of the
		block sizes issued, not just an even split between them.
		This option allows you to weight various block sizes,
		so that you are able to define a specific amount of
		block sizes issued. The format for this option is:

			bssplit=blocksize/percentage:blocksize/percentage

		for as many block sizes as needed. So if you want to define
		a workload that has 50% 64k blocks, 10% 4k blocks, and
		40% 32k blocks, you would write:

			bssplit=4k/10:64k/50:32k/40

		Ordering does not matter. If the percentage is left blank,
		fio will fill in the remaining values evenly. So a bssplit
		option like this one:

			bssplit=4k/50:1k/:32k/

		would have 50% 4k ios, and 25% 1k and 32k ios. The percentages
		always add up to 100, if bssplit is given a range that adds
		up to more, it will error out.

Signed-off-by: Jens Axboe <jens.axboe@oracle.com>
diff --git a/HOWTO b/HOWTO
index d5fb52c..569b998 100644
--- a/HOWTO
+++ b/HOWTO
@@ -281,6 +281,30 @@
 		writes, however a second range can be given after a comma.
 		See bs=.
 
+bssplit=str	Sometimes you want even finer grained control of the
+		block sizes issued, not just an even split between them.
+		This option allows you to weight various block sizes,
+		so that you are able to define a specific amount of
+		block sizes issued. The format for this option is:
+
+			bssplit=blocksize/percentage:blocksize/percentage
+
+		for as many block sizes as needed. So if you want to define
+		a workload that has 50% 64k blocks, 10% 4k blocks, and
+		40% 32k blocks, you would write:
+
+			bssplit=4k/10:64k/50:32k/40
+
+		Ordering does not matter. If the percentage is left blank,
+		fio will fill in the remaining values evenly. So a bssplit
+		option like this one:
+
+			bssplit=4k/50:1k/:32k/
+
+		would have 50% 4k ios, and 25% 1k and 32k ios. The percentages
+		always add up to 100, if bssplit is given a range that adds
+		up to more, it will error out.
+
 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 ca9fede..ca6e290 100644
--- a/fio.h
+++ b/fio.h
@@ -373,6 +373,11 @@
 	unsigned long total_run_time;
 };
 
+struct bssplit {
+	unsigned int bs;
+	unsigned char perc;
+};
+
 struct thread_options {
 	int pad;
 	char *description;
@@ -395,6 +400,8 @@
 	unsigned int bs[2];
 	unsigned int min_bs[2];
 	unsigned int max_bs[2];
+	struct bssplit *bssplit;
+	unsigned int bssplit_nr;
 
 	unsigned int nr_files;
 	unsigned int open_files;
diff --git a/io_u.c b/io_u.c
index 93c451c..7890a87 100644
--- a/io_u.c
+++ b/io_u.c
@@ -186,7 +186,21 @@
 		buflen = td->o.min_bs[ddir];
 	else {
 		r = os_random_long(&td->bsrange_state);
-		buflen = (unsigned int) (1 + (double) (td->o.max_bs[ddir] - 1) * r / (RAND_MAX + 1.0));
+		if (!td->o.bssplit_nr)
+			buflen = (unsigned int) (1 + (double) (td->o.max_bs[ddir] - 1) * r / (RAND_MAX + 1.0));
+		else {
+			long perc = 0;
+			unsigned int i;
+
+			for (i = 0; i < td->o.bssplit_nr; i++) {
+				struct bssplit *bsp = &td->o.bssplit[i];
+
+				buflen = bsp->bs;
+				perc += bsp->perc;
+				if (r <= ((LONG_MAX / 100L) * perc))
+					break;
+			}
+		}
 		if (!td->o.bs_unaligned)
 			buflen = (buflen + td->o.min_bs[ddir] - 1) & ~(td->o.min_bs[ddir] - 1);
 	}
diff --git a/options.c b/options.c
index c56b9df..055fbab 100644
--- a/options.c
+++ b/options.c
@@ -28,6 +28,120 @@
 	return strdup(p);
 }
 
+static int bs_cmp(const void *p1, const void *p2)
+{
+	const struct bssplit *bsp1 = p1;
+	const struct bssplit *bsp2 = p2;
+
+	return bsp1->perc < bsp2->perc;
+}
+
+static int str_bssplit_cb(void *data, const char *input)
+{
+	struct thread_data *td = data;
+	char *fname, *str, *p;
+	unsigned int i, perc, perc_missing;
+	unsigned int max_bs, min_bs;
+	long long val;
+
+	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));
+
+	i = 0;
+	max_bs = 0;
+	min_bs = -1;
+	while ((fname = strsep(&str, ":")) != NULL) {
+		char *perc_str;
+
+		if (!strlen(fname))
+			break;
+
+		/*
+		 * 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 * sizeof(struct bssplit));
+		}
+
+		perc_str = strstr(fname, "/");
+		if (perc_str) {
+			*perc_str = '\0';
+			perc_str++;
+			perc = atoi(perc_str);
+			if (perc > 100)
+				perc = 100;
+			else if (!perc)
+				perc = -1;
+		} else
+			perc = -1;
+
+		if (str_to_decimal(fname, &val, 1)) {
+			log_err("fio: bssplit conversion failed\n");
+			free(td->o.bssplit);
+			return 1;
+		}
+
+		if (val > max_bs)
+			max_bs = val;
+		if (val < min_bs)
+			min_bs = val;
+
+		td->o.bssplit[i].bs = val;
+		td->o.bssplit[i].perc = perc;
+		i++;
+	}
+
+	td->o.bssplit_nr = 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];
+
+		if (bsp->perc == (unsigned char) -1)
+			perc_missing++;
+		else
+			perc += bsp->perc;
+	}
+
+	if (perc > 100) {
+		log_err("fio: bssplit percentages add to more than 100%%\n");
+		free(td->o.bssplit);
+		return 1;
+	}
+	/*
+	 * If values didn't have a percentage set, divide the remains between
+	 * them.
+	 */
+	if (perc_missing) {
+		for (i = 0; i < td->o.bssplit_nr; i++) {
+			struct bssplit *bsp = &td->o.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;
+
+	/*
+	 * now sort based on percentages, for ease of lookup
+	 */
+	qsort(td->o.bssplit, td->o.bssplit_nr, sizeof(struct bssplit), bs_cmp);
+
+	free(p);
+	return 0;
+}
+
 static int str_rw_cb(void *data, const char *str)
 {
 	struct thread_data *td = data;
@@ -460,6 +574,13 @@
 		.parent = "rw",
 	},
 	{
+		.name	= "bssplit",
+		.type	= FIO_OPT_STR,
+		.cb	= str_bssplit_cb,
+		.help	= "Set a specific mix of block sizes",
+		.parent	= "rw",
+	},
+	{
 		.name	= "bs_unaligned",
 		.alias	= "blocksize_unaligned",
 		.type	= FIO_OPT_STR_SET,
diff --git a/parse.c b/parse.c
index 879a3aa..f907a34 100644
--- a/parse.c
+++ b/parse.c
@@ -106,7 +106,7 @@
 /*
  * convert string into decimal value, noting any size suffix
  */
-static int str_to_decimal(const char *str, long long *val, int kilo)
+int str_to_decimal(const char *str, long long *val, int kilo)
 {
 	int len;
 
diff --git a/parse.h b/parse.h
index c3b66d7..42046b5 100644
--- a/parse.h
+++ b/parse.h
@@ -58,6 +58,7 @@
 
 extern void strip_blank_front(char **);
 extern void strip_blank_end(char *);
+extern int str_to_decimal(const char *, long long *, int);
 
 /*
  * Handlers for the options