Allow percentage setting for size=

Sometimes it's useful to set this to a particular size of a device.
Allowing percentages makes this more easy, as one does not have to
do the math outside of fio and pass in as environment variables
or custom job files.

To use, simply add a

	size=15%

or whatever is needed, then fio will use 15% of the total size of
the device(s) or file(s) given. Percentage can be from 1 to 100.

Signed-off-by: Jens Axboe <jaxboe@fusionio.com>
diff --git a/HOWTO b/HOWTO
index ee899b8..ce1bd9e 100644
--- a/HOWTO
+++ b/HOWTO
@@ -382,7 +382,10 @@
 		fio will divide this size between the available files
 		specified by the job. If not set, fio will use the full
 		size of the given files or devices. If the the files
-		do not exist, size must be given.
+		do not exist, size must be given. It is also possible to
+		give size as a percentage between 1 and 100. If size=20%
+		is given, fio will use 20% of the full size of the given
+		files or devices.
 
 filesize=int	Individual file sizes. May be a range, in which case fio
 		will select sizes for files at random within the given range
diff --git a/filesetup.c b/filesetup.c
index 2a79e74..8f51592 100644
--- a/filesetup.c
+++ b/filesetup.c
@@ -759,6 +759,9 @@
 		}
 	}
 
+	if (td->o.size_percent)
+		total_size = (total_size * td->o.size_percent) / 100;
+
 	if (!td->o.size || td->o.size > total_size)
 		td->o.size = total_size;
 
diff --git a/fio.1 b/fio.1
index 0838d07..b7046d5 100644
--- a/fio.1
+++ b/fio.1
@@ -257,7 +257,9 @@
 Unless \fBnrfiles\fR and \fBfilesize\fR options are given, this amount will be
 divided between the available files for the job. If not set, fio will use the
 full size of the given files or devices. If the the files do not exist, size
-must be given.
+must be given. It is also possible to give size as a percentage between 1 and
+100. If size=20% is given, fio will use 20% of the full size of the given files
+or devices.
 .TP
 .BI fill_device \fR=\fPbool "\fR,\fB fill_fs" \fR=\fPbool
 Sets size to something really large and waits for ENOSPC (no space left on
diff --git a/fio.h b/fio.h
index b869dd1..d8e3011 100644
--- a/fio.h
+++ b/fio.h
@@ -165,6 +165,7 @@
 	unsigned int iodepth_batch_complete;
 
 	unsigned long long size;
+	unsigned int size_percent;
 	unsigned int fill_device;
 	unsigned long long file_size_low;
 	unsigned long long file_size_high;
diff --git a/options.c b/options.c
index b4456a4..74c6478 100644
--- a/options.c
+++ b/options.c
@@ -744,6 +744,20 @@
 	return 0;
 }
 
+static int str_size_cb(void *data, unsigned long long *__val)
+{
+	struct thread_data *td = data;
+	unsigned long long v = *__val;
+
+	if (parse_is_percent(v)) {
+		td->o.size = 0;
+		td->o.size_percent = -1ULL - v;
+	} else
+		td->o.size = v;
+
+	return 0;
+}
+
 static int rw_verify(struct fio_option *o, void *data)
 {
 	struct thread_data *td = data;
@@ -1031,8 +1045,7 @@
 	{
 		.name	= "size",
 		.type	= FIO_OPT_STR_VAL,
-		.off1	= td_var_offset(size),
-		.minval = 1,
+		.cb	= str_size_cb,
 		.help	= "Total size of device or files",
 	},
 	{
diff --git a/parse.c b/parse.c
index 97ea4aa..ef23fbe 100644
--- a/parse.c
+++ b/parse.c
@@ -118,7 +118,8 @@
 	}
 }
 
-static unsigned long long __get_mult_bytes(const char *p, void *data)
+static unsigned long long __get_mult_bytes(const char *p, void *data,
+					   int *percent)
 {
 	unsigned int kb_base = fio_get_kb_base(data);
 	unsigned long long ret = 1;
@@ -158,6 +159,10 @@
 		pow = 2;
 	else if (!strcmp("k", c) || !strcmp("kb", c))
 		pow = 1;
+	else if (!strcmp("%", c)) {
+		*percent = 1;
+		return ret;
+	}
 
 	while (pow--)
 		ret *= (unsigned long long) mult;
@@ -166,12 +171,13 @@
 	return ret;
 }
 
-static unsigned long long get_mult_bytes(const char *str, int len, void *data)
+static unsigned long long get_mult_bytes(const char *str, int len, void *data,
+					 int *percent)
 {
 	const char *p = str;
 
 	if (len < 2)
-		return __get_mult_bytes(str, data);
+		return __get_mult_bytes(str, data, percent);
 
         /*
          * Go forward until we hit a non-digit
@@ -182,10 +188,10 @@
 		p++;
 	}
 
-	if (!isalpha((int) *p))
+	if (!isalpha((int) *p) && (*p != '%'))
 		p = NULL;
 
-	return __get_mult_bytes(p, data);
+	return __get_mult_bytes(p, data, percent);
 }
 
 /*
@@ -208,9 +214,16 @@
 	if (*val == LONG_MAX && errno == ERANGE)
 		return 1;
 
-	if (kilo)
-		*val *= get_mult_bytes(str, len, data);
-	else
+	if (kilo) {
+		unsigned long long mult;
+		int perc = 0;
+
+		mult = get_mult_bytes(str, len, data, &perc);
+		if (perc)
+			*val = -1ULL - *val;
+		else
+			*val *= mult;
+	} else
 		*val *= get_mult_time(str[len - 1]);
 
 	return 0;
diff --git a/parse.h b/parse.h
index 41e3633..2dd8459 100644
--- a/parse.h
+++ b/parse.h
@@ -89,4 +89,9 @@
 #define max(a, b)	((a) > (b) ? (a) : (b))
 #endif
 
+static inline int parse_is_percent(unsigned long long val)
+{
+	return val <= -1ULL && val >= (-1ULL - 100ULL);
+}
+
 #endif