Add support for modifying the randomness of a workload

Fio supports random or sequential IO, or random IO with a number of
sequential IOs in between. The percentage_random options allows
more fine grained control over this. It defaults to 100, which would
make any random workload still purely random. If set below 100,
there will be some percentage of sequential IOs. This happens randomly,
but at the specified percentages.

The percentage_sequential option is just the complement of that, it
is guaranteed that:

	percentage_sequential + percentage_random == 100%

The later setting has priority, each will adjust the other.

Signed-off-by: Jens Axboe <axboe@kernel.dk>
diff --git a/HOWTO b/HOWTO
index 6445348..8211c3b 100644
--- a/HOWTO
+++ b/HOWTO
@@ -766,6 +766,17 @@
 		random_distribution=zipf:1.2 as the option. If a non-uniform
 		model is used, fio will disable use of the random map.
 
+percentage_random=int	For a random workload, set how big a percentage should
+		be random. This defaults to 100%, in which case the workload
+		is fully random. It can be set from anywhere from 0 to 100.
+		Setting it to 0 would make the workload fully sequential. Any
+		setting in between will result in a random mix of sequential
+		and random IO, at the given percentages.
+	
+percentage_sequential=int	See percentage_random. It is guaranteed that
+		they add up to 100. The later setting has priority, each
+		will adjust the other.
+
 norandommap	Normally fio will cover every block of the file when doing
 		random IO. If this option is given, fio will just get a
 		new random offset without looking at past io history. This
diff --git a/cconv.c b/cconv.c
index 57c76e3..c2b2793 100644
--- a/cconv.c
+++ b/cconv.c
@@ -125,6 +125,8 @@
 	o->zipf_theta.u.f = fio_uint64_to_double(le64_to_cpu(top->zipf_theta.u.i));
 	o->pareto_h.u.f = fio_uint64_to_double(le64_to_cpu(top->pareto_h.u.i));
 	o->random_generator = le32_to_cpu(top->random_generator);
+	o->perc_rand = le32_to_cpu(top->perc_rand);
+	o->perc_seq = le32_to_cpu(top->perc_seq);
 	o->hugepage_size = le32_to_cpu(top->hugepage_size);
 	o->rw_min_bs = le32_to_cpu(top->rw_min_bs);
 	o->thinktime = le32_to_cpu(top->thinktime);
@@ -283,6 +285,8 @@
 	top->zipf_theta.u.i = __cpu_to_le64(fio_double_to_uint64(o->zipf_theta.u.f));
 	top->pareto_h.u.i = __cpu_to_le64(fio_double_to_uint64(o->pareto_h.u.f));
 	top->random_generator = cpu_to_le32(o->random_generator);
+	top->perc_rand = cpu_to_le32(o->perc_rand);
+	top->perc_seq = cpu_to_le32(o->perc_seq);
 	top->hugepage_size = cpu_to_le32(o->hugepage_size);
 	top->rw_min_bs = cpu_to_le32(o->rw_min_bs);
 	top->thinktime = cpu_to_le32(o->thinktime);
diff --git a/fio.1 b/fio.1
index 401d956..5082bf8 100644
--- a/fio.1
+++ b/fio.1
@@ -641,6 +641,15 @@
 random_distribution=zipf:1.2 as the option. If a non-uniform model is used,
 fio will disable use of the random map.
 .TP
+.BI percentage_random \fR=\fPint
+For a random workload, set how big a percentage should be random. This defaults
+to 100%, in which case the workload is fully random. It can be set from
+anywhere from 0 to 100.  Setting it to 0 would make the workload fully
+sequential.
+.TP
+.BI percentage_sequential \fR=\fPint
+See \fBpercentage_random\fR.
+.TP
 .B norandommap
 Normally \fBfio\fR will cover every block of the file when doing random I/O. If
 this parameter is given, a new offset will be chosen without looking at past
diff --git a/fio.h b/fio.h
index 5438b76..965d7d9 100644
--- a/fio.h
+++ b/fio.h
@@ -81,6 +81,7 @@
 	FIO_RAND_FILE_SIZE_OFF,
 	FIO_RAND_TRIM_OFF,
 	FIO_RAND_BUF_OFF,
+	FIO_RAND_SEQ_RAND_OFF,
 	FIO_RAND_NR_OFFS,
 };
 
@@ -256,6 +257,14 @@
 	unsigned int ddir_seq_nr;
 
 	/*
+	 * rand/seq mixed workload state
+	 */
+	union {
+		os_random_state_t seq_rand_state;
+		struct frand_state __seq_rand_state;
+	};
+
+	/*
 	 * IO history logs for verification. We use a tree for sorting,
 	 * if we are overwriting. Otherwise just use a fifo.
 	 */
diff --git a/init.c b/init.c
index aba7671..7246bd8 100644
--- a/init.c
+++ b/init.c
@@ -701,6 +701,7 @@
 		td->rand_seeds[FIO_RAND_BLOCK_OFF] = FIO_RANDSEED * td->thread_number;
 
 	os_random_seed(td->rand_seeds[FIO_RAND_BLOCK_OFF], &td->random_state);
+	os_random_seed(td->rand_seeds[FIO_RAND_SEQ_RAND_OFF], &td->seq_rand_state);
 }
 
 static void td_fill_rand_seeds_internal(struct thread_data *td)
@@ -722,6 +723,7 @@
 		td->rand_seeds[FIO_RAND_BLOCK_OFF] = FIO_RANDSEED * td->thread_number;
 
 	init_rand_seed(&td->__random_state, td->rand_seeds[FIO_RAND_BLOCK_OFF]);
+	init_rand_seed(&td->__seq_rand_state, td->rand_seeds[FIO_RAND_SEQ_RAND_OFF]);
 }
 
 void td_fill_rand_seeds(struct thread_data *td)
diff --git a/io_u.c b/io_u.c
index 19ef7b9..d03049e 100644
--- a/io_u.c
+++ b/io_u.c
@@ -191,6 +191,25 @@
 	return 1;
 }
 
+static int should_do_random(struct thread_data *td)
+{
+	unsigned int v;
+	unsigned long r;
+
+	if (td->o.perc_rand == 100)
+		return 1;
+
+	if (td->o.use_os_rand) {
+		r = os_random_long(&td->seq_rand_state);
+		v = 1 + (int) (100.0 * (r / (OS_RAND_MAX + 1.0)));
+	} else {
+		r = __rand(&td->__seq_rand_state);
+		v = 1 + (int) (100.0 * (r / (FRAND_MAX + 1.0)));
+	}
+
+	return v <= td->o.perc_rand;
+}
+
 static int get_next_rand_offset(struct thread_data *td, struct fio_file *f,
 				enum fio_ddir ddir, uint64_t *b)
 {
@@ -285,9 +304,16 @@
 	b = offset = -1ULL;
 
 	if (rw_seq) {
-		if (td_random(td))
-			ret = get_next_rand_block(td, f, ddir, &b);
-		else
+		if (td_random(td)) {
+			if (should_do_random(td))
+				ret = get_next_rand_block(td, f, ddir, &b);
+			else {
+				io_u->flags |= IO_U_F_BUSY_OK;
+				ret = get_next_seq_offset(td, f, ddir, &offset);
+				if (ret)
+					ret = get_next_rand_block(td, f, ddir, &b);
+			}
+		} else
 			ret = get_next_seq_offset(td, f, ddir, &offset);
 	} else {
 		io_u->flags |= IO_U_F_BUSY_OK;
diff --git a/options.c b/options.c
index 1219803..97c5b6f 100644
--- a/options.c
+++ b/options.c
@@ -376,6 +376,25 @@
 	return 0;
 }
 
+static int str_perc_rand_cb(void *data, unsigned long long *val)
+{
+	struct thread_data *td = data;
+
+	td->o.perc_rand = *val;
+	td->o.perc_seq = 100 - *val;
+	return 0;
+}
+
+static int str_perc_seq_cb(void *data, unsigned long long *val)
+{
+	struct thread_data *td = data;
+
+	td->o.perc_seq = *val;
+	td->o.perc_rand = 100 - *val;
+	return 0;
+}
+
+
 static int str_exitall_cb(void)
 {
 	exitall_on_terminate = 1;
@@ -1643,6 +1662,32 @@
 		.group	= FIO_OPT_G_RANDOM,
 	},
 	{
+		.name	= "percentage_random",
+		.lname	= "Percentage Random",
+		.type	= FIO_OPT_INT,
+		.cb	= str_perc_rand_cb,
+		.maxval	= 100,
+		.help	= "Percentage of seq/random mix that should be random",
+		.def	= "100",
+		.interval = 5,
+		.inverse = "percentage_sequential",
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_RANDOM,
+	},
+	{
+		.name	= "percentage_sequential",
+		.lname	= "Percentage Sequential",
+		.type	= FIO_OPT_INT,
+		.cb	= str_perc_seq_cb,
+		.maxval	= 100,
+		.help	= "Percentage of seq/random mix that should be sequential",
+		.def	= "0",
+		.interval = 5,
+		.inverse = "percentage_random",
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_RANDOM,
+	},
+	{
 		.name	= "nrfiles",
 		.lname	= "Number of files",
 		.alias	= "nr_files",
diff --git a/thread_options.h b/thread_options.h
index f25988a..138d026 100644
--- a/thread_options.h
+++ b/thread_options.h
@@ -113,6 +113,9 @@
 
 	unsigned int random_generator;
 
+	unsigned int perc_rand;
+	unsigned int perc_seq;
+
 	unsigned int hugepage_size;
 	unsigned int rw_min_bs;
 	unsigned int thinktime;
@@ -322,6 +325,9 @@
 
 	uint32_t random_generator;
 
+	uint32_t perc_rand;
+	uint32_t perc_seq;
+
 	uint32_t hugepage_size;
 	uint32_t rw_min_bs;
 	uint32_t thinktime;