Recent changes (master)

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



The following changes since commit 5afda955b4dbdc9938655ad5cb843191d5828226:

  options: make unit_base be posval[] based (2013-04-09 21:05:14 +0200)

are available in the git repository at:
  git://git.kernel.dk/fio.git master

Jens Axboe (14):
      parse: for option with posval, dump allowed values on failure
      Convert unit_base to posval[]
      parse: don't print min/max option ranges if posval[] is set
      Add thread_options.h
      Unify options with gfio
      parse: bring in more stuff from gfio
      parse: fix spelling error
      Categorize engine and profile options
      tiobench: remove duplicate profile group
      Move rbtree to lib/
      README: remove partial option list
      Merge missing content to man page
      Merge in zipf differences from gfio
      Merge in crc32c-intel probe

 HOWTO                    |    1 +
 Makefile                 |    2 +-
 README                   |  112 +------
 backend.c                |    2 +
 engines/e4defrag.c       |    4 +
 engines/libaio.c         |    2 +
 engines/net.c            |   12 +
 filesetup.c              |    8 +-
 fio.1                    |    8 +-
 fio.h                    |  250 +------------
 iolog.h                  |    2 +
 rbtree.c => lib/rbtree.c |    0
 rbtree.h => lib/rbtree.h |    0
 options.c                |  947 ++++++++++++++++++++++++++++++++++++++--------
 options.h                |   81 ++++
 parse.c                  |   20 +-
 parse.h                  |    8 +
 profiles/tiobench.c      |   10 +
 stat.h                   |    2 +
 thread_options.h         |  264 +++++++++++++
 verify.c                 |    8 +
 verify.h                 |    1 +
 22 files changed, 1216 insertions(+), 528 deletions(-)
 rename rbtree.c => lib/rbtree.c (100%)
 rename rbtree.h => lib/rbtree.h (100%)
 create mode 100644 thread_options.h

---

Diff of recent changes:

diff --git a/HOWTO b/HOWTO
index 76effee..eab53d2 100644
--- a/HOWTO
+++ b/HOWTO
@@ -1241,6 +1241,7 @@ log_avg_msec=int By default, fio will log an entry in the iops, latency,
 lockmem=int	Pin down the specified amount of memory with mlock(2). Can
 		potentially be used instead of removing memory or booting
 		with less memory to simulate a smaller amount of memory.
+		The amount specified is per worker.
 
 exec_prerun=str	Before running this job, issue the command specified
 		through system(3).
diff --git a/Makefile b/Makefile
index a5322a9..13a6b8a 100644
--- a/Makefile
+++ b/Makefile
@@ -22,7 +22,7 @@ SCRIPTS = fio_generate_plots
 
 SOURCE := gettime.c fio.c ioengines.c init.c stat.c log.c time.c filesetup.c \
 		eta.c verify.c memory.c io_u.c parse.c mutex.c options.c \
-		rbtree.c smalloc.c filehash.c profile.c debug.c lib/rand.c \
+		lib/rbtree.c smalloc.c filehash.c profile.c debug.c lib/rand.c \
 		lib/num2str.c lib/ieee754.c $(wildcard crc/*.c) engines/cpu.c \
 		engines/mmap.c engines/sync.c engines/null.c engines/net.c \
 		memalign.c server.c client.c iolog.c backend.c libfio.c flow.c \
diff --git a/README b/README
index 317ddec..8db962b 100644
--- a/README
+++ b/README
@@ -224,115 +224,9 @@ complex setups with the command line, but generally it's a lot easier to
 just write a simple job file to describe the workload. The job file format
 is in the ini style format, as that is easy to read and write for the user.
 
-The job file parameters are:
-
-	name=x		Use 'x' as the identifier for this job.
-	description=x	'x' is a text description of the job.
-	directory=x	Use 'x' as the top level directory for storing files
-	filename=x	Force the use of 'x' as the filename for all files
-			in this thread. If not given, fio will make up
-			a suitable filename based on the thread and file
-			number.
-	rw=x		'x' may be: read, randread, write, randwrite,
-			rw (read-write mix), randrw (read-write random mix)
-	rwmixcycle=x	Base cycle for switching between read and write
-			in msecs.
-	rwmixread=x	'x' percentage of rw mix ios will be reads. If
-			rwmixwrite is also given, the last of the two will
-			 be used if they don't add up to 100%.
-	rwmixwrite=x	'x' percentage of rw mix ios will be writes. See
-			rwmixread.
-	rand_repeatable=x  The sequence of random io blocks can be repeatable
-			across runs, if 'x' is 1.
-	size=x		Set file size to x bytes (x string can include k/m/g)
-	ioengine=x	'x' may be: aio/libaio/linuxaio for Linux aio,
-			posixaio for POSIX aio, solarisaio for Solaris
-			native async IO, windowsaio for Windows native async IO,
-			sync for regular read/write io,
-			psync for regular pread/pwrite io, vsync for regular
-			readv/writev (with queuing emulation) mmap for mmap'ed
-			io, syslet-rw for syslet driven read/write, splice for
-			using splice/vmsplice, sg for direct SG_IO io, net
-			for network io, rdma for RDMA io, or cpuio for a
-			cycler burner load. sg only works on Linux on
-			SCSI (or SCSI-like devices, such as usb-storage or
-			sata/libata driven) devices. Fio also has a null
-			io engine, which is mainly used for testing
-			fio itself.
-
-	iodepth=x	For async io, allow 'x' ios in flight
-	overwrite=x	If 'x', layout a write file first.
-	nrfiles=x	Spread io load over 'x' number of files per job,
-			if possible.
-	prio=x		Run io at prio X, 0-7 is the kernel allowed range
-	prioclass=x	Run io at prio class X
-	bs=x		Use 'x' for thread blocksize. May include k/m postfix.
-	bsrange=x-y	Mix thread block sizes randomly between x and y. May
-			also include k/m postfix.
-	direct=x	1 for direct IO, 0 for buffered IO
-	thinktime=x	"Think" x usec after each io
-	rate=x		Throttle rate to x KB/sec
-	ratemin=x	Quit if rate of x KB/sec can't be met
-	ratecycle=x	ratemin averaged over x msecs
-	cpumask=x	Only allow job to run on CPUs defined by mask.
-	cpus_allowed=x	Like 'cpumask', but allow text setting of CPU affinity.
-	numa_cpu_nodes=x,y-z  Allow job to run on specified NUMA nodes' CPU.
-	numa_mem_policy=m:x,y-z  Setup numa memory allocation policy.
-			'm' stands for policy, such as local, interleave,
-			bind, prefer, local. 'x, y-z' are numa node(s) for
-			memory allocation according to policy.
-	fsync=x		If writing with buffered IO, fsync after every
-			'x' blocks have been written.
-	end_fsync=x	If 'x', run fsync() after end-of-job.
-	startdelay=x	Start this thread x seconds after startup
-	runtime=x	Terminate x seconds after startup. Can include a
-			normal time suffix if not given in seconds, such as
-			'm' for minutes, 'h' for hours, and 'd' for days.
-	offset=x	Start io at offset x (x string can include k/m/g)
-	invalidate=x	Invalidate page cache for file prior to doing io
-	sync=x		Use sync writes if x and writing buffered IO.
-	mem=x		If x == malloc, use malloc for buffers. If x == shm,
-			use shared memory for buffers. If x == mmap, use
-			anonymous mmap.
-	exitall		When one thread quits, terminate the others
-	bwavgtime=x	Average bandwidth stats over an x msec window.
-	create_serialize=x	If 'x', serialize file creation.
-	create_fsync=x	If 'x', run fsync() after file creation.
-	unlink		If set, unlink files when done.
-	loops=x		Run the job 'x' number of times.
-	verify=x	If 'x' == md5, use md5 for verifies. If 'x' == crc32,
-			use crc32 for verifies. md5 is 'safer', but crc32 is
-			a lot faster. Only makes sense for writing to a file.
-			For other types of checksumming, see HOWTO.
-	stonewall	Wait for preceeding jobs to end before running.
-	numjobs=x	Create 'x' similar entries for this job
-	thread		Use pthreads instead of forked jobs
-	zonesize=x
-	zoneskip=y	Zone options must be paired. If given, the job
-			will skip y bytes for every x read/written. This
-			can be used to gauge hard drive speed over the entire
-			platter, without reading everything. Both x/y can
-			include k/m/g suffix.
-	read_iolog=x	Open and read io pattern from file 'x'. The file format
-                        is described in the HOWTO.
-	write_iolog=x	Write an iolog to file 'x' in the same format as iolog.
-			The iolog options are exclusive, if both given the
-			read iolog will be performed.  Specify a separate file
-			for each job, otherwise the iologs will be interspersed
-			and the file may be corrupt.
-	write_bw_log	Write a bandwidth log.
-	write_lat_log	Write a latency log.
-	lockmem=x	Lock down x amount of memory on the machine, to
-			simulate a machine with less memory available. x can
-			include k/m/g suffix.
-	nice=x		Run job at given nice value.
-	exec_prerun=x	Run 'x' before job io is begun.
-	exec_postrun=x	Run 'x' after job io has finished.
-	ioscheduler=x	Use ioscheduler 'x' for this job.
-	cpuload=x	For a CPU io thread, percentage of CPU time to attempt
-			to burn.
-	cpuchunks=x	Split burn cycles into pieces of x usecs.
-
+The HOWTO or man page has a full list of all options, along with
+descriptions, etc. The --cmdhelp option also lists all options. If
+used with an option argument, it will detail that particular option.
 
 
 Client/server
diff --git a/backend.c b/backend.c
index fcb74dc..c397eb1 100644
--- a/backend.c
+++ b/backend.c
@@ -1256,6 +1256,8 @@ static void *thread_main(void *data)
 			goto err;
 	}
 
+	fio_verify_init(td);
+
 	fio_gettime(&td->epoch, NULL);
 	fio_getrusage(&td->ru_start);
 	clear_state = 0;
diff --git a/engines/e4defrag.c b/engines/e4defrag.c
index 6063e6c..d9ddf75 100644
--- a/engines/e4defrag.c
+++ b/engines/e4defrag.c
@@ -47,6 +47,8 @@ static struct fio_option options[] = {
 		.type	= FIO_OPT_STR_STORE,
 		.off1	= offsetof(struct e4defrag_options, donor_name),
 		.help	= "File used as a block donor",
+		.category = FIO_OPT_C_ENGINE,
+		.group	= FIO_OPT_G_E4DEFRAG,
 	},
 	{
 		.name	= "inplace",
@@ -55,6 +57,8 @@ static struct fio_option options[] = {
 		.minval	= 0,
 		.maxval	= 1,
 		.help	= "Alloc and free space inside defrag event",
+		.category = FIO_OPT_C_ENGINE,
+		.group	= FIO_OPT_G_E4DEFRAG,
 	},
 	{
 		.name	= NULL,
diff --git a/engines/libaio.c b/engines/libaio.c
index 4d1f3a3..0dca583 100644
--- a/engines/libaio.c
+++ b/engines/libaio.c
@@ -32,6 +32,8 @@ static struct fio_option options[] = {
 		.type	= FIO_OPT_STR_SET,
 		.off1	= offsetof(struct libaio_options, userspace_reap),
 		.help	= "Use alternative user-space reap implementation",
+		.category = FIO_OPT_C_ENGINE,
+		.category = FIO_OPT_G_LIBAIO,
 	},
 	{
 		.name	= NULL,
diff --git a/engines/net.c b/engines/net.c
index 31f7151..3a352de 100644
--- a/engines/net.c
+++ b/engines/net.c
@@ -61,6 +61,8 @@ static struct fio_option options[] = {
 		.type	= FIO_OPT_STR_STORE,
 		.cb	= str_hostname_cb,
 		.help	= "Hostname for net IO engine",
+		.category = FIO_OPT_C_ENGINE,
+		.group	= FIO_OPT_G_NETIO,
 	},
 	{
 		.name	= "port",
@@ -69,6 +71,8 @@ static struct fio_option options[] = {
 		.minval	= 1,
 		.maxval	= 65535,
 		.help	= "Port to use for TCP or UDP net connections",
+		.category = FIO_OPT_C_ENGINE,
+		.group	= FIO_OPT_G_NETIO,
 	},
 	{
 		.name	= "protocol",
@@ -91,6 +95,8 @@ static struct fio_option options[] = {
 			    .help = "UNIX domain socket",
 			  },
 		},
+		.category = FIO_OPT_C_ENGINE,
+		.group	= FIO_OPT_G_NETIO,
 	},
 #ifdef CONFIG_TCP_NODELAY
 	{
@@ -98,6 +104,8 @@ static struct fio_option options[] = {
 		.type	= FIO_OPT_BOOL,
 		.off1	= offsetof(struct netio_options, nodelay),
 		.help	= "Use TCP_NODELAY on TCP connections",
+		.category = FIO_OPT_C_ENGINE,
+		.group	= FIO_OPT_G_NETIO,
 	},
 #endif
 	{
@@ -105,12 +113,16 @@ static struct fio_option options[] = {
 		.type	= FIO_OPT_STR_SET,
 		.off1	= offsetof(struct netio_options, listen),
 		.help	= "Listen for incoming TCP connections",
+		.category = FIO_OPT_C_ENGINE,
+		.group	= FIO_OPT_G_NETIO,
 	},
 	{
 		.name	= "pingpong",
 		.type	= FIO_OPT_STR_SET,
 		.off1	= offsetof(struct netio_options, pingpong),
 		.help	= "Ping-pong IO requests",
+		.category = FIO_OPT_C_ENGINE,
+		.group	= FIO_OPT_G_NETIO,
 	},
 	{
 		.name	= NULL,
diff --git a/filesetup.c b/filesetup.c
index 88d6565..9edcac1 100644
--- a/filesetup.c
+++ b/filesetup.c
@@ -97,9 +97,9 @@ static int extend_file(struct thread_data *td, struct fio_file *f)
 
 			r = fallocate(f->fd, FALLOC_FL_KEEP_SIZE, 0,
 					f->real_file_size);
-			if (r != 0) {
+			if (r != 0)
 				td_verror(td, errno, "fallocate");
-			}
+
 			break;
 #endif /* CONFIG_LINUX_FALLOCATE */
 		default:
@@ -918,9 +918,9 @@ static int __init_rand_distribution(struct thread_data *td, struct fio_file *f)
 		seed = td->rand_seeds[4];
 
 	if (td->o.random_distribution == FIO_RAND_DIST_ZIPF)
-		zipf_init(&f->zipf, nranges, td->o.zipf_theta, seed);
+		zipf_init(&f->zipf, nranges, td->o.zipf_theta.u.f, seed);
 	else
-		pareto_init(&f->zipf, nranges, td->o.pareto_h, seed);
+		pareto_init(&f->zipf, nranges, td->o.pareto_h.u.f, seed);
 
 	return 1;
 }
diff --git a/fio.1 b/fio.1
index be0cf91..eddc265 100644
--- a/fio.1
+++ b/fio.1
@@ -1034,7 +1034,7 @@ Disable measurements of throughput/bandwidth numbers. See \fBdisable_lat\fR.
 .TP
 .BI lockmem \fR=\fPint
 Pin the specified amount of memory with \fBmlock\fR\|(2).  Can be used to
-simulate a smaller amount of memory.
+simulate a smaller amount of memory. The amount specified is per worker.
 .TP
 .BI exec_prerun \fR=\fPstr
 Before running the job, execute the specified command with \fBsystem\fR\|(3).
@@ -1170,6 +1170,12 @@ Some parameters are only valid when a specific ioengine is in use. These are
 used identically to normal parameters, with the caveat that when used on the
 command line, the must come after the ioengine that defines them is selected.
 .TP
+.BI (cpu)cpuload \fR=\fPint
+Attempt to use the specified percentage of CPU cycles.
+.TP
+.BI (cpu)cpuchunks \fR=\fPint
+Split the load into cycles of the given time. In microseconds.
+.TP
 .BI (libaio)userspace_reap
 Normally, with the libaio engine in use, fio will use
 the io_getevents system call to reap newly returned events.
diff --git a/fio.h b/fio.h
index e74bdb3..e55413a 100644
--- a/fio.h
+++ b/fio.h
@@ -17,9 +17,9 @@
 struct thread_data;
 
 #include "compiler/compiler.h"
+#include "thread_options.h"
 #include "flist.h"
 #include "fifo.h"
-#include "rbtree.h"
 #include "arch/arch.h"
 #include "os/os.h"
 #include "mutex.h"
@@ -36,6 +36,7 @@ struct thread_data;
 #include "gettime.h"
 #include "lib/getopt.h"
 #include "lib/rand.h"
+#include "lib/rbtree.h"
 #include "server.h"
 #include "stat.h"
 #include "flow.h"
@@ -55,17 +56,6 @@ struct thread_data;
 #endif
 
 /*
- * What type of allocation to use for io buffers
- */
-enum fio_memtype {
-	MEM_MALLOC = 0,	/* ordinary malloc */
-	MEM_SHM,	/* use shared memory segments */
-	MEM_SHMHUGE,	/* use shared memory segments with huge pages */
-	MEM_MMAP,	/* use anonynomous mmap */
-	MEM_MMAPHUGE,	/* memory mapped huge file */
-};
-
-/*
  * offset generator types
  */
 enum {
@@ -73,242 +63,6 @@ enum {
 	RW_SEQ_IDENT,
 };
 
-/*
- * What type of errors to continue on when continue_on_error is used
- */
-enum error_type_bit {
-	ERROR_TYPE_READ_BIT = 0,
-	ERROR_TYPE_WRITE_BIT = 1,
-	ERROR_TYPE_VERIFY_BIT = 2,
-	ERROR_TYPE_CNT = 3,
-};
-
-enum error_type {
-        ERROR_TYPE_NONE = 0,
-        ERROR_TYPE_READ = 1 << ERROR_TYPE_READ_BIT,
-        ERROR_TYPE_WRITE = 1 << ERROR_TYPE_WRITE_BIT,
-        ERROR_TYPE_VERIFY = 1 << ERROR_TYPE_VERIFY_BIT,
-        ERROR_TYPE_ANY = 0xffff,
-};
-
-struct bssplit {
-	unsigned int bs;
-	unsigned char perc;
-};
-
-struct thread_options {
-	int pad;
-	char *description;
-	char *name;
-	char *directory;
-	char *filename;
-	char *filename_format;
-	char *opendir;
-	char *ioengine;
-	enum td_ddir td_ddir;
-	unsigned int rw_seq;
-	unsigned int kb_base;
-	unsigned int unit_base;
-	unsigned int ddir_seq_nr;
-	long ddir_seq_add;
-	unsigned int iodepth;
-	unsigned int iodepth_low;
-	unsigned int iodepth_batch;
-	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;
-	unsigned long long start_offset;
-
-	unsigned int bs[DDIR_RWDIR_CNT];
-	unsigned int ba[DDIR_RWDIR_CNT];
-	unsigned int min_bs[DDIR_RWDIR_CNT];
-	unsigned int max_bs[DDIR_RWDIR_CNT];
-	struct bssplit *bssplit[DDIR_RWDIR_CNT];
-	unsigned int bssplit_nr[DDIR_RWDIR_CNT];
-
-	int *ignore_error[ERROR_TYPE_CNT];
-	unsigned int ignore_error_nr[ERROR_TYPE_CNT];
-	unsigned int error_dump;
-
-	unsigned int nr_files;
-	unsigned int open_files;
-	enum file_lock_mode file_lock_mode;
-
-	unsigned int odirect;
-	unsigned int invalidate_cache;
-	unsigned int create_serialize;
-	unsigned int create_fsync;
-	unsigned int create_on_open;
-	unsigned int create_only;
-	unsigned int end_fsync;
-	unsigned int pre_read;
-	unsigned int sync_io;
-	unsigned int verify;
-	unsigned int do_verify;
-	unsigned int verifysort;
-	unsigned int verifysort_nr;
-	unsigned int verify_interval;
-	unsigned int verify_offset;
-	char verify_pattern[MAX_PATTERN_SIZE];
-	unsigned int verify_pattern_bytes;
-	unsigned int verify_fatal;
-	unsigned int verify_dump;
-	unsigned int verify_async;
-	unsigned long long verify_backlog;
-	unsigned int verify_batch;
-	unsigned int experimental_verify;
-	unsigned int use_thread;
-	unsigned int unlink;
-	unsigned int do_disk_util;
-	unsigned int override_sync;
-	unsigned int rand_repeatable;
-	unsigned int use_os_rand;
-	unsigned int write_lat_log;
-	unsigned int write_bw_log;
-	unsigned int write_iops_log;
-	unsigned int log_avg_msec;
-	unsigned int norandommap;
-	unsigned int softrandommap;
-	unsigned int bs_unaligned;
-	unsigned int fsync_on_close;
-
-	unsigned int random_distribution;
-	double zipf_theta;
-	double pareto_h;
-
-	unsigned int random_generator;
-
-	unsigned int hugepage_size;
-	unsigned int rw_min_bs;
-	unsigned int thinktime;
-	unsigned int thinktime_spin;
-	unsigned int thinktime_blocks;
-	unsigned int fsync_blocks;
-	unsigned int fdatasync_blocks;
-	unsigned int barrier_blocks;
-	unsigned long long start_delay;
-	unsigned long long timeout;
-	unsigned long long ramp_time;
-	unsigned int overwrite;
-	unsigned int bw_avg_time;
-	unsigned int iops_avg_time;
-	unsigned int loops;
-	unsigned long long zone_range;
-	unsigned long long zone_size;
-	unsigned long long zone_skip;
-	enum fio_memtype mem_type;
-	unsigned int mem_align;
-
-	unsigned int max_latency;
-
-	unsigned int stonewall;
-	unsigned int new_group;
-	unsigned int numjobs;
-	os_cpu_mask_t cpumask;
-	unsigned int cpumask_set;
-	os_cpu_mask_t verify_cpumask;
-	unsigned int verify_cpumask_set;
-#ifdef CONFIG_LIBNUMA
-	struct bitmask *numa_cpunodesmask;
-	unsigned int numa_cpumask_set;
-	unsigned short numa_mem_mode;
-	unsigned int numa_mem_prefer_node;
-	struct bitmask *numa_memnodesmask;
-	unsigned int numa_memmask_set;
-#endif
-	unsigned int iolog;
-	unsigned int rwmixcycle;
-	unsigned int rwmix[2];
-	unsigned int nice;
-	unsigned int file_service_type;
-	unsigned int group_reporting;
-	unsigned int fadvise_hint;
-	enum fio_fallocate_mode fallocate_mode;
-	unsigned int zero_buffers;
-	unsigned int refill_buffers;
-	unsigned int scramble_buffers;
-	unsigned int compress_percentage;
-	unsigned int compress_chunk;
-	unsigned int time_based;
-	unsigned int disable_lat;
-	unsigned int disable_clat;
-	unsigned int disable_slat;
-	unsigned int disable_bw;
-	unsigned int unified_rw_rep;
-	unsigned int gtod_reduce;
-	unsigned int gtod_cpu;
-	unsigned int gtod_offload;
-	enum fio_cs clocksource;
-	unsigned int no_stall;
-	unsigned int trim_percentage;
-	unsigned int trim_batch;
-	unsigned int trim_zero;
-	unsigned long long trim_backlog;
-	unsigned int clat_percentiles;
-	unsigned int percentile_precision;	/* digits after decimal for percentiles */
-	fio_fp64_t percentile_list[FIO_IO_U_LIST_MAX_LEN];
-
-	char *read_iolog_file;
-	char *write_iolog_file;
-	char *bw_log_file;
-	char *lat_log_file;
-	char *iops_log_file;
-	char *replay_redirect;
-
-	/*
-	 * Pre-run and post-run shell
-	 */
-	char *exec_prerun;
-	char *exec_postrun;
-
-	unsigned int rate[DDIR_RWDIR_CNT];
-	unsigned int ratemin[DDIR_RWDIR_CNT];
-	unsigned int ratecycle;
-	unsigned int rate_iops[DDIR_RWDIR_CNT];
-	unsigned int rate_iops_min[DDIR_RWDIR_CNT];
-
-	char *ioscheduler;
-
-	/*
-	 * CPU "io" cycle burner
-	 */
-	unsigned int cpuload;
-	unsigned int cpucycle;
-
-	/*
-	 * I/O Error handling
-	 */
-	enum error_type continue_on_error;
-
-	/*
-	 * Benchmark profile type
-	 */
-	char *profile;
-
-	/*
-	 * blkio cgroup support
-	 */
-	char *cgroup;
-	unsigned int cgroup_weight;
-	unsigned int cgroup_nodelete;
-
-	unsigned int uid;
-	unsigned int gid;
-
-	int flow_id;
-	int flow;
-	int flow_watermark;
-	unsigned int flow_sleep;
-
-	unsigned long long offset_increment;
-
-	unsigned int sync_file_range;
-};
-
 enum {
 	TD_F_VER_BACKLOG	= 1,
 	TD_F_TRIM_BACKLOG	= 2,
diff --git a/iolog.h b/iolog.h
index bb5c9b7..3d140a2 100644
--- a/iolog.h
+++ b/iolog.h
@@ -1,6 +1,7 @@
 #ifndef FIO_IOLOG_H
 #define FIO_IOLOG_H
 
+#include "lib/rbtree.h"
 #include "lib/ieee754.h"
 
 /*
@@ -84,6 +85,7 @@ enum file_log_act {
 	FIO_LOG_UNLINK_FILE,
 };
 
+struct io_u;
 extern int __must_check read_iolog_get(struct thread_data *, struct io_u *);
 extern void log_io_u(struct thread_data *, struct io_u *);
 extern void log_file(struct thread_data *, struct fio_file *, enum file_log_act);
diff --git a/lib/rbtree.c b/lib/rbtree.c
new file mode 100644
index 0000000..883bc72
--- /dev/null
+++ b/lib/rbtree.c
@@ -0,0 +1,333 @@
+/*
+  Red Black Trees
+  (C) 1999  Andrea Arcangeli <andrea@xxxxxxx>
+  (C) 2002  David Woodhouse <dwmw2@xxxxxxxxxxxxx>
+  
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+  linux/lib/rbtree.c
+*/
+
+#include "rbtree.h"
+
+static void __rb_rotate_left(struct rb_node *node, struct rb_root *root)
+{
+	struct rb_node *right = node->rb_right;
+	struct rb_node *parent = rb_parent(node);
+
+	if ((node->rb_right = right->rb_left))
+		rb_set_parent(right->rb_left, node);
+	right->rb_left = node;
+
+	rb_set_parent(right, parent);
+
+	if (parent)
+	{
+		if (node == parent->rb_left)
+			parent->rb_left = right;
+		else
+			parent->rb_right = right;
+	}
+	else
+		root->rb_node = right;
+	rb_set_parent(node, right);
+}
+
+static void __rb_rotate_right(struct rb_node *node, struct rb_root *root)
+{
+	struct rb_node *left = node->rb_left;
+	struct rb_node *parent = rb_parent(node);
+
+	if ((node->rb_left = left->rb_right))
+		rb_set_parent(left->rb_right, node);
+	left->rb_right = node;
+
+	rb_set_parent(left, parent);
+
+	if (parent)
+	{
+		if (node == parent->rb_right)
+			parent->rb_right = left;
+		else
+			parent->rb_left = left;
+	}
+	else
+		root->rb_node = left;
+	rb_set_parent(node, left);
+}
+
+void rb_insert_color(struct rb_node *node, struct rb_root *root)
+{
+	struct rb_node *parent, *gparent;
+
+	while ((parent = rb_parent(node)) && rb_is_red(parent))
+	{
+		gparent = rb_parent(parent);
+
+		if (parent == gparent->rb_left)
+		{
+			{
+				register struct rb_node *uncle = gparent->rb_right;
+				if (uncle && rb_is_red(uncle))
+				{
+					rb_set_black(uncle);
+					rb_set_black(parent);
+					rb_set_red(gparent);
+					node = gparent;
+					continue;
+				}
+			}
+
+			if (parent->rb_right == node)
+			{
+				register struct rb_node *tmp;
+				__rb_rotate_left(parent, root);
+				tmp = parent;
+				parent = node;
+				node = tmp;
+			}
+
+			rb_set_black(parent);
+			rb_set_red(gparent);
+			__rb_rotate_right(gparent, root);
+		} else {
+			{
+				register struct rb_node *uncle = gparent->rb_left;
+				if (uncle && rb_is_red(uncle))
+				{
+					rb_set_black(uncle);
+					rb_set_black(parent);
+					rb_set_red(gparent);
+					node = gparent;
+					continue;
+				}
+			}
+
+			if (parent->rb_left == node)
+			{
+				register struct rb_node *tmp;
+				__rb_rotate_right(parent, root);
+				tmp = parent;
+				parent = node;
+				node = tmp;
+			}
+
+			rb_set_black(parent);
+			rb_set_red(gparent);
+			__rb_rotate_left(gparent, root);
+		}
+	}
+
+	rb_set_black(root->rb_node);
+}
+
+static void __rb_erase_color(struct rb_node *node, struct rb_node *parent,
+			     struct rb_root *root)
+{
+	struct rb_node *other;
+
+	while ((!node || rb_is_black(node)) && node != root->rb_node)
+	{
+		if (parent->rb_left == node)
+		{
+			other = parent->rb_right;
+			if (rb_is_red(other))
+			{
+				rb_set_black(other);
+				rb_set_red(parent);
+				__rb_rotate_left(parent, root);
+				other = parent->rb_right;
+			}
+			if ((!other->rb_left || rb_is_black(other->rb_left)) &&
+			    (!other->rb_right || rb_is_black(other->rb_right)))
+			{
+				rb_set_red(other);
+				node = parent;
+				parent = rb_parent(node);
+			}
+			else
+			{
+				if (!other->rb_right || rb_is_black(other->rb_right))
+				{
+					struct rb_node *o_left;
+					if ((o_left = other->rb_left))
+						rb_set_black(o_left);
+					rb_set_red(other);
+					__rb_rotate_right(other, root);
+					other = parent->rb_right;
+				}
+				rb_set_color(other, rb_color(parent));
+				rb_set_black(parent);
+				if (other->rb_right)
+					rb_set_black(other->rb_right);
+				__rb_rotate_left(parent, root);
+				node = root->rb_node;
+				break;
+			}
+		}
+		else
+		{
+			other = parent->rb_left;
+			if (rb_is_red(other))
+			{
+				rb_set_black(other);
+				rb_set_red(parent);
+				__rb_rotate_right(parent, root);
+				other = parent->rb_left;
+			}
+			if ((!other->rb_left || rb_is_black(other->rb_left)) &&
+			    (!other->rb_right || rb_is_black(other->rb_right)))
+			{
+				rb_set_red(other);
+				node = parent;
+				parent = rb_parent(node);
+			}
+			else
+			{
+				if (!other->rb_left || rb_is_black(other->rb_left))
+				{
+					register struct rb_node *o_right;
+					if ((o_right = other->rb_right))
+						rb_set_black(o_right);
+					rb_set_red(other);
+					__rb_rotate_left(other, root);
+					other = parent->rb_left;
+				}
+				rb_set_color(other, rb_color(parent));
+				rb_set_black(parent);
+				if (other->rb_left)
+					rb_set_black(other->rb_left);
+				__rb_rotate_right(parent, root);
+				node = root->rb_node;
+				break;
+			}
+		}
+	}
+	if (node)
+		rb_set_black(node);
+}
+
+void rb_erase(struct rb_node *node, struct rb_root *root)
+{
+	struct rb_node *child, *parent;
+	int color;
+
+	if (!node->rb_left)
+		child = node->rb_right;
+	else if (!node->rb_right)
+		child = node->rb_left;
+	else
+	{
+		struct rb_node *old = node, *left;
+
+		node = node->rb_right;
+		while ((left = node->rb_left) != NULL)
+			node = left;
+		child = node->rb_right;
+		parent = rb_parent(node);
+		color = rb_color(node);
+
+		if (child)
+			rb_set_parent(child, parent);
+		if (parent == old) {
+			parent->rb_right = child;
+			parent = node;
+		} else
+			parent->rb_left = child;
+
+		node->rb_parent_color = old->rb_parent_color;
+		node->rb_right = old->rb_right;
+		node->rb_left = old->rb_left;
+
+		if (rb_parent(old))
+		{
+			if (rb_parent(old)->rb_left == old)
+				rb_parent(old)->rb_left = node;
+			else
+				rb_parent(old)->rb_right = node;
+		} else
+			root->rb_node = node;
+
+		rb_set_parent(old->rb_left, node);
+		if (old->rb_right)
+			rb_set_parent(old->rb_right, node);
+		goto color;
+	}
+
+	parent = rb_parent(node);
+	color = rb_color(node);
+
+	if (child)
+		rb_set_parent(child, parent);
+	if (parent)
+	{
+		if (parent->rb_left == node)
+			parent->rb_left = child;
+		else
+			parent->rb_right = child;
+	}
+	else
+		root->rb_node = child;
+
+ color:
+	if (color == RB_BLACK)
+		__rb_erase_color(child, parent, root);
+}
+
+/*
+ * This function returns the first node (in sort order) of the tree.
+ */
+struct rb_node *rb_first(struct rb_root *root)
+{
+	struct rb_node	*n;
+
+	n = root->rb_node;
+	if (!n)
+		return NULL;
+	while (n->rb_left)
+		n = n->rb_left;
+	return n;
+}
+
+struct rb_node *rb_next(const struct rb_node *node)
+{
+	struct rb_node *parent;
+
+	if (RB_EMPTY_NODE(node))
+		return NULL;
+
+	/*
+	 * If we have a right-hand child, go down and then left as far
+	 * as we can.
+	 */
+	if (node->rb_right) {
+		node = node->rb_right; 
+		while (node->rb_left)
+			node=node->rb_left;
+		return (struct rb_node *)node;
+	}
+
+	/*
+	 * No right-hand children. Everything down and left is smaller than us,
+	 * so any 'next' node must be in the general direction of our parent.
+	 * Go up the tree; any time the ancestor is a right-hand child of its
+	 * parent, keep going up. First time it's a left-hand child of its
+	 * parent, said parent is our 'next' node.
+	 */
+	while ((parent = rb_parent(node)) && node == parent->rb_right)
+		node = parent;
+
+	return parent;
+}
diff --git a/lib/rbtree.h b/lib/rbtree.h
new file mode 100644
index 0000000..c6cfe4a
--- /dev/null
+++ b/lib/rbtree.h
@@ -0,0 +1,155 @@
+/*
+  Red Black Trees
+  (C) 1999  Andrea Arcangeli <andrea@xxxxxxx>
+  
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+  linux/include/linux/rbtree.h
+
+  To use rbtrees you'll have to implement your own insert and search cores.
+  This will avoid us to use callbacks and to drop drammatically performances.
+  I know it's not the cleaner way,  but in C (not in C++) to get
+  performances and genericity...
+
+  Some example of insert and search follows here. The search is a plain
+  normal search over an ordered tree. The insert instead must be implemented
+  int two steps: as first thing the code must insert the element in
+  order as a red leaf in the tree, then the support library function
+  rb_insert_color() must be called. Such function will do the
+  not trivial work to rebalance the rbtree if necessary.
+
+-----------------------------------------------------------------------
+static inline struct page * rb_search_page_cache(struct inode * inode,
+						 unsigned long offset)
+{
+	struct rb_node * n = inode->i_rb_page_cache.rb_node;
+	struct page * page;
+
+	while (n)
+	{
+		page = rb_entry(n, struct page, rb_page_cache);
+
+		if (offset < page->offset)
+			n = n->rb_left;
+		else if (offset > page->offset)
+			n = n->rb_right;
+		else
+			return page;
+	}
+	return NULL;
+}
+
+static inline struct page * __rb_insert_page_cache(struct inode * inode,
+						   unsigned long offset,
+						   struct rb_node * node)
+{
+	struct rb_node ** p = &inode->i_rb_page_cache.rb_node;
+	struct rb_node * parent = NULL;
+	struct page * page;
+
+	while (*p)
+	{
+		parent = *p;
+		page = rb_entry(parent, struct page, rb_page_cache);
+
+		if (offset < page->offset)
+			p = &(*p)->rb_left;
+		else if (offset > page->offset)
+			p = &(*p)->rb_right;
+		else
+			return page;
+	}
+
+	rb_link_node(node, parent, p);
+
+	return NULL;
+}
+
+static inline struct page * rb_insert_page_cache(struct inode * inode,
+						 unsigned long offset,
+						 struct rb_node * node)
+{
+	struct page * ret;
+	if ((ret = __rb_insert_page_cache(inode, offset, node)))
+		goto out;
+	rb_insert_color(node, &inode->i_rb_page_cache);
+ out:
+	return ret;
+}
+-----------------------------------------------------------------------
+*/
+
+#ifndef	_LINUX_RBTREE_H
+#define	_LINUX_RBTREE_H
+
+#include <stdlib.h>
+#include <inttypes.h>
+
+struct rb_node
+{
+	intptr_t rb_parent_color;
+#define	RB_RED		0
+#define	RB_BLACK	1
+	struct rb_node *rb_right;
+	struct rb_node *rb_left;
+} __attribute__((aligned(sizeof(long))));
+    /* The alignment might seem pointless, but allegedly CRIS needs it */
+
+struct rb_root
+{
+	struct rb_node *rb_node;
+};
+
+
+#define rb_parent(r)   ((struct rb_node *)((r)->rb_parent_color & ~3))
+#define rb_color(r)   ((r)->rb_parent_color & 1)
+#define rb_is_red(r)   (!rb_color(r))
+#define rb_is_black(r) rb_color(r)
+#define rb_set_red(r)  do { (r)->rb_parent_color &= ~1; } while (0)
+#define rb_set_black(r)  do { (r)->rb_parent_color |= 1; } while (0)
+
+static inline void rb_set_parent(struct rb_node *rb, struct rb_node *p)
+{
+	rb->rb_parent_color = (rb->rb_parent_color & 3) | (uintptr_t)p;
+}
+static inline void rb_set_color(struct rb_node *rb, int color)
+{
+	rb->rb_parent_color = (rb->rb_parent_color & ~1) | color;
+}
+
+#define RB_ROOT	(struct rb_root) { NULL, }
+#define	rb_entry(ptr, type, member) container_of(ptr, type, member)
+
+#define RB_EMPTY_ROOT(root)	((root)->rb_node == NULL)
+#define RB_EMPTY_NODE(node)	(rb_parent(node) == node)
+#define RB_CLEAR_NODE(node)	(rb_set_parent(node, node))
+
+extern void rb_insert_color(struct rb_node *, struct rb_root *);
+extern void rb_erase(struct rb_node *, struct rb_root *);
+
+/* Find logical next and previous nodes in a tree */
+extern struct rb_node *rb_first(struct rb_root *);
+extern struct rb_node *rb_next(const struct rb_node *);
+
+static inline void rb_link_node(struct rb_node * node, struct rb_node * parent,
+				struct rb_node ** rb_link)
+{
+	node->rb_parent_color = (uintptr_t)parent;
+	node->rb_left = node->rb_right = NULL;
+
+	*rb_link = node;
+}
+
+#endif	/* _LINUX_RBTREE_H */
diff --git a/options.c b/options.c
index 6cd1e92..16b9fbc 100644
--- a/options.c
+++ b/options.c
@@ -348,18 +348,6 @@ static int str_mem_cb(void *data, const char *mem)
 	return 0;
 }
 
-static int str_verify_cb(void *data, const char *mem)
-{
-	struct thread_data *td = data;
-
-	if (td->o.verify == VERIFY_CRC32C_INTEL ||
-	    td->o.verify == VERIFY_CRC32C) {
-		crc32c_intel_probe();
-	}
-
-	return 0;
-}
-
 static int fio_clock_source_cb(void *data, const char *str)
 {
 	struct thread_data *td = data;
@@ -370,12 +358,6 @@ static int fio_clock_source_cb(void *data, const char *str)
 	return 0;
 }
 
-static int str_lockmem_cb(void fio_unused *data, unsigned long long *val)
-{
-	mlock_size = *val;
-	return 0;
-}
-
 static int str_rwmix_read_cb(void *data, unsigned long long *val)
 {
 	struct thread_data *td = data;
@@ -684,16 +666,6 @@ out:
 }
 #endif
 
-#ifdef FIO_HAVE_TRIM
-static int str_verify_trim_cb(void *data, unsigned long long *val)
-{
-	struct thread_data *td = data;
-
-	td->o.trim_percentage = *val;
-	return 0;
-}
-#endif
-
 static int str_fst_cb(void *data, const char *str)
 {
 	struct thread_data *td = data;
@@ -751,13 +723,13 @@ static int str_random_distribution_cb(void *data, const char *str)
 			log_err("fio: zipf theta must different than 1.0\n");
 			return 1;
 		}
-		td->o.zipf_theta = val;
+		td->o.zipf_theta.u.f = val;
 	} else {
 		if (val <= 0.00 || val >= 1.00) {
 			log_err("fio: pareto input out of range (0 < input < 1.0)\n");
 			return 1;
 		}
-		td->o.pareto_h = val;
+		td->o.pareto_h.u.f = val;
 	}
 
 	return 0;
@@ -906,19 +878,6 @@ static int str_opendir_cb(void *data, const char fio_unused *str)
 	return add_dir_files(td, td->o.opendir);
 }
 
-static int str_verify_offset_cb(void *data, unsigned long long *off)
-{
-	struct thread_data *td = data;
-
-	if (*off && *off < sizeof(struct verify_header)) {
-		log_err("fio: verify_offset too small\n");
-		return 1;
-	}
-
-	td->o.verify_offset = *off;
-	return 0;
-}
-
 static int str_verify_pattern_cb(void *data, const char *input)
 {
 	struct thread_data *td = data;
@@ -1087,49 +1046,203 @@ static int gtod_cpu_verify(struct fio_option *o, void *data)
 	return 0;
 }
 
-static int kb_base_verify(struct fio_option *o, void *data)
+/*
+ * Option grouping
+ */
+static struct opt_group fio_opt_groups[] = {
+	{
+		.name	= "General",
+		.mask	= FIO_OPT_C_GENERAL,
+	},
+	{
+		.name	= "I/O",
+		.mask	= FIO_OPT_C_IO,
+	},
+	{
+		.name	= "File",
+		.mask	= FIO_OPT_C_FILE,
+	},
+	{
+		.name	= "Statistics",
+		.mask	= FIO_OPT_C_STAT,
+	},
+	{
+		.name	= "Logging",
+		.mask	= FIO_OPT_C_LOG,
+	},
+	{
+		.name	= "Profiles",
+		.mask	= FIO_OPT_C_PROFILE,
+	},
+	{
+		.name	= NULL,
+	},
+};
+
+static struct opt_group *__opt_group_from_mask(struct opt_group *ogs, unsigned int *mask,
+					       unsigned int inv_mask)
 {
-	struct thread_data *td = data;
+	struct opt_group *og;
+	int i;
 
-	if (td->o.kb_base != 1024 && td->o.kb_base != 1000) {
-		log_err("fio: kb_base set to nonsensical value: %u\n",
-				td->o.kb_base);
-		return 1;
+	if (*mask == inv_mask || !*mask)
+		return NULL;
+
+	for (i = 0; ogs[i].name; i++) {
+		og = &ogs[i];
+
+		if (*mask & og->mask) {
+			*mask &= ~(og->mask);
+			return og;
+		}
 	}
 
-	return 0;
+	return NULL;
+}
+
+struct opt_group *opt_group_from_mask(unsigned int *mask)
+{
+	return __opt_group_from_mask(fio_opt_groups, mask, FIO_OPT_C_INVALID);
+}
+
+static struct opt_group fio_opt_cat_groups[] = {
+	{
+		.name	= "Rate",
+		.mask	= FIO_OPT_G_RATE,
+	},
+	{
+		.name	= "Zone",
+		.mask	= FIO_OPT_G_ZONE,
+	},
+	{
+		.name	= "Read/write mix",
+		.mask	= FIO_OPT_G_RWMIX,
+	},
+	{
+		.name	= "Verify",
+		.mask	= FIO_OPT_G_VERIFY,
+	},
+	{
+		.name	= "Trim",
+		.mask	= FIO_OPT_G_TRIM,
+	},
+	{
+		.name	= "I/O Logging",
+		.mask	= FIO_OPT_G_IOLOG,
+	},
+	{
+		.name	= "I/O Depth",
+		.mask	= FIO_OPT_G_IO_DEPTH,
+	},
+	{
+		.name	= "I/O Flow",
+		.mask	= FIO_OPT_G_IO_FLOW,
+	},
+	{
+		.name	= "Description",
+		.mask	= FIO_OPT_G_DESC,
+	},
+	{
+		.name	= "Filename",
+		.mask	= FIO_OPT_G_FILENAME,
+	},
+	{
+		.name	= "General I/O",
+		.mask	= FIO_OPT_G_IO_BASIC,
+	},
+	{
+		.name	= "Cgroups",
+		.mask	= FIO_OPT_G_CGROUP,
+	},
+	{
+		.name	= "Runtime",
+		.mask	= FIO_OPT_G_RUNTIME,
+	},
+	{
+		.name	= "Process",
+		.mask	= FIO_OPT_G_PROCESS,
+	},
+	{
+		.name	= "Job credentials / priority",
+		.mask	= FIO_OPT_G_CRED,
+	},
+	{
+		.name	= "Clock settings",
+		.mask	= FIO_OPT_G_CLOCK,
+	},
+	{
+		.name	= "I/O Type",
+		.mask	= FIO_OPT_G_IO_TYPE,
+	},
+	{
+		.name	= "I/O Thinktime",
+		.mask	= FIO_OPT_G_THINKTIME,
+	},
+	{
+		.name	= "Randomizations",
+		.mask	= FIO_OPT_G_RANDOM,
+	},
+	{
+		.name	= "I/O buffers",
+		.mask	= FIO_OPT_G_IO_BUF,
+	},
+	{
+		.name	= "Tiobench profile",
+		.mask	= FIO_OPT_G_TIOBENCH,
+	},
+
+	{
+		.name	= NULL,
+	}
+};
+
+struct opt_group *opt_group_cat_from_mask(unsigned int *mask)
+{
+	return __opt_group_from_mask(fio_opt_cat_groups, mask, FIO_OPT_G_INVALID);
 }
 
 /*
  * Map of job/command line options
  */
-static struct fio_option options[FIO_MAX_OPTS] = {
+static struct fio_option fio_options[FIO_MAX_OPTS] = {
 	{
 		.name	= "description",
+		.lname	= "Description of job",
 		.type	= FIO_OPT_STR_STORE,
 		.off1	= td_var_offset(description),
 		.help	= "Text job description",
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_DESC,
 	},
 	{
 		.name	= "name",
+		.lname	= "Job name",
 		.type	= FIO_OPT_STR_STORE,
 		.off1	= td_var_offset(name),
 		.help	= "Name of this job",
-	},
-	{
-		.name	= "directory",
-		.type	= FIO_OPT_STR_STORE,
-		.off1	= td_var_offset(directory),
-		.cb	= str_directory_cb,
-		.help	= "Directory to store files in",
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_DESC,
 	},
 	{
 		.name	= "filename",
+		.lname	= "Filename(s)",
 		.type	= FIO_OPT_STR_STORE,
 		.off1	= td_var_offset(filename),
 		.cb	= str_filename_cb,
 		.prio	= -1, /* must come after "directory" */
 		.help	= "File(s) to use for the workload",
+		.category = FIO_OPT_C_FILE,
+		.group	= FIO_OPT_G_FILENAME,
+	},
+	{
+		.name	= "directory",
+		.lname	= "Directory",
+		.type	= FIO_OPT_STR_STORE,
+		.off1	= td_var_offset(directory),
+		.cb	= str_directory_cb,
+		.help	= "Directory to store files in",
+		.category = FIO_OPT_C_FILE,
+		.group	= FIO_OPT_G_FILENAME,
 	},
 	{
 		.name	= "filename_format",
@@ -1138,44 +1251,20 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 		.prio	= -1, /* must come after "directory" */
 		.help	= "Override default $jobname.$jobnum.$filenum naming",
 		.def	= "$jobname.$jobnum.$filenum",
-	},
-	{
-		.name	= "kb_base",
-		.type	= FIO_OPT_INT,
-		.off1	= td_var_offset(kb_base),
-		.verify	= kb_base_verify,
-		.prio	= 1,
-		.def	= "1024",
-		.help	= "How many bytes per KB for reporting (1000 or 1024)",
-	},
-	{
-		.name	= "unit_base",
-		.type	= FIO_OPT_INT,
-		.off1	= td_var_offset(unit_base),
-		.prio	= 1,
-		.posval = {
-			  { .ival = "0",
-			    .oval = 0,
-			    .help = "Auto-detect",
-			  },
-			  { .ival = "8",
-			    .oval = 8,
-			    .help = "Normal (byte based)",
-			  },
-			  { .ival = "1",
-			    .oval = 1,
-			    .help = "Bit based",
-			  },
-		},
-		.help	= "Bit multiple of result summary data (8 for byte, 1 for bit)",
+		.category = FIO_OPT_C_FILE,
+		.group	= FIO_OPT_G_FILENAME,
 	},
 	{
 		.name	= "lockfile",
+		.lname	= "Lockfile",
 		.type	= FIO_OPT_STR,
 		.off1	= td_var_offset(file_lock_mode),
 		.help	= "Lock file when doing IO to it",
 		.parent	= "filename",
+		.hide	= 0,
 		.def	= "none",
+		.category = FIO_OPT_C_FILE,
+		.group	= FIO_OPT_G_FILENAME,
 		.posval = {
 			  { .ival = "none",
 			    .oval = FILE_LOCK_NONE,
@@ -1194,13 +1283,17 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 	},
 	{
 		.name	= "opendir",
+		.lname	= "Open directory",
 		.type	= FIO_OPT_STR_STORE,
 		.off1	= td_var_offset(opendir),
 		.cb	= str_opendir_cb,
 		.help	= "Recursively add files from this directory and down",
+		.category = FIO_OPT_C_FILE,
+		.group	= FIO_OPT_G_FILENAME,
 	},
 	{
 		.name	= "rw",
+		.lname	= "Read/write",
 		.alias	= "readwrite",
 		.type	= FIO_OPT_STR,
 		.cb	= str_rw_cb,
@@ -1208,6 +1301,8 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 		.help	= "IO direction",
 		.def	= "read",
 		.verify	= rw_verify,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_IO_BASIC,
 		.posval = {
 			  { .ival = "read",
 			    .oval = TD_DDIR_READ,
@@ -1249,10 +1344,13 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 	},
 	{
 		.name	= "rw_sequencer",
+		.lname	= "RW Sequencer",
 		.type	= FIO_OPT_STR,
 		.off1	= td_var_offset(rw_seq),
 		.help	= "IO offset generator modifier",
 		.def	= "sequential",
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_IO_BASIC,
 		.posval = {
 			  { .ival = "sequential",
 			    .oval = RW_SEQ_SEQ,
@@ -1267,10 +1365,13 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 
 	{
 		.name	= "ioengine",
+		.lname	= "IO Engine",
 		.type	= FIO_OPT_STR_STORE,
 		.off1	= td_var_offset(ioengine),
 		.help	= "IO engine to use",
 		.def	= FIO_PREFERRED_ENGINE,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_IO_BASIC,
 		.posval	= {
 			  { .ival = "sync",
 			    .help = "Use read/write",
@@ -1363,78 +1464,118 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 	},
 	{
 		.name	= "iodepth",
+		.lname	= "IO Depth",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(iodepth),
 		.help	= "Number of IO buffers to keep in flight",
 		.minval = 1,
+		.interval = 1,
 		.def	= "1",
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_IO_BASIC,
 	},
 	{
 		.name	= "iodepth_batch",
+		.lname	= "IO Depth batch",
 		.alias	= "iodepth_batch_submit",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(iodepth_batch),
 		.help	= "Number of IO buffers to submit in one go",
 		.parent	= "iodepth",
+		.hide	= 1,
 		.minval	= 1,
+		.interval = 1,
 		.def	= "1",
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_IO_BASIC,
 	},
 	{
 		.name	= "iodepth_batch_complete",
+		.lname	= "IO Depth batch complete",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(iodepth_batch_complete),
 		.help	= "Number of IO buffers to retrieve in one go",
 		.parent	= "iodepth",
+		.hide	= 1,
 		.minval	= 0,
+		.interval = 1,
 		.def	= "1",
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_IO_BASIC,
 	},
 	{
 		.name	= "iodepth_low",
+		.lname	= "IO Depth batch low",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(iodepth_low),
 		.help	= "Low water mark for queuing depth",
 		.parent	= "iodepth",
+		.hide	= 1,
+		.interval = 1,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_IO_BASIC,
 	},
 	{
 		.name	= "size",
+		.lname	= "Size",
 		.type	= FIO_OPT_STR_VAL,
 		.cb	= str_size_cb,
 		.help	= "Total size of device or files",
+		.interval = 1024 * 1024,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "fill_device",
+		.lname	= "Fill device",
 		.alias	= "fill_fs",
 		.type	= FIO_OPT_BOOL,
 		.off1	= td_var_offset(fill_device),
 		.help	= "Write until an ENOSPC error occurs",
 		.def	= "0",
+		.category = FIO_OPT_C_FILE,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "filesize",
+		.lname	= "File size",
 		.type	= FIO_OPT_STR_VAL,
 		.off1	= td_var_offset(file_size_low),
 		.off2	= td_var_offset(file_size_high),
 		.minval = 1,
 		.help	= "Size of individual files",
+		.interval = 1024 * 1024,
+		.category = FIO_OPT_C_FILE,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "offset",
+		.lname	= "IO offset",
 		.alias	= "fileoffset",
 		.type	= FIO_OPT_STR_VAL,
 		.off1	= td_var_offset(start_offset),
 		.help	= "Start IO from this offset",
 		.def	= "0",
+		.interval = 1024 * 1024,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "offset_increment",
+		.lname	= "IO offset increment",
 		.type	= FIO_OPT_STR_VAL,
 		.off1	= td_var_offset(offset_increment),
 		.help	= "What is the increment from one offset to the next",
 		.parent = "offset",
+		.hide	= 1,
 		.def	= "0",
+		.interval = 1024 * 1024,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "bs",
+		.lname	= "Block size",
 		.alias	= "blocksize",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(bs[DDIR_READ]),
@@ -1444,9 +1585,14 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 		.help	= "Block size unit",
 		.def	= "4k",
 		.parent = "rw",
+		.hide	= 1,
+		.interval = 512,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "ba",
+		.lname	= "Block size align",
 		.alias	= "blockalign",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(ba[DDIR_READ]),
@@ -1455,9 +1601,14 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 		.minval	= 1,
 		.help	= "IO block offset alignment",
 		.parent	= "rw",
+		.hide	= 1,
+		.interval = 512,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "bsrange",
+		.lname	= "Block size range",
 		.alias	= "blocksize_range",
 		.type	= FIO_OPT_RANGE,
 		.off1	= td_var_offset(min_bs[DDIR_READ]),
@@ -1469,52 +1620,81 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 		.minval = 1,
 		.help	= "Set block size range (in more detail than bs)",
 		.parent = "rw",
+		.hide	= 1,
+		.interval = 4096,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "bssplit",
+		.lname	= "Block size split",
 		.type	= FIO_OPT_STR,
 		.cb	= str_bssplit_cb,
 		.help	= "Set a specific mix of block sizes",
 		.parent	= "rw",
+		.hide	= 1,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "bs_unaligned",
+		.lname	= "Block size unaligned",
 		.alias	= "blocksize_unaligned",
 		.type	= FIO_OPT_STR_SET,
 		.off1	= td_var_offset(bs_unaligned),
 		.help	= "Don't sector align IO buffer sizes",
 		.parent = "rw",
+		.hide	= 1,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "randrepeat",
+		.lname	= "Random repeatable",
 		.type	= FIO_OPT_BOOL,
 		.off1	= td_var_offset(rand_repeatable),
 		.help	= "Use repeatable random IO pattern",
 		.def	= "1",
 		.parent = "rw",
+		.hide	= 1,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_RANDOM,
 	},
 	{
 		.name	= "use_os_rand",
+		.lname	= "Use OS random",
 		.type	= FIO_OPT_BOOL,
 		.off1	= td_var_offset(use_os_rand),
 		.help	= "Set to use OS random generator",
 		.def	= "0",
 		.parent = "rw",
+		.hide	= 1,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_RANDOM,
 	},
 	{
 		.name	= "norandommap",
+		.lname	= "No randommap",
 		.type	= FIO_OPT_STR_SET,
 		.off1	= td_var_offset(norandommap),
 		.help	= "Accept potential duplicate random blocks",
 		.parent = "rw",
+		.hide	= 1,
+		.hide_on_set = 1,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_RANDOM,
 	},
 	{
 		.name	= "softrandommap",
+		.lname	= "Soft randommap",
 		.type	= FIO_OPT_BOOL,
 		.off1	= td_var_offset(softrandommap),
 		.help	= "Set norandommap if randommap allocation fails",
 		.parent	= "norandommap",
+		.hide	= 1,
 		.def	= "0",
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_RANDOM,
 	},
 	{
 		.name	= "random_generator",
@@ -1532,6 +1712,8 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 			    .help = "Variable length LFSR",
 			  },
 		},
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_RANDOM,
 	},
 	{
 		.name	= "random_distribution",
@@ -1554,28 +1736,40 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 			    .help = "Pareto distribution",
 			  },
 		},
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_RANDOM,
 	},
 	{
 		.name	= "nrfiles",
+		.lname	= "Number of files",
 		.alias	= "nr_files",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(nr_files),
 		.help	= "Split job workload between this number of files",
 		.def	= "1",
+		.interval = 1,
+		.category = FIO_OPT_C_FILE,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "openfiles",
+		.lname	= "Number of open files",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(open_files),
 		.help	= "Number of files to keep open at the same time",
+		.category = FIO_OPT_C_FILE,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "file_service_type",
+		.lname	= "File service type",
 		.type	= FIO_OPT_STR,
 		.cb	= str_fst_cb,
 		.off1	= td_var_offset(file_service_type),
 		.help	= "How to select which file to service next",
 		.def	= "roundrobin",
+		.category = FIO_OPT_C_FILE,
+		.group	= FIO_OPT_G_INVALID,
 		.posval	= {
 			  { .ival = "random",
 			    .oval = FIO_FSERVICE_RANDOM,
@@ -1591,14 +1785,18 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 			  },
 		},
 		.parent = "nrfiles",
+		.hide	= 1,
 	},
 #ifdef CONFIG_POSIX_FALLOCATE
 	{
 		.name	= "fallocate",
+		.lname	= "Fallocate",
 		.type	= FIO_OPT_STR,
 		.off1	= td_var_offset(fallocate_mode),
 		.help	= "Whether pre-allocation is performed when laying out files",
 		.def	= "posix",
+		.category = FIO_OPT_C_FILE,
+		.group	= FIO_OPT_G_INVALID,
 		.posval	= {
 			  { .ival = "none",
 			    .oval = FIO_FALLOCATE_NONE,
@@ -1628,35 +1826,51 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 #endif	/* CONFIG_POSIX_FALLOCATE */
 	{
 		.name	= "fadvise_hint",
+		.lname	= "Fadvise hint",
 		.type	= FIO_OPT_BOOL,
 		.off1	= td_var_offset(fadvise_hint),
 		.help	= "Use fadvise() to advise the kernel on IO pattern",
 		.def	= "1",
+		.category = FIO_OPT_C_FILE,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "fsync",
+		.lname	= "Fsync",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(fsync_blocks),
 		.help	= "Issue fsync for writes every given number of blocks",
 		.def	= "0",
+		.interval = 1,
+		.category = FIO_OPT_C_FILE,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "fdatasync",
+		.lname	= "Fdatasync",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(fdatasync_blocks),
 		.help	= "Issue fdatasync for writes every given number of blocks",
 		.def	= "0",
+		.interval = 1,
+		.category = FIO_OPT_C_FILE,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "write_barrier",
+		.lname	= "Write barrier",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(barrier_blocks),
 		.help	= "Make every Nth write a barrier write",
 		.def	= "0",
+		.interval = 1,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_INVALID,
 	},
 #ifdef CONFIG_SYNC_FILE_RANGE
 	{
 		.name	= "sync_file_range",
+		.lname	= "Sync file range",
 		.posval	= {
 			  { .ival = "wait_before",
 			    .oval = SYNC_FILE_RANGE_WAIT_BEFORE,
@@ -1679,77 +1893,113 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 		.cb	= str_sfr_cb,
 		.off1	= td_var_offset(sync_file_range),
 		.help	= "Use sync_file_range()",
+		.category = FIO_OPT_C_FILE,
+		.group	= FIO_OPT_G_INVALID,
 	},
 #endif
 	{
 		.name	= "direct",
+		.lname	= "Direct I/O",
 		.type	= FIO_OPT_BOOL,
 		.off1	= td_var_offset(odirect),
 		.help	= "Use O_DIRECT IO (negates buffered)",
 		.def	= "0",
+		.inverse = "buffered",
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_IO_TYPE,
 	},
 	{
 		.name	= "buffered",
+		.lname	= "Buffered I/O",
 		.type	= FIO_OPT_BOOL,
 		.off1	= td_var_offset(odirect),
 		.neg	= 1,
 		.help	= "Use buffered IO (negates direct)",
 		.def	= "1",
+		.inverse = "direct",
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_IO_TYPE,
 	},
 	{
 		.name	= "overwrite",
+		.lname	= "Overwrite",
 		.type	= FIO_OPT_BOOL,
 		.off1	= td_var_offset(overwrite),
 		.help	= "When writing, set whether to overwrite current data",
 		.def	= "0",
+		.category = FIO_OPT_C_FILE,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "loops",
+		.lname	= "Loops",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(loops),
 		.help	= "Number of times to run the job",
 		.def	= "1",
+		.interval = 1,
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_RUNTIME,
 	},
 	{
 		.name	= "numjobs",
+		.lname	= "Number of jobs",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(numjobs),
 		.help	= "Duplicate this job this many times",
 		.def	= "1",
+		.interval = 1,
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_RUNTIME,
 	},
 	{
 		.name	= "startdelay",
+		.lname	= "Start delay",
 		.type	= FIO_OPT_STR_VAL_TIME,
 		.off1	= td_var_offset(start_delay),
 		.help	= "Only start job when this period has passed",
 		.def	= "0",
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_RUNTIME,
 	},
 	{
 		.name	= "runtime",
+		.lname	= "Runtime",
 		.alias	= "timeout",
 		.type	= FIO_OPT_STR_VAL_TIME,
 		.off1	= td_var_offset(timeout),
 		.help	= "Stop workload when this amount of time has passed",
 		.def	= "0",
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_RUNTIME,
 	},
 	{
 		.name	= "time_based",
+		.lname	= "Time based",
 		.type	= FIO_OPT_STR_SET,
 		.off1	= td_var_offset(time_based),
 		.help	= "Keep running until runtime/timeout is met",
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_RUNTIME,
 	},
 	{
 		.name	= "ramp_time",
+		.lname	= "Ramp time",
 		.type	= FIO_OPT_STR_VAL_TIME,
 		.off1	= td_var_offset(ramp_time),
 		.help	= "Ramp up time before measuring performance",
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_RUNTIME,
 	},
 	{
 		.name	= "clocksource",
+		.lname	= "Clock source",
 		.type	= FIO_OPT_STR,
 		.cb	= fio_clock_source_cb,
 		.off1	= td_var_offset(clocksource),
 		.help	= "What type of timing source to use",
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_CLOCK,
 		.posval	= {
 #ifdef CONFIG_GETTIMEOFDAY
 			  { .ival = "gettimeofday",
@@ -1774,11 +2024,14 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 	{
 		.name	= "mem",
 		.alias	= "iomem",
+		.lname	= "I/O Memory",
 		.type	= FIO_OPT_STR,
 		.cb	= str_mem_cb,
 		.off1	= td_var_offset(mem_type),
 		.help	= "Backing type for IO buffers",
 		.def	= "malloc",
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_INVALID,
 		.posval	= {
 			  { .ival = "malloc",
 			    .oval = MEM_MALLOC,
@@ -1809,20 +2062,26 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 	{
 		.name	= "iomem_align",
 		.alias	= "mem_align",
+		.lname	= "I/O memory alignment",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(mem_align),
 		.minval	= 0,
 		.help	= "IO memory buffer offset alignment",
 		.def	= "0",
 		.parent	= "iomem",
+		.hide	= 1,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "verify",
+		.lname	= "Verify",
 		.type	= FIO_OPT_STR,
 		.off1	= td_var_offset(verify),
 		.help	= "Verify data written",
-		.cb	= str_verify_cb,
 		.def	= "0",
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_VERIFY,
 		.posval = {
 			  { .ival = "0",
 			    .oval = VERIFY_NONE,
@@ -1881,19 +2140,27 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 	},
 	{
 		.name	= "do_verify",
+		.lname	= "Perform verify step",
 		.type	= FIO_OPT_BOOL,
 		.off1	= td_var_offset(do_verify),
 		.help	= "Run verification stage after write",
 		.def	= "1",
 		.parent = "verify",
+		.hide	= 1,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_VERIFY,
 	},
 	{
 		.name	= "verifysort",
+		.lname	= "Verify sort",
 		.type	= FIO_OPT_BOOL,
 		.off1	= td_var_offset(verifysort),
 		.help	= "Sort written verify blocks for read back",
 		.def	= "1",
 		.parent = "verify",
+		.hide	= 1,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_VERIFY,
 	},
 	{
 		.name	= "verifysort_nr",
@@ -1904,75 +2171,114 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 		.maxval	= 131072,
 		.def	= "1024",
 		.parent = "verify",
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_VERIFY,
 	},
 	{
 		.name   = "verify_interval",
+		.lname	= "Verify interval",
 		.type   = FIO_OPT_INT,
 		.off1   = td_var_offset(verify_interval),
 		.minval	= 2 * sizeof(struct verify_header),
 		.help   = "Store verify buffer header every N bytes",
 		.parent	= "verify",
+		.hide	= 1,
+		.interval = 2 * sizeof(struct verify_header),
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_VERIFY,
 	},
 	{
 		.name	= "verify_offset",
+		.lname	= "Verify offset",
 		.type	= FIO_OPT_INT,
 		.help	= "Offset verify header location by N bytes",
-		.def	= "0",
-		.cb	= str_verify_offset_cb,
+		.off1	= td_var_offset(verify_offset),
+		.minval	= sizeof(struct verify_header),
 		.parent	= "verify",
+		.hide	= 1,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_VERIFY,
 	},
 	{
 		.name	= "verify_pattern",
+		.lname	= "Verify pattern",
 		.type	= FIO_OPT_STR,
 		.cb	= str_verify_pattern_cb,
 		.help	= "Fill pattern for IO buffers",
 		.parent	= "verify",
+		.hide	= 1,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_VERIFY,
 	},
 	{
 		.name	= "verify_fatal",
+		.lname	= "Verify fatal",
 		.type	= FIO_OPT_BOOL,
 		.off1	= td_var_offset(verify_fatal),
 		.def	= "0",
 		.help	= "Exit on a single verify failure, don't continue",
 		.parent = "verify",
+		.hide	= 1,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_VERIFY,
 	},
 	{
 		.name	= "verify_dump",
+		.lname	= "Verify dump",
 		.type	= FIO_OPT_BOOL,
 		.off1	= td_var_offset(verify_dump),
 		.def	= "0",
 		.help	= "Dump contents of good and bad blocks on failure",
 		.parent = "verify",
+		.hide	= 1,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_VERIFY,
 	},
 	{
 		.name	= "verify_async",
+		.lname	= "Verify asynchronously",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(verify_async),
 		.def	= "0",
 		.help	= "Number of async verifier threads to use",
 		.parent	= "verify",
+		.hide	= 1,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_VERIFY,
 	},
 	{
 		.name	= "verify_backlog",
+		.lname	= "Verify backlog",
 		.type	= FIO_OPT_STR_VAL,
 		.off1	= td_var_offset(verify_backlog),
 		.help	= "Verify after this number of blocks are written",
 		.parent	= "verify",
+		.hide	= 1,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_VERIFY,
 	},
 	{
 		.name	= "verify_backlog_batch",
+		.lname	= "Verify backlog batch",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(verify_batch),
 		.help	= "Verify this number of IO blocks",
 		.parent	= "verify",
+		.hide	= 1,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_VERIFY,
 	},
 #ifdef FIO_HAVE_CPU_AFFINITY
 	{
 		.name	= "verify_async_cpus",
+		.lname	= "Async verify CPUs",
 		.type	= FIO_OPT_STR,
 		.cb	= str_verify_cpus_allowed_cb,
 		.help	= "Set CPUs allowed for async verify threads",
 		.parent	= "verify_async",
+		.hide	= 1,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_VERIFY,
 	},
 #endif
 	{
@@ -1980,326 +2286,439 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 		.off1	= td_var_offset(experimental_verify),
 		.type	= FIO_OPT_BOOL,
 		.help	= "Enable experimental verification",
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_VERIFY,
 	},
 #ifdef FIO_HAVE_TRIM
 	{
 		.name	= "trim_percentage",
+		.lname	= "Trim percentage",
 		.type	= FIO_OPT_INT,
-		.cb	= str_verify_trim_cb,
+		.off1	= td_var_offset(trim_percentage),
+		.minval = 0,
 		.maxval = 100,
 		.help	= "Number of verify blocks to discard/trim",
 		.parent	= "verify",
 		.def	= "0",
+		.interval = 1,
+		.hide	= 1,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_TRIM,
 	},
 	{
 		.name	= "trim_verify_zero",
-		.type	= FIO_OPT_INT,
+		.lname	= "Verify trim zero",
+		.type	= FIO_OPT_BOOL,
 		.help	= "Verify that trim/discarded blocks are returned as zeroes",
 		.off1	= td_var_offset(trim_zero),
 		.parent	= "trim_percentage",
+		.hide	= 1,
 		.def	= "1",
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_TRIM,
 	},
 	{
 		.name	= "trim_backlog",
+		.lname	= "Trim backlog",
 		.type	= FIO_OPT_STR_VAL,
 		.off1	= td_var_offset(trim_backlog),
 		.help	= "Trim after this number of blocks are written",
 		.parent	= "trim_percentage",
+		.hide	= 1,
+		.interval = 1,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_TRIM,
 	},
 	{
 		.name	= "trim_backlog_batch",
+		.lname	= "Trim backlog batch",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(trim_batch),
 		.help	= "Trim this number of IO blocks",
 		.parent	= "trim_percentage",
+		.hide	= 1,
+		.interval = 1,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_TRIM,
 	},
 #endif
 	{
 		.name	= "write_iolog",
+		.lname	= "Write I/O log",
 		.type	= FIO_OPT_STR_STORE,
 		.off1	= td_var_offset(write_iolog_file),
 		.help	= "Store IO pattern to file",
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_IOLOG,
 	},
 	{
 		.name	= "read_iolog",
+		.lname	= "Read I/O log",
 		.type	= FIO_OPT_STR_STORE,
 		.off1	= td_var_offset(read_iolog_file),
 		.help	= "Playback IO pattern from file",
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_IOLOG,
 	},
 	{
 		.name	= "replay_no_stall",
-		.type	= FIO_OPT_INT,
+		.lname	= "Don't stall on replay",
+		.type	= FIO_OPT_BOOL,
 		.off1	= td_var_offset(no_stall),
 		.def	= "0",
 		.parent	= "read_iolog",
+		.hide	= 1,
 		.help	= "Playback IO pattern file as fast as possible without stalls",
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_IOLOG,
 	},
 	{
 		.name	= "replay_redirect",
+		.lname	= "Redirect device for replay",
 		.type	= FIO_OPT_STR_STORE,
 		.off1	= td_var_offset(replay_redirect),
 		.parent	= "read_iolog",
+		.hide	= 1,
 		.help	= "Replay all I/O onto this device, regardless of trace device",
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_IOLOG,
 	},
 	{
 		.name	= "exec_prerun",
+		.lname	= "Pre-execute runnable",
 		.type	= FIO_OPT_STR_STORE,
 		.off1	= td_var_offset(exec_prerun),
 		.help	= "Execute this file prior to running job",
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "exec_postrun",
+		.lname	= "Post-execute runnable",
 		.type	= FIO_OPT_STR_STORE,
 		.off1	= td_var_offset(exec_postrun),
 		.help	= "Execute this file after running job",
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_INVALID,
 	},
 #ifdef FIO_HAVE_IOSCHED_SWITCH
 	{
 		.name	= "ioscheduler",
+		.lname	= "I/O scheduler",
 		.type	= FIO_OPT_STR_STORE,
 		.off1	= td_var_offset(ioscheduler),
 		.help	= "Use this IO scheduler on the backing device",
+		.category = FIO_OPT_C_FILE,
+		.group	= FIO_OPT_G_INVALID,
 	},
 #endif
 	{
 		.name	= "zonesize",
+		.lname	= "Zone size",
 		.type	= FIO_OPT_STR_VAL,
 		.off1	= td_var_offset(zone_size),
 		.help	= "Amount of data to read per zone",
 		.def	= "0",
+		.interval = 1024 * 1024,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_ZONE,
 	},
 	{
 		.name	= "zonerange",
+		.lname	= "Zone range",
 		.type	= FIO_OPT_STR_VAL,
 		.off1	= td_var_offset(zone_range),
 		.help	= "Give size of an IO zone",
 		.def	= "0",
+		.interval = 1024 * 1024,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_ZONE,
 	},
 	{
 		.name	= "zoneskip",
+		.lname	= "Zone skip",
 		.type	= FIO_OPT_STR_VAL,
 		.off1	= td_var_offset(zone_skip),
 		.help	= "Space between IO zones",
 		.def	= "0",
+		.interval = 1024 * 1024,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_ZONE,
 	},
 	{
 		.name	= "lockmem",
+		.lname	= "Lock memory",
 		.type	= FIO_OPT_STR_VAL,
-		.cb	= str_lockmem_cb,
-		.help	= "Lock down this amount of memory",
+		.off1	= td_var_offset(lockmem),
+		.help	= "Lock down this amount of memory (per worker)",
 		.def	= "0",
+		.interval = 1024 * 1024,
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "rwmixread",
+		.lname	= "Read/write mix read",
 		.type	= FIO_OPT_INT,
 		.cb	= str_rwmix_read_cb,
 		.maxval	= 100,
 		.help	= "Percentage of mixed workload that is reads",
 		.def	= "50",
+		.interval = 5,
+		.inverse = "rwmixwrite",
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_RWMIX,
 	},
 	{
 		.name	= "rwmixwrite",
+		.lname	= "Read/write mix write",
 		.type	= FIO_OPT_INT,
 		.cb	= str_rwmix_write_cb,
 		.maxval	= 100,
 		.help	= "Percentage of mixed workload that is writes",
 		.def	= "50",
+		.interval = 5,
+		.inverse = "rwmixread",
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_RWMIX,
 	},
 	{
 		.name	= "rwmixcycle",
+		.lname	= "Read/write mix cycle",
 		.type	= FIO_OPT_DEPRECATED,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_RWMIX,
 	},
 	{
 		.name	= "nice",
+		.lname	= "Nice",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(nice),
 		.help	= "Set job CPU nice value",
 		.minval	= -19,
 		.maxval	= 20,
 		.def	= "0",
+		.interval = 1,
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_CRED,
 	},
 #ifdef FIO_HAVE_IOPRIO
 	{
 		.name	= "prio",
+		.lname	= "I/O nice priority",
 		.type	= FIO_OPT_INT,
 		.cb	= str_prio_cb,
 		.help	= "Set job IO priority value",
 		.minval	= 0,
 		.maxval	= 7,
+		.interval = 1,
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_CRED,
 	},
 	{
 		.name	= "prioclass",
+		.lname	= "I/O nice priority class",
 		.type	= FIO_OPT_INT,
 		.cb	= str_prioclass_cb,
 		.help	= "Set job IO priority class",
 		.minval	= 0,
 		.maxval	= 3,
+		.interval = 1,
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_CRED,
 	},
 #endif
 	{
 		.name	= "thinktime",
+		.lname	= "Thinktime",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(thinktime),
 		.help	= "Idle time between IO buffers (usec)",
 		.def	= "0",
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_THINKTIME,
 	},
 	{
 		.name	= "thinktime_spin",
+		.lname	= "Thinktime spin",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(thinktime_spin),
 		.help	= "Start think time by spinning this amount (usec)",
 		.def	= "0",
 		.parent	= "thinktime",
+		.hide	= 1,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_THINKTIME,
 	},
 	{
 		.name	= "thinktime_blocks",
+		.lname	= "Thinktime blocks",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(thinktime_blocks),
 		.help	= "IO buffer period between 'thinktime'",
 		.def	= "1",
 		.parent	= "thinktime",
+		.hide	= 1,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_THINKTIME,
 	},
 	{
 		.name	= "rate",
+		.lname	= "I/O rate",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(rate[DDIR_READ]),
 		.off2	= td_var_offset(rate[DDIR_WRITE]),
 		.off3	= td_var_offset(rate[DDIR_TRIM]),
 		.help	= "Set bandwidth rate",
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_RATE,
 	},
 	{
 		.name	= "ratemin",
+		.lname	= "I/O min rate",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(ratemin[DDIR_READ]),
 		.off2	= td_var_offset(ratemin[DDIR_WRITE]),
 		.off3	= td_var_offset(ratemin[DDIR_TRIM]),
 		.help	= "Job must meet this rate or it will be shutdown",
 		.parent	= "rate",
+		.hide	= 1,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_RATE,
 	},
 	{
 		.name	= "rate_iops",
+		.lname	= "I/O rate IOPS",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(rate_iops[DDIR_READ]),
 		.off2	= td_var_offset(rate_iops[DDIR_WRITE]),
 		.off3	= td_var_offset(rate_iops[DDIR_TRIM]),
 		.help	= "Limit IO used to this number of IO operations/sec",
+		.hide	= 1,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_RATE,
 	},
 	{
 		.name	= "rate_iops_min",
+		.lname	= "I/O min rate IOPS",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(rate_iops_min[DDIR_READ]),
 		.off2	= td_var_offset(rate_iops_min[DDIR_WRITE]),
 		.off3	= td_var_offset(rate_iops_min[DDIR_TRIM]),
 		.help	= "Job must meet this rate or it will be shut down",
 		.parent	= "rate_iops",
+		.hide	= 1,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_RATE,
 	},
 	{
 		.name	= "ratecycle",
+		.lname	= "I/O rate cycle",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(ratecycle),
 		.help	= "Window average for rate limits (msec)",
 		.def	= "1000",
 		.parent = "rate",
+		.hide	= 1,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_RATE,
 	},
 	{
 		.name	= "max_latency",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(max_latency),
 		.help	= "Maximum tolerated IO latency (usec)",
+		.category = FIO_OPT_C_IO,
+		.group = FIO_OPT_G_RATE,
 	},
 	{
 		.name	= "invalidate",
+		.lname	= "Cache invalidate",
 		.type	= FIO_OPT_BOOL,
 		.off1	= td_var_offset(invalidate_cache),
 		.help	= "Invalidate buffer/page cache prior to running job",
 		.def	= "1",
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_IO_TYPE,
 	},
 	{
 		.name	= "sync",
+		.lname	= "Synchronous I/O",
 		.type	= FIO_OPT_BOOL,
 		.off1	= td_var_offset(sync_io),
 		.help	= "Use O_SYNC for buffered writes",
 		.def	= "0",
 		.parent = "buffered",
-	},
-	{
-		.name	= "bwavgtime",
-		.type	= FIO_OPT_INT,
-		.off1	= td_var_offset(bw_avg_time),
-		.help	= "Time window over which to calculate bandwidth"
-			  " (msec)",
-		.def	= "500",
-		.parent	= "write_bw_log",
-	},
-	{
-		.name	= "iopsavgtime",
-		.type	= FIO_OPT_INT,
-		.off1	= td_var_offset(iops_avg_time),
-		.help	= "Time window over which to calculate IOPS (msec)",
-		.def	= "500",
-		.parent	= "write_iops_log",
+		.hide	= 1,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_IO_TYPE,
 	},
 	{
 		.name	= "create_serialize",
+		.lname	= "Create serialize",
 		.type	= FIO_OPT_BOOL,
 		.off1	= td_var_offset(create_serialize),
 		.help	= "Serialize creating of job files",
 		.def	= "1",
+		.category = FIO_OPT_C_FILE,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "create_fsync",
+		.lname	= "Create fsync",
 		.type	= FIO_OPT_BOOL,
 		.off1	= td_var_offset(create_fsync),
 		.help	= "fsync file after creation",
 		.def	= "1",
+		.category = FIO_OPT_C_FILE,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "create_on_open",
+		.lname	= "Create on open",
 		.type	= FIO_OPT_BOOL,
 		.off1	= td_var_offset(create_on_open),
 		.help	= "Create files when they are opened for IO",
 		.def	= "0",
+		.category = FIO_OPT_C_FILE,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "create_only",
 		.type	= FIO_OPT_BOOL,
 		.off1	= td_var_offset(create_only),
 		.help	= "Only perform file creation phase",
+		.category = FIO_OPT_C_FILE,
 		.def	= "0",
 	},
 	{
 		.name	= "pre_read",
+		.lname	= "Pre-read files",
 		.type	= FIO_OPT_BOOL,
 		.off1	= td_var_offset(pre_read),
 		.help	= "Pre-read files before starting official testing",
 		.def	= "0",
-	},
-	{
-		.name	= "cpuload",
-		.type	= FIO_OPT_INT,
-		.off1	= td_var_offset(cpuload),
-		.help	= "Use this percentage of CPU",
-	},
-	{
-		.name	= "cpuchunks",
-		.type	= FIO_OPT_INT,
-		.off1	= td_var_offset(cpucycle),
-		.help	= "Length of the CPU burn cycles (usecs)",
-		.def	= "50000",
-		.parent = "cpuload",
+		.category = FIO_OPT_C_FILE,
+		.group	= FIO_OPT_G_INVALID,
 	},
 #ifdef FIO_HAVE_CPU_AFFINITY
 	{
 		.name	= "cpumask",
+		.lname	= "CPU mask",
 		.type	= FIO_OPT_INT,
 		.cb	= str_cpumask_cb,
 		.help	= "CPU affinity mask",
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_CRED,
 	},
 	{
 		.name	= "cpus_allowed",
+		.lname	= "CPUs allowed",
 		.type	= FIO_OPT_STR,
 		.cb	= str_cpus_allowed_cb,
 		.help	= "Set CPUs allowed",
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_CRED,
 	},
 #endif
 #ifdef CONFIG_LIBNUMA
@@ -2308,144 +2727,227 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 		.type	= FIO_OPT_STR,
 		.cb	= str_numa_cpunodes_cb,
 		.help	= "NUMA CPU nodes bind",
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "numa_mem_policy",
 		.type	= FIO_OPT_STR,
 		.cb	= str_numa_mpol_cb,
 		.help	= "NUMA memory policy setup",
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_INVALID,
 	},
 #endif
 	{
 		.name	= "end_fsync",
+		.lname	= "End fsync",
 		.type	= FIO_OPT_BOOL,
 		.off1	= td_var_offset(end_fsync),
 		.help	= "Include fsync at the end of job",
 		.def	= "0",
+		.category = FIO_OPT_C_FILE,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "fsync_on_close",
+		.lname	= "Fsync on close",
 		.type	= FIO_OPT_BOOL,
 		.off1	= td_var_offset(fsync_on_close),
 		.help	= "fsync files on close",
 		.def	= "0",
+		.category = FIO_OPT_C_FILE,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "unlink",
+		.lname	= "Unlink file",
 		.type	= FIO_OPT_BOOL,
 		.off1	= td_var_offset(unlink),
 		.help	= "Unlink created files after job has completed",
 		.def	= "0",
+		.category = FIO_OPT_C_FILE,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "exitall",
+		.lname	= "Exit-all on terminate",
 		.type	= FIO_OPT_STR_SET,
 		.cb	= str_exitall_cb,
 		.help	= "Terminate all jobs when one exits",
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_PROCESS,
 	},
 	{
 		.name	= "stonewall",
+		.lname	= "Wait for previous",
 		.alias	= "wait_for_previous",
 		.type	= FIO_OPT_STR_SET,
 		.off1	= td_var_offset(stonewall),
 		.help	= "Insert a hard barrier between this job and previous",
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_PROCESS,
 	},
 	{
 		.name	= "new_group",
+		.lname	= "New group",
 		.type	= FIO_OPT_STR_SET,
 		.off1	= td_var_offset(new_group),
 		.help	= "Mark the start of a new group (for reporting)",
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_PROCESS,
 	},
 	{
 		.name	= "thread",
+		.lname	= "Thread",
 		.type	= FIO_OPT_STR_SET,
 		.off1	= td_var_offset(use_thread),
-		.help	= "Use threads instead of forks",
+		.help	= "Use threads instead of processes",
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_PROCESS,
 	},
 	{
 		.name	= "write_bw_log",
+		.lname	= "Write bandwidth log",
 		.type	= FIO_OPT_STR,
-		.off1	= td_var_offset(write_bw_log),
+		.off1	= td_var_offset(bw_log_file),
 		.cb	= str_write_bw_log_cb,
 		.help	= "Write log of bandwidth during run",
+		.category = FIO_OPT_C_LOG,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "write_lat_log",
+		.lname	= "Write latency log",
 		.type	= FIO_OPT_STR,
-		.off1	= td_var_offset(write_lat_log),
+		.off1	= td_var_offset(lat_log_file),
 		.cb	= str_write_lat_log_cb,
 		.help	= "Write log of latency during run",
+		.category = FIO_OPT_C_LOG,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "write_iops_log",
+		.lname	= "Write IOPS log",
 		.type	= FIO_OPT_STR,
-		.off1	= td_var_offset(write_iops_log),
+		.off1	= td_var_offset(iops_log_file),
 		.cb	= str_write_iops_log_cb,
 		.help	= "Write log of IOPS during run",
+		.category = FIO_OPT_C_LOG,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "log_avg_msec",
+		.lname	= "Log averaging (msec)",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(log_avg_msec),
 		.help	= "Average bw/iops/lat logs over this period of time",
 		.def	= "0",
+		.category = FIO_OPT_C_LOG,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
-		.name	= "hugepage-size",
+		.name	= "bwavgtime",
+		.lname	= "Bandwidth average time",
 		.type	= FIO_OPT_INT,
-		.off1	= td_var_offset(hugepage_size),
-		.help	= "When using hugepages, specify size of each page",
-		.def	= __fio_stringify(FIO_HUGE_PAGE),
+		.off1	= td_var_offset(bw_avg_time),
+		.help	= "Time window over which to calculate bandwidth"
+			  " (msec)",
+		.def	= "500",
+		.parent	= "write_bw_log",
+		.hide	= 1,
+		.interval = 100,
+		.category = FIO_OPT_C_LOG,
+		.group	= FIO_OPT_G_INVALID,
+	},
+	{
+		.name	= "iopsavgtime",
+		.lname	= "IOPS average time",
+		.type	= FIO_OPT_INT,
+		.off1	= td_var_offset(iops_avg_time),
+		.help	= "Time window over which to calculate IOPS (msec)",
+		.def	= "500",
+		.parent	= "write_iops_log",
+		.hide	= 1,
+		.interval = 100,
+		.category = FIO_OPT_C_LOG,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "group_reporting",
-		.type	= FIO_OPT_STR_SET,
+		.lname	= "Group reporting",
+		.type	= FIO_OPT_BOOL,
 		.off1	= td_var_offset(group_reporting),
 		.help	= "Do reporting on a per-group basis",
+		.def	= "1",
+		.category = FIO_OPT_C_STAT,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "zero_buffers",
+		.lname	= "Zero I/O buffers",
 		.type	= FIO_OPT_STR_SET,
 		.off1	= td_var_offset(zero_buffers),
 		.help	= "Init IO buffers to all zeroes",
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_IO_BUF,
 	},
 	{
 		.name	= "refill_buffers",
+		.lname	= "Refill I/O buffers",
 		.type	= FIO_OPT_STR_SET,
 		.off1	= td_var_offset(refill_buffers),
 		.help	= "Refill IO buffers on every IO submit",
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_IO_BUF,
 	},
 	{
 		.name	= "scramble_buffers",
+		.lname	= "Scramble I/O buffers",
 		.type	= FIO_OPT_BOOL,
 		.off1	= td_var_offset(scramble_buffers),
 		.help	= "Slightly scramble buffers on every IO submit",
 		.def	= "1",
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_IO_BUF,
 	},
 	{
 		.name	= "buffer_compress_percentage",
+		.lname	= "Buffer compression percentage",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(compress_percentage),
 		.maxval	= 100,
 		.minval	= 1,
 		.help	= "How compressible the buffer is (approximately)",
+		.interval = 5,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_IO_BUF,
 	},
 	{
 		.name	= "buffer_compress_chunk",
+		.lname	= "Buffer compression chunk size",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(compress_chunk),
 		.parent	= "buffer_compress_percentage",
+		.hide	= 1,
 		.help	= "Size of compressible region in buffer",
+		.interval = 256,
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_IO_BUF,
 	},
 	{
 		.name	= "clat_percentiles",
+		.lname	= "Completion latency percentiles",
 		.type	= FIO_OPT_BOOL,
 		.off1	= td_var_offset(clat_percentiles),
 		.help	= "Enable the reporting of completion latency percentiles",
 		.def	= "1",
+		.category = FIO_OPT_C_STAT,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "percentile_list",
+		.lname	= "Completion latency percentile list",
 		.type	= FIO_OPT_FLOAT_LIST,
 		.off1	= td_var_offset(percentile_list),
 		.off2	= td_var_offset(percentile_precision),
@@ -2454,62 +2956,90 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 		.maxlen	= FIO_IO_U_LIST_MAX_LEN,
 		.minfp	= 0.0,
 		.maxfp	= 100.0,
+		.category = FIO_OPT_C_STAT,
+		.group	= FIO_OPT_G_INVALID,
 	},
 
 #ifdef FIO_HAVE_DISK_UTIL
 	{
 		.name	= "disk_util",
+		.lname	= "Disk utilization",
 		.type	= FIO_OPT_BOOL,
 		.off1	= td_var_offset(do_disk_util),
 		.help	= "Log disk utilization statistics",
 		.def	= "1",
+		.category = FIO_OPT_C_STAT,
+		.group	= FIO_OPT_G_INVALID,
 	},
 #endif
 	{
 		.name	= "gtod_reduce",
+		.lname	= "Reduce gettimeofday() calls",
 		.type	= FIO_OPT_BOOL,
 		.help	= "Greatly reduce number of gettimeofday() calls",
 		.cb	= str_gtod_reduce_cb,
 		.def	= "0",
+		.hide_on_set = 1,
+		.category = FIO_OPT_C_STAT,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "disable_lat",
+		.lname	= "Disable all latency stats",
 		.type	= FIO_OPT_BOOL,
 		.off1	= td_var_offset(disable_lat),
 		.help	= "Disable latency numbers",
 		.parent	= "gtod_reduce",
+		.hide	= 1,
 		.def	= "0",
+		.category = FIO_OPT_C_STAT,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "disable_clat",
+		.lname	= "Disable completion latency stats",
 		.type	= FIO_OPT_BOOL,
 		.off1	= td_var_offset(disable_clat),
 		.help	= "Disable completion latency numbers",
 		.parent	= "gtod_reduce",
+		.hide	= 1,
 		.def	= "0",
+		.category = FIO_OPT_C_STAT,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "disable_slat",
+		.lname	= "Disable submission latency stats",
 		.type	= FIO_OPT_BOOL,
 		.off1	= td_var_offset(disable_slat),
 		.help	= "Disable submission latency numbers",
 		.parent	= "gtod_reduce",
+		.hide	= 1,
 		.def	= "0",
+		.category = FIO_OPT_C_STAT,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "disable_bw_measurement",
+		.lname	= "Disable bandwidth stats",
 		.type	= FIO_OPT_BOOL,
 		.off1	= td_var_offset(disable_bw),
 		.help	= "Disable bandwidth logging",
 		.parent	= "gtod_reduce",
+		.hide	= 1,
 		.def	= "0",
+		.category = FIO_OPT_C_STAT,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "gtod_cpu",
+		.lname	= "Dedicated gettimeofday() CPU",
 		.type	= FIO_OPT_INT,
 		.cb	= str_gtod_cpu_cb,
 		.help	= "Set up dedicated gettimeofday() thread on this CPU",
 		.verify	= gtod_cpu_verify,
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_CLOCK,
 	},
 	{
 		.name	= "unified_rw_reporting",
@@ -2517,13 +3047,18 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 		.off1	= td_var_offset(unified_rw_rep),
 		.help	= "Unify reporting across data direction",
 		.def	= "0",
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "continue_on_error",
+		.lname	= "Continue on error",
 		.type	= FIO_OPT_STR,
 		.off1	= td_var_offset(continue_on_error),
 		.help	= "Continue on non-fatal errors during IO",
 		.def	= "none",
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_ERR,
 		.posval = {
 			  { .ival = "none",
 			    .oval = ERROR_TYPE_NONE,
@@ -2565,6 +3100,8 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 		.cb	= str_ignore_error_cb,
 		.help	= "Set a specific list of errors to ignore",
 		.parent	= "rw",
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_ERR,
 	},
 	{
 		.name	= "error_dump",
@@ -2572,80 +3109,172 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 		.off1	= td_var_offset(error_dump),
 		.def	= "0",
 		.help	= "Dump info on each error",
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_ERR,
 	},
-
 	{
 		.name	= "profile",
+		.lname	= "Profile",
 		.type	= FIO_OPT_STR_STORE,
 		.off1	= td_var_offset(profile),
 		.help	= "Select a specific builtin performance test",
+		.category = FIO_OPT_C_PROFILE,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "cgroup",
+		.lname	= "Cgroup",
 		.type	= FIO_OPT_STR_STORE,
 		.off1	= td_var_offset(cgroup),
 		.help	= "Add job to cgroup of this name",
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_CGROUP,
+	},
+	{
+		.name	= "cgroup_nodelete",
+		.lname	= "Cgroup no-delete",
+		.type	= FIO_OPT_BOOL,
+		.off1	= td_var_offset(cgroup_nodelete),
+		.help	= "Do not delete cgroups after job completion",
+		.def	= "0",
+		.parent	= "cgroup",
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_CGROUP,
 	},
 	{
 		.name	= "cgroup_weight",
+		.lname	= "Cgroup weight",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(cgroup_weight),
 		.help	= "Use given weight for cgroup",
 		.minval = 100,
 		.maxval	= 1000,
-	},
-	{
-		.name	= "cgroup_nodelete",
-		.type	= FIO_OPT_BOOL,
-		.off1	= td_var_offset(cgroup_nodelete),
-		.help	= "Do not delete cgroups after job completion",
-		.def	= "0",
+		.parent	= "cgroup",
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_CGROUP,
 	},
 	{
 		.name	= "uid",
+		.lname	= "User ID",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(uid),
 		.help	= "Run job with this user ID",
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_CRED,
 	},
 	{
 		.name	= "gid",
+		.lname	= "Group ID",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(gid),
 		.help	= "Run job with this group ID",
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_CRED,
+	},
+	{
+		.name	= "kb_base",
+		.lname	= "KB Base",
+		.type	= FIO_OPT_INT,
+		.off1	= td_var_offset(kb_base),
+		.prio	= 1,
+		.def	= "1024",
+		.posval = {
+			  { .ival = "1024",
+			    .oval = 1024,
+			    .help = "Use 1024 as the K base",
+			  },
+			  { .ival = "1000",
+			    .oval = 1000,
+			    .help = "Use 1000 as the K base",
+			  },
+		},
+		.help	= "How many bytes per KB for reporting (1000 or 1024)",
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_INVALID,
+	},
+	{
+		.name	= "unit_base",
+		.lname	= "Base unit for reporting (Bits or Bytes)",
+		.type	= FIO_OPT_INT,
+		.off1	= td_var_offset(unit_base),
+		.prio	= 1,
+		.posval = {
+			  { .ival = "0",
+			    .oval = 0,
+			    .help = "Auto-detect",
+			  },
+			  { .ival = "8",
+			    .oval = 8,
+			    .help = "Normal (byte based)",
+			  },
+			  { .ival = "1",
+			    .oval = 1,
+			    .help = "Bit based",
+			  },
+		},
+		.help	= "Bit multiple of result summary data (8 for byte, 1 for bit)",
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_INVALID,
+	},
+	{
+		.name	= "hugepage-size",
+		.lname	= "Hugepage size",
+		.type	= FIO_OPT_INT,
+		.off1	= td_var_offset(hugepage_size),
+		.help	= "When using hugepages, specify size of each page",
+		.def	= __fio_stringify(FIO_HUGE_PAGE),
+		.interval = 1024 * 1024,
+		.category = FIO_OPT_C_GENERAL,
+		.group	= FIO_OPT_G_INVALID,
 	},
 	{
 		.name	= "flow_id",
+		.lname	= "I/O flow ID",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(flow_id),
 		.help	= "The flow index ID to use",
 		.def	= "0",
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_IO_FLOW,
 	},
 	{
 		.name	= "flow",
+		.lname	= "I/O flow weight",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(flow),
 		.help	= "Weight for flow control of this job",
 		.parent	= "flow_id",
+		.hide	= 1,
 		.def	= "0",
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_IO_FLOW,
 	},
 	{
 		.name	= "flow_watermark",
+		.lname	= "I/O flow watermark",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(flow_watermark),
 		.help	= "High watermark for flow control. This option"
 			" should be set to the same value for all threads"
 			" with non-zero flow.",
 		.parent	= "flow_id",
+		.hide	= 1,
 		.def	= "1024",
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_IO_FLOW,
 	},
 	{
 		.name	= "flow_sleep",
+		.lname	= "I/O flow sleep",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(flow_sleep),
 		.help	= "How many microseconds to sleep after being held"
 			" back by the flow control mechanism",
 		.parent	= "flow_id",
+		.hide	= 1,
 		.def	= "0",
+		.category = FIO_OPT_C_IO,
+		.group	= FIO_OPT_G_IO_FLOW,
 	},
 	{
 		.name = NULL,
@@ -2709,13 +3338,13 @@ void fio_options_dup_and_init(struct option *long_options)
 {
 	unsigned int i;
 
-	options_init(options);
+	options_init(fio_options);
 
 	i = 0;
 	while (long_options[i].name)
 		i++;
 
-	options_to_lopts(options, long_options, i, FIO_GETOPT_JOB);
+	options_to_lopts(fio_options, long_options, i, FIO_GETOPT_JOB);
 }
 
 struct fio_keyword {
@@ -2938,13 +3567,13 @@ int fio_options_parse(struct thread_data *td, char **opts, int num_opts)
 	int i, ret, unknown;
 	char **opts_copy;
 
-	sort_options(opts, options, num_opts);
+	sort_options(opts, fio_options, num_opts);
 	opts_copy = dup_and_sub_options(opts, num_opts);
 
 	for (ret = 0, i = 0, unknown = 0; i < num_opts; i++) {
 		struct fio_option *o;
-		int newret = parse_option(opts_copy[i], opts[i], options, &o,
-					  td);
+		int newret = parse_option(opts_copy[i], opts[i], fio_options,
+						&o, td);
 
 		if (opts_copy[i]) {
 			if (newret && !o) {
@@ -2990,7 +3619,7 @@ int fio_options_parse(struct thread_data *td, char **opts, int num_opts)
 
 int fio_cmd_option_parse(struct thread_data *td, const char *opt, char *val)
 {
-	return parse_cmd_option(opt, val, options, td);
+	return parse_cmd_option(opt, val, fio_options, td);
 }
 
 int fio_cmd_ioengine_option_parse(struct thread_data *td, const char *opt,
@@ -3001,12 +3630,12 @@ int fio_cmd_ioengine_option_parse(struct thread_data *td, const char *opt,
 
 void fio_fill_default_options(struct thread_data *td)
 {
-	fill_default_options(td, options);
+	fill_default_options(td, fio_options);
 }
 
 int fio_show_option_help(const char *opt)
 {
-	return show_cmd_help(options, opt);
+	return show_cmd_help(fio_options, opt);
 }
 
 void options_mem_dupe(void *data, struct fio_option *options)
@@ -3029,7 +3658,7 @@ void options_mem_dupe(void *data, struct fio_option *options)
  */
 void fio_options_mem_dupe(struct thread_data *td)
 {
-	options_mem_dupe(&td->o, options);
+	options_mem_dupe(&td->o, fio_options);
 
 	if (td->eo && td->io_ops) {
 		void *oldeo = td->eo;
@@ -3058,13 +3687,13 @@ int add_option(struct fio_option *o)
 	struct fio_option *__o;
 	int opt_index = 0;
 
-	__o = options;
+	__o = fio_options;
 	while (__o->name) {
 		opt_index++;
 		__o++;
 	}
 
-	memcpy(&options[opt_index], o, sizeof(*o));
+	memcpy(&fio_options[opt_index], o, sizeof(*o));
 	return 0;
 }
 
@@ -3072,7 +3701,7 @@ void invalidate_profile_options(const char *prof_name)
 {
 	struct fio_option *o;
 
-	o = options;
+	o = fio_options;
 	while (o->name) {
 		if (o->prof_name && !strcmp(o->prof_name, prof_name)) {
 			o->type = FIO_OPT_INVALID;
@@ -3087,7 +3716,7 @@ void add_opt_posval(const char *optname, const char *ival, const char *help)
 	struct fio_option *o;
 	unsigned int i;
 
-	o = find_option(options, optname);
+	o = find_option(fio_options, optname);
 	if (!o)
 		return;
 
@@ -3106,7 +3735,7 @@ void del_opt_posval(const char *optname, const char *ival)
 	struct fio_option *o;
 	unsigned int i;
 
-	o = find_option(options, optname);
+	o = find_option(fio_options, optname);
 	if (!o)
 		return;
 
@@ -3123,7 +3752,7 @@ void del_opt_posval(const char *optname, const char *ival)
 
 void fio_options_free(struct thread_data *td)
 {
-	options_free(options, td);
+	options_free(fio_options, td);
 	if (td->eo && td->io_ops && td->io_ops->options) {
 		options_free(td->io_ops->options, td->eo);
 		free(td->eo);
diff --git a/options.h b/options.h
index ed6b9c2..cd34886 100644
--- a/options.h
+++ b/options.h
@@ -40,4 +40,85 @@ static inline struct fio_option *find_option(struct fio_option *options,
 	return NULL;
 }
 
+struct opt_group {
+	const char *name;
+	unsigned int mask;
+};
+
+enum opt_category {
+	__FIO_OPT_C_GENERAL	= 0,
+	__FIO_OPT_C_IO,
+	__FIO_OPT_C_FILE,
+	__FIO_OPT_C_STAT,
+	__FIO_OPT_C_LOG,
+	__FIO_OPT_C_PROFILE,
+	__FIO_OPT_C_ENGINE,
+	__FIO_OPT_C_NR,
+
+	FIO_OPT_C_GENERAL	= (1U << __FIO_OPT_C_GENERAL),
+	FIO_OPT_C_IO		= (1U << __FIO_OPT_C_IO),
+	FIO_OPT_C_FILE		= (1U << __FIO_OPT_C_FILE),
+	FIO_OPT_C_STAT		= (1U << __FIO_OPT_C_STAT),
+	FIO_OPT_C_LOG		= (1U << __FIO_OPT_C_LOG),
+	FIO_OPT_C_PROFILE	= (1U << __FIO_OPT_C_PROFILE),
+	FIO_OPT_C_ENGINE	= (1U << __FIO_OPT_C_ENGINE),
+	FIO_OPT_C_INVALID	= (1U << __FIO_OPT_C_NR),
+};
+
+enum opt_category_group {
+	__FIO_OPT_G_RATE	= 0,
+	__FIO_OPT_G_ZONE,
+	__FIO_OPT_G_RWMIX,
+	__FIO_OPT_G_VERIFY,
+	__FIO_OPT_G_TRIM,
+	__FIO_OPT_G_IOLOG,
+	__FIO_OPT_G_IO_DEPTH,
+	__FIO_OPT_G_IO_FLOW,
+	__FIO_OPT_G_DESC,
+	__FIO_OPT_G_FILENAME,
+	__FIO_OPT_G_IO_BASIC,
+	__FIO_OPT_G_CGROUP,
+	__FIO_OPT_G_RUNTIME,
+	__FIO_OPT_G_PROCESS,
+	__FIO_OPT_G_CRED,
+	__FIO_OPT_G_CLOCK,
+	__FIO_OPT_G_IO_TYPE,
+	__FIO_OPT_G_THINKTIME,
+	__FIO_OPT_G_RANDOM,
+	__FIO_OPT_G_IO_BUF,
+	__FIO_OPT_G_TIOBENCH,
+	__FIO_OPT_G_ERR,
+	__FIO_OPT_G_E4DEFRAG,
+	__FIO_OPT_G_NETIO,
+	__FIO_OPT_G_LIBAIO,
+	__FIO_OPT_G_NR,
+
+	FIO_OPT_G_RATE		= (1U << __FIO_OPT_G_RATE),
+	FIO_OPT_G_ZONE		= (1U << __FIO_OPT_G_ZONE),
+	FIO_OPT_G_RWMIX		= (1U << __FIO_OPT_G_RWMIX),
+	FIO_OPT_G_VERIFY	= (1U << __FIO_OPT_G_VERIFY),
+	FIO_OPT_G_TRIM		= (1U << __FIO_OPT_G_TRIM),
+	FIO_OPT_G_IOLOG		= (1U << __FIO_OPT_G_IOLOG),
+	FIO_OPT_G_IO_DEPTH	= (1U << __FIO_OPT_G_IO_DEPTH),
+	FIO_OPT_G_IO_FLOW	= (1U << __FIO_OPT_G_IO_FLOW),
+	FIO_OPT_G_DESC		= (1U << __FIO_OPT_G_DESC),
+	FIO_OPT_G_FILENAME	= (1U << __FIO_OPT_G_FILENAME),
+	FIO_OPT_G_IO_BASIC	= (1U << __FIO_OPT_G_IO_BASIC),
+	FIO_OPT_G_CGROUP	= (1U << __FIO_OPT_G_CGROUP),
+	FIO_OPT_G_RUNTIME	= (1U << __FIO_OPT_G_RUNTIME),
+	FIO_OPT_G_PROCESS	= (1U << __FIO_OPT_G_PROCESS),
+	FIO_OPT_G_CRED		= (1U << __FIO_OPT_G_CRED),
+	FIO_OPT_G_CLOCK		= (1U << __FIO_OPT_G_CLOCK),
+	FIO_OPT_G_IO_TYPE	= (1U << __FIO_OPT_G_IO_TYPE),
+	FIO_OPT_G_THINKTIME	= (1U << __FIO_OPT_G_THINKTIME),
+	FIO_OPT_G_RANDOM	= (1U << __FIO_OPT_G_RANDOM),
+	FIO_OPT_G_IO_BUF	= (1U << __FIO_OPT_G_IO_BUF),
+	FIO_OPT_G_TIOBENCH	= (1U << __FIO_OPT_G_TIOBENCH),
+	FIO_OPT_G_ERR		= (1U << __FIO_OPT_G_ERR),
+	FIO_OPT_G_E4DEFRAG	= (1U << __FIO_OPT_G_E4DEFRAG),
+	FIO_OPT_G_NETIO		= (1U << __FIO_OPT_G_NETIO),
+	FIO_OPT_G_LIBAIO	= (1U << __FIO_OPT_G_LIBAIO),
+	FIO_OPT_G_INVALID	= (1U << __FIO_OPT_G_NR),
+};
+
 #endif
diff --git a/parse.c b/parse.c
index dddcb8e..83227e9 100644
--- a/parse.c
+++ b/parse.c
@@ -50,7 +50,7 @@ static void posval_sort(struct fio_option *o, struct value_pair *vpmap)
 static void show_option_range(struct fio_option *o,
 				int (*logger)(const char *format, ...))
 {
-	if (o->type == FIO_OPT_FLOAT_LIST){
+	if (o->type == FIO_OPT_FLOAT_LIST) {
 		if (o->minfp == DBL_MIN && o->maxfp == DBL_MAX)
 			return;
 
@@ -58,7 +58,7 @@ static void show_option_range(struct fio_option *o,
 		if (o->maxfp != DBL_MAX)
 			logger(", max=%f", o->maxfp);
 		logger("\n");
-	} else {
+	} else if (!o->posval[0].ival) {
 		if (!o->minval && !o->maxval)
 			return;
 
@@ -479,7 +479,8 @@ static int __handle_option(struct fio_option *o, const char *ptr, void *data,
 				}
 			}
 			if (ret) {
-				log_err("value %d not in allowed range\n",ull);
+				log_err("fio: value %llu not allowed:\n", ull);
+				show_option_values(o);
 				return 1;
 			}
 		}
@@ -958,9 +959,8 @@ int parse_option(char *opt, const char *input,
 		return 1;
 	}
 
-	if (!handle_option(*o, post, data)) {
+	if (!handle_option(*o, post, data))
 		return 0;
-	}
 
 	log_err("fio: failed parsing %s\n", input);
 	return 1;
@@ -1169,6 +1169,11 @@ void option_init(struct fio_option *o)
 	}
 	if (!o->cb && (!o->off1 && !o->roff1))
 		log_err("Option %s: neither cb nor offset given\n", o->name);
+	if (!o->category) {
+		log_info("Option %s: no category defined. Setting to misc\n", o->name);
+		o->category = FIO_OPT_C_GENERAL;
+		o->group = FIO_OPT_G_INVALID;
+	}
 	if (o->type == FIO_OPT_STR || o->type == FIO_OPT_STR_STORE ||
 	    o->type == FIO_OPT_STR_MULTI)
 		return;
@@ -1188,8 +1193,11 @@ void options_init(struct fio_option *options)
 
 	dprint(FD_PARSE, "init options\n");
 
-	for (o = &options[0]; o->name; o++)
+	for (o = &options[0]; o->name; o++) {
 		option_init(o);
+		if (o->inverse)
+			o->inv_opt = find_option(options, o->inverse);
+	}
 }
 
 void options_free(struct fio_option *options, void *data)
diff --git a/parse.h b/parse.h
index b2f9e5a..d09c22d 100644
--- a/parse.h
+++ b/parse.h
@@ -40,6 +40,7 @@ struct value_pair {
  */
 struct fio_option {
 	const char *name;		/* option name */
+	const char *lname;		/* long option name */
 	const char *alias;		/* possible old allowed name */
 	enum fio_opt_type type;		/* option type */
 	unsigned int off1;		/* potential parameters */
@@ -53,6 +54,7 @@ struct fio_option {
 	int minval;
 	double maxfp;			/* max and min floating value */
 	double minfp;
+	unsigned int interval;		/* client hint for suitable interval */
 	unsigned int maxlen;		/* max length */
 	int neg;			/* negate value stored */
 	int prio;
@@ -61,8 +63,14 @@ struct fio_option {
 	const char *def;		/* default setting */
 	struct value_pair posval[PARSE_MAX_VP];/* possible values */
 	const char *parent;		/* parent option */
+	int hide;			/* hide if parent isn't set */
+	int hide_on_set;		/* hide on set, not on unset */
+	const char *inverse;		/* if set, apply opposite action to this option */
+	struct fio_option *inv_opt;	/* cached lookup */
 	int (*verify)(struct fio_option *, void *);
 	const char *prof_name;		/* only valid for specific profile */
+	unsigned int category;		/* what type of option */
+	unsigned int group;		/* who to group with */
 };
 
 typedef int (str_cb_fn)(void *, char *);
diff --git a/profiles/tiobench.c b/profiles/tiobench.c
index f86a337..1e0d2ef 100644
--- a/profiles/tiobench.c
+++ b/profiles/tiobench.c
@@ -26,6 +26,8 @@ static struct fio_option options[] = {
 		.type	= FIO_OPT_STR_VAL,
 		.roff1	= &size,
 		.help	= "Size in MB",
+		.category = FIO_OPT_C_PROFILE,
+		.group	= FIO_OPT_G_TIOBENCH,
 	},
 	{
 		.name	= "block",
@@ -33,24 +35,32 @@ static struct fio_option options[] = {
 		.roff1	= &bs,
 		.help	= "Block size in bytes",
 		.def	= "4k",
+		.category = FIO_OPT_C_PROFILE,
+		.group	= FIO_OPT_G_TIOBENCH,
 	},
 	{
 		.name	= "numruns",
 		.type	= FIO_OPT_INT,
 		.roff1	= &loops,
 		.help	= "Number of runs",
+		.category = FIO_OPT_C_PROFILE,
+		.group	= FIO_OPT_G_TIOBENCH,
 	},
 	{
 		.name	= "dir",
 		.type	= FIO_OPT_STR_STORE,
 		.roff1	= &dir,
 		.help	= "Test directory",
+		.category = FIO_OPT_C_PROFILE,
+		.group	= FIO_OPT_G_TIOBENCH,
 	},
 	{
 		.name	= "threads",
 		.type	= FIO_OPT_INT,
 		.roff1	= &nthreads,
 		.help	= "Number of Threads",
+		.category = FIO_OPT_C_PROFILE,
+		.group	= FIO_OPT_G_TIOBENCH,
 	},
 	{
 		.name	= NULL,
diff --git a/rbtree.c b/rbtree.c
deleted file mode 100644
index 883bc72..0000000
--- a/rbtree.c
+++ /dev/null
@@ -1,333 +0,0 @@
-/*
-  Red Black Trees
-  (C) 1999  Andrea Arcangeli <andrea@xxxxxxx>
-  (C) 2002  David Woodhouse <dwmw2@xxxxxxxxxxxxx>
-  
-  This program is free software; you can redistribute it and/or modify
-  it under the terms of the GNU General Public License as published by
-  the Free Software Foundation; either version 2 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU General Public License for more details.
-
-  You should have received a copy of the GNU General Public License
-  along with this program; if not, write to the Free Software
-  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-  linux/lib/rbtree.c
-*/
-
-#include "rbtree.h"
-
-static void __rb_rotate_left(struct rb_node *node, struct rb_root *root)
-{
-	struct rb_node *right = node->rb_right;
-	struct rb_node *parent = rb_parent(node);
-
-	if ((node->rb_right = right->rb_left))
-		rb_set_parent(right->rb_left, node);
-	right->rb_left = node;
-
-	rb_set_parent(right, parent);
-
-	if (parent)
-	{
-		if (node == parent->rb_left)
-			parent->rb_left = right;
-		else
-			parent->rb_right = right;
-	}
-	else
-		root->rb_node = right;
-	rb_set_parent(node, right);
-}
-
-static void __rb_rotate_right(struct rb_node *node, struct rb_root *root)
-{
-	struct rb_node *left = node->rb_left;
-	struct rb_node *parent = rb_parent(node);
-
-	if ((node->rb_left = left->rb_right))
-		rb_set_parent(left->rb_right, node);
-	left->rb_right = node;
-
-	rb_set_parent(left, parent);
-
-	if (parent)
-	{
-		if (node == parent->rb_right)
-			parent->rb_right = left;
-		else
-			parent->rb_left = left;
-	}
-	else
-		root->rb_node = left;
-	rb_set_parent(node, left);
-}
-
-void rb_insert_color(struct rb_node *node, struct rb_root *root)
-{
-	struct rb_node *parent, *gparent;
-
-	while ((parent = rb_parent(node)) && rb_is_red(parent))
-	{
-		gparent = rb_parent(parent);
-
-		if (parent == gparent->rb_left)
-		{
-			{
-				register struct rb_node *uncle = gparent->rb_right;
-				if (uncle && rb_is_red(uncle))
-				{
-					rb_set_black(uncle);
-					rb_set_black(parent);
-					rb_set_red(gparent);
-					node = gparent;
-					continue;
-				}
-			}
-
-			if (parent->rb_right == node)
-			{
-				register struct rb_node *tmp;
-				__rb_rotate_left(parent, root);
-				tmp = parent;
-				parent = node;
-				node = tmp;
-			}
-
-			rb_set_black(parent);
-			rb_set_red(gparent);
-			__rb_rotate_right(gparent, root);
-		} else {
-			{
-				register struct rb_node *uncle = gparent->rb_left;
-				if (uncle && rb_is_red(uncle))
-				{
-					rb_set_black(uncle);
-					rb_set_black(parent);
-					rb_set_red(gparent);
-					node = gparent;
-					continue;
-				}
-			}
-
-			if (parent->rb_left == node)
-			{
-				register struct rb_node *tmp;
-				__rb_rotate_right(parent, root);
-				tmp = parent;
-				parent = node;
-				node = tmp;
-			}
-
-			rb_set_black(parent);
-			rb_set_red(gparent);
-			__rb_rotate_left(gparent, root);
-		}
-	}
-
-	rb_set_black(root->rb_node);
-}
-
-static void __rb_erase_color(struct rb_node *node, struct rb_node *parent,
-			     struct rb_root *root)
-{
-	struct rb_node *other;
-
-	while ((!node || rb_is_black(node)) && node != root->rb_node)
-	{
-		if (parent->rb_left == node)
-		{
-			other = parent->rb_right;
-			if (rb_is_red(other))
-			{
-				rb_set_black(other);
-				rb_set_red(parent);
-				__rb_rotate_left(parent, root);
-				other = parent->rb_right;
-			}
-			if ((!other->rb_left || rb_is_black(other->rb_left)) &&
-			    (!other->rb_right || rb_is_black(other->rb_right)))
-			{
-				rb_set_red(other);
-				node = parent;
-				parent = rb_parent(node);
-			}
-			else
-			{
-				if (!other->rb_right || rb_is_black(other->rb_right))
-				{
-					struct rb_node *o_left;
-					if ((o_left = other->rb_left))
-						rb_set_black(o_left);
-					rb_set_red(other);
-					__rb_rotate_right(other, root);
-					other = parent->rb_right;
-				}
-				rb_set_color(other, rb_color(parent));
-				rb_set_black(parent);
-				if (other->rb_right)
-					rb_set_black(other->rb_right);
-				__rb_rotate_left(parent, root);
-				node = root->rb_node;
-				break;
-			}
-		}
-		else
-		{
-			other = parent->rb_left;
-			if (rb_is_red(other))
-			{
-				rb_set_black(other);
-				rb_set_red(parent);
-				__rb_rotate_right(parent, root);
-				other = parent->rb_left;
-			}
-			if ((!other->rb_left || rb_is_black(other->rb_left)) &&
-			    (!other->rb_right || rb_is_black(other->rb_right)))
-			{
-				rb_set_red(other);
-				node = parent;
-				parent = rb_parent(node);
-			}
-			else
-			{
-				if (!other->rb_left || rb_is_black(other->rb_left))
-				{
-					register struct rb_node *o_right;
-					if ((o_right = other->rb_right))
-						rb_set_black(o_right);
-					rb_set_red(other);
-					__rb_rotate_left(other, root);
-					other = parent->rb_left;
-				}
-				rb_set_color(other, rb_color(parent));
-				rb_set_black(parent);
-				if (other->rb_left)
-					rb_set_black(other->rb_left);
-				__rb_rotate_right(parent, root);
-				node = root->rb_node;
-				break;
-			}
-		}
-	}
-	if (node)
-		rb_set_black(node);
-}
-
-void rb_erase(struct rb_node *node, struct rb_root *root)
-{
-	struct rb_node *child, *parent;
-	int color;
-
-	if (!node->rb_left)
-		child = node->rb_right;
-	else if (!node->rb_right)
-		child = node->rb_left;
-	else
-	{
-		struct rb_node *old = node, *left;
-
-		node = node->rb_right;
-		while ((left = node->rb_left) != NULL)
-			node = left;
-		child = node->rb_right;
-		parent = rb_parent(node);
-		color = rb_color(node);
-
-		if (child)
-			rb_set_parent(child, parent);
-		if (parent == old) {
-			parent->rb_right = child;
-			parent = node;
-		} else
-			parent->rb_left = child;
-
-		node->rb_parent_color = old->rb_parent_color;
-		node->rb_right = old->rb_right;
-		node->rb_left = old->rb_left;
-
-		if (rb_parent(old))
-		{
-			if (rb_parent(old)->rb_left == old)
-				rb_parent(old)->rb_left = node;
-			else
-				rb_parent(old)->rb_right = node;
-		} else
-			root->rb_node = node;
-
-		rb_set_parent(old->rb_left, node);
-		if (old->rb_right)
-			rb_set_parent(old->rb_right, node);
-		goto color;
-	}
-
-	parent = rb_parent(node);
-	color = rb_color(node);
-
-	if (child)
-		rb_set_parent(child, parent);
-	if (parent)
-	{
-		if (parent->rb_left == node)
-			parent->rb_left = child;
-		else
-			parent->rb_right = child;
-	}
-	else
-		root->rb_node = child;
-
- color:
-	if (color == RB_BLACK)
-		__rb_erase_color(child, parent, root);
-}
-
-/*
- * This function returns the first node (in sort order) of the tree.
- */
-struct rb_node *rb_first(struct rb_root *root)
-{
-	struct rb_node	*n;
-
-	n = root->rb_node;
-	if (!n)
-		return NULL;
-	while (n->rb_left)
-		n = n->rb_left;
-	return n;
-}
-
-struct rb_node *rb_next(const struct rb_node *node)
-{
-	struct rb_node *parent;
-
-	if (RB_EMPTY_NODE(node))
-		return NULL;
-
-	/*
-	 * If we have a right-hand child, go down and then left as far
-	 * as we can.
-	 */
-	if (node->rb_right) {
-		node = node->rb_right; 
-		while (node->rb_left)
-			node=node->rb_left;
-		return (struct rb_node *)node;
-	}
-
-	/*
-	 * No right-hand children. Everything down and left is smaller than us,
-	 * so any 'next' node must be in the general direction of our parent.
-	 * Go up the tree; any time the ancestor is a right-hand child of its
-	 * parent, keep going up. First time it's a left-hand child of its
-	 * parent, said parent is our 'next' node.
-	 */
-	while ((parent = rb_parent(node)) && node == parent->rb_right)
-		node = parent;
-
-	return parent;
-}
diff --git a/rbtree.h b/rbtree.h
deleted file mode 100644
index c6cfe4a..0000000
--- a/rbtree.h
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
-  Red Black Trees
-  (C) 1999  Andrea Arcangeli <andrea@xxxxxxx>
-  
-  This program is free software; you can redistribute it and/or modify
-  it under the terms of the GNU General Public License as published by
-  the Free Software Foundation; either version 2 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU General Public License for more details.
-
-  You should have received a copy of the GNU General Public License
-  along with this program; if not, write to the Free Software
-  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-  linux/include/linux/rbtree.h
-
-  To use rbtrees you'll have to implement your own insert and search cores.
-  This will avoid us to use callbacks and to drop drammatically performances.
-  I know it's not the cleaner way,  but in C (not in C++) to get
-  performances and genericity...
-
-  Some example of insert and search follows here. The search is a plain
-  normal search over an ordered tree. The insert instead must be implemented
-  int two steps: as first thing the code must insert the element in
-  order as a red leaf in the tree, then the support library function
-  rb_insert_color() must be called. Such function will do the
-  not trivial work to rebalance the rbtree if necessary.
-
------------------------------------------------------------------------
-static inline struct page * rb_search_page_cache(struct inode * inode,
-						 unsigned long offset)
-{
-	struct rb_node * n = inode->i_rb_page_cache.rb_node;
-	struct page * page;
-
-	while (n)
-	{
-		page = rb_entry(n, struct page, rb_page_cache);
-
-		if (offset < page->offset)
-			n = n->rb_left;
-		else if (offset > page->offset)
-			n = n->rb_right;
-		else
-			return page;
-	}
-	return NULL;
-}
-
-static inline struct page * __rb_insert_page_cache(struct inode * inode,
-						   unsigned long offset,
-						   struct rb_node * node)
-{
-	struct rb_node ** p = &inode->i_rb_page_cache.rb_node;
-	struct rb_node * parent = NULL;
-	struct page * page;
-
-	while (*p)
-	{
-		parent = *p;
-		page = rb_entry(parent, struct page, rb_page_cache);
-
-		if (offset < page->offset)
-			p = &(*p)->rb_left;
-		else if (offset > page->offset)
-			p = &(*p)->rb_right;
-		else
-			return page;
-	}
-
-	rb_link_node(node, parent, p);
-
-	return NULL;
-}
-
-static inline struct page * rb_insert_page_cache(struct inode * inode,
-						 unsigned long offset,
-						 struct rb_node * node)
-{
-	struct page * ret;
-	if ((ret = __rb_insert_page_cache(inode, offset, node)))
-		goto out;
-	rb_insert_color(node, &inode->i_rb_page_cache);
- out:
-	return ret;
-}
------------------------------------------------------------------------
-*/
-
-#ifndef	_LINUX_RBTREE_H
-#define	_LINUX_RBTREE_H
-
-#include <stdlib.h>
-#include <inttypes.h>
-
-struct rb_node
-{
-	intptr_t rb_parent_color;
-#define	RB_RED		0
-#define	RB_BLACK	1
-	struct rb_node *rb_right;
-	struct rb_node *rb_left;
-} __attribute__((aligned(sizeof(long))));
-    /* The alignment might seem pointless, but allegedly CRIS needs it */
-
-struct rb_root
-{
-	struct rb_node *rb_node;
-};
-
-
-#define rb_parent(r)   ((struct rb_node *)((r)->rb_parent_color & ~3))
-#define rb_color(r)   ((r)->rb_parent_color & 1)
-#define rb_is_red(r)   (!rb_color(r))
-#define rb_is_black(r) rb_color(r)
-#define rb_set_red(r)  do { (r)->rb_parent_color &= ~1; } while (0)
-#define rb_set_black(r)  do { (r)->rb_parent_color |= 1; } while (0)
-
-static inline void rb_set_parent(struct rb_node *rb, struct rb_node *p)
-{
-	rb->rb_parent_color = (rb->rb_parent_color & 3) | (uintptr_t)p;
-}
-static inline void rb_set_color(struct rb_node *rb, int color)
-{
-	rb->rb_parent_color = (rb->rb_parent_color & ~1) | color;
-}
-
-#define RB_ROOT	(struct rb_root) { NULL, }
-#define	rb_entry(ptr, type, member) container_of(ptr, type, member)
-
-#define RB_EMPTY_ROOT(root)	((root)->rb_node == NULL)
-#define RB_EMPTY_NODE(node)	(rb_parent(node) == node)
-#define RB_CLEAR_NODE(node)	(rb_set_parent(node, node))
-
-extern void rb_insert_color(struct rb_node *, struct rb_root *);
-extern void rb_erase(struct rb_node *, struct rb_root *);
-
-/* Find logical next and previous nodes in a tree */
-extern struct rb_node *rb_first(struct rb_root *);
-extern struct rb_node *rb_next(const struct rb_node *);
-
-static inline void rb_link_node(struct rb_node * node, struct rb_node * parent,
-				struct rb_node ** rb_link)
-{
-	node->rb_parent_color = (uintptr_t)parent;
-	node->rb_left = node->rb_right = NULL;
-
-	*rb_link = node;
-}
-
-#endif	/* _LINUX_RBTREE_H */
diff --git a/stat.h b/stat.h
index f23abfa..fd4eb1a 100644
--- a/stat.h
+++ b/stat.h
@@ -1,6 +1,8 @@
 #ifndef FIO_STAT_H
 #define FIO_STAT_H
 
+#include "iolog.h"
+
 struct group_run_stats {
 	uint64_t max_run[DDIR_RWDIR_CNT], min_run[DDIR_RWDIR_CNT];
 	uint64_t max_bw[DDIR_RWDIR_CNT], min_bw[DDIR_RWDIR_CNT];
diff --git a/thread_options.h b/thread_options.h
new file mode 100644
index 0000000..7004803
--- /dev/null
+++ b/thread_options.h
@@ -0,0 +1,264 @@
+#ifndef FIO_THREAD_OPTIONS_H
+#define FIO_THREAD_OPTIONS_H
+
+#include "arch/arch.h"
+#include "os/os.h"
+#include "stat.h"
+#include "gettime.h"
+#include "lib/ieee754.h"
+
+/*
+ * What type of allocation to use for io buffers
+ */
+enum fio_memtype {
+	MEM_MALLOC = 0,	/* ordinary malloc */
+	MEM_SHM,	/* use shared memory segments */
+	MEM_SHMHUGE,	/* use shared memory segments with huge pages */
+	MEM_MMAP,	/* use anonynomous mmap */
+	MEM_MMAPHUGE,	/* memory mapped huge file */
+};
+
+/*
+ * What type of errors to continue on when continue_on_error is used
+ */
+enum error_type_bit {
+	ERROR_TYPE_READ_BIT = 0,
+	ERROR_TYPE_WRITE_BIT = 1,
+	ERROR_TYPE_VERIFY_BIT = 2,
+	ERROR_TYPE_CNT = 3,
+};
+
+#define ERROR_STR_MAX	128
+
+enum error_type {
+        ERROR_TYPE_NONE = 0,
+        ERROR_TYPE_READ = 1 << ERROR_TYPE_READ_BIT,
+        ERROR_TYPE_WRITE = 1 << ERROR_TYPE_WRITE_BIT,
+        ERROR_TYPE_VERIFY = 1 << ERROR_TYPE_VERIFY_BIT,
+        ERROR_TYPE_ANY = 0xffff,
+};
+
+#define BSSPLIT_MAX	64
+
+struct bssplit {
+	uint32_t bs;
+	uint32_t perc;
+};
+
+struct thread_options {
+	int pad;
+	char *description;
+	char *name;
+	char *directory;
+	char *filename;
+	char *filename_format;
+	char *opendir;
+	char *ioengine;
+	enum td_ddir td_ddir;
+	unsigned int rw_seq;
+	unsigned int kb_base;
+	unsigned int unit_base;
+	unsigned int ddir_seq_nr;
+	long ddir_seq_add;
+	unsigned int iodepth;
+	unsigned int iodepth_low;
+	unsigned int iodepth_batch;
+	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;
+	unsigned long long start_offset;
+
+	unsigned int bs[DDIR_RWDIR_CNT];
+	unsigned int ba[DDIR_RWDIR_CNT];
+	unsigned int min_bs[DDIR_RWDIR_CNT];
+	unsigned int max_bs[DDIR_RWDIR_CNT];
+	struct bssplit *bssplit[DDIR_RWDIR_CNT];
+	unsigned int bssplit_nr[DDIR_RWDIR_CNT];
+
+	int *ignore_error[ERROR_TYPE_CNT];
+	unsigned int ignore_error_nr[ERROR_TYPE_CNT];
+	unsigned int error_dump;
+
+	unsigned int nr_files;
+	unsigned int open_files;
+	enum file_lock_mode file_lock_mode;
+
+	unsigned int odirect;
+	unsigned int invalidate_cache;
+	unsigned int create_serialize;
+	unsigned int create_fsync;
+	unsigned int create_on_open;
+	unsigned int create_only;
+	unsigned int end_fsync;
+	unsigned int pre_read;
+	unsigned int sync_io;
+	unsigned int verify;
+	unsigned int do_verify;
+	unsigned int verifysort;
+	unsigned int verifysort_nr;
+	unsigned int verify_interval;
+	unsigned int verify_offset;
+	char verify_pattern[MAX_PATTERN_SIZE];
+	unsigned int verify_pattern_bytes;
+	unsigned int verify_fatal;
+	unsigned int verify_dump;
+	unsigned int verify_async;
+	unsigned long long verify_backlog;
+	unsigned int verify_batch;
+	unsigned int experimental_verify;
+	unsigned int use_thread;
+	unsigned int unlink;
+	unsigned int do_disk_util;
+	unsigned int override_sync;
+	unsigned int rand_repeatable;
+	unsigned int use_os_rand;
+	unsigned int write_lat_log;
+	unsigned int write_bw_log;
+	unsigned int write_iops_log;
+	unsigned int log_avg_msec;
+	unsigned int norandommap;
+	unsigned int softrandommap;
+	unsigned int bs_unaligned;
+	unsigned int fsync_on_close;
+
+	unsigned int random_distribution;
+	fio_fp64_t zipf_theta;
+	fio_fp64_t pareto_h;
+
+	unsigned int random_generator;
+
+	unsigned int hugepage_size;
+	unsigned int rw_min_bs;
+	unsigned int thinktime;
+	unsigned int thinktime_spin;
+	unsigned int thinktime_blocks;
+	unsigned int fsync_blocks;
+	unsigned int fdatasync_blocks;
+	unsigned int barrier_blocks;
+	unsigned long long start_delay;
+	unsigned long long timeout;
+	unsigned long long ramp_time;
+	unsigned int overwrite;
+	unsigned int bw_avg_time;
+	unsigned int iops_avg_time;
+	unsigned int loops;
+	unsigned long long zone_range;
+	unsigned long long zone_size;
+	unsigned long long zone_skip;
+	unsigned long long lockmem;
+	enum fio_memtype mem_type;
+	unsigned int mem_align;
+
+	unsigned int max_latency;
+
+	unsigned int stonewall;
+	unsigned int new_group;
+	unsigned int numjobs;
+	os_cpu_mask_t cpumask;
+	unsigned int cpumask_set;
+	os_cpu_mask_t verify_cpumask;
+	unsigned int verify_cpumask_set;
+#ifdef CONFIG_LIBNUMA
+	struct bitmask *numa_cpunodesmask;
+	unsigned int numa_cpumask_set;
+	unsigned short numa_mem_mode;
+	unsigned int numa_mem_prefer_node;
+	struct bitmask *numa_memnodesmask;
+	unsigned int numa_memmask_set;
+#endif
+	unsigned int iolog;
+	unsigned int rwmixcycle;
+	unsigned int rwmix[2];
+	unsigned int nice;
+	unsigned int ioprio;
+	unsigned int ioprio_class;
+	unsigned int file_service_type;
+	unsigned int group_reporting;
+	unsigned int fadvise_hint;
+	enum fio_fallocate_mode fallocate_mode;
+	unsigned int zero_buffers;
+	unsigned int refill_buffers;
+	unsigned int scramble_buffers;
+	unsigned int compress_percentage;
+	unsigned int compress_chunk;
+	unsigned int time_based;
+	unsigned int disable_lat;
+	unsigned int disable_clat;
+	unsigned int disable_slat;
+	unsigned int disable_bw;
+	unsigned int unified_rw_rep;
+	unsigned int gtod_reduce;
+	unsigned int gtod_cpu;
+	unsigned int gtod_offload;
+	enum fio_cs clocksource;
+	unsigned int no_stall;
+	unsigned int trim_percentage;
+	unsigned int trim_batch;
+	unsigned int trim_zero;
+	unsigned long long trim_backlog;
+	unsigned int clat_percentiles;
+	unsigned int percentile_precision;	/* digits after decimal for percentiles */
+	fio_fp64_t percentile_list[FIO_IO_U_LIST_MAX_LEN];
+
+	char *read_iolog_file;
+	char *write_iolog_file;
+	char *bw_log_file;
+	char *lat_log_file;
+	char *iops_log_file;
+	char *replay_redirect;
+
+	/*
+	 * Pre-run and post-run shell
+	 */
+	char *exec_prerun;
+	char *exec_postrun;
+
+	unsigned int rate[DDIR_RWDIR_CNT];
+	unsigned int ratemin[DDIR_RWDIR_CNT];
+	unsigned int ratecycle;
+	unsigned int rate_iops[DDIR_RWDIR_CNT];
+	unsigned int rate_iops_min[DDIR_RWDIR_CNT];
+
+	char *ioscheduler;
+
+	/*
+	 * CPU "io" cycle burner
+	 */
+	unsigned int cpuload;
+	unsigned int cpucycle;
+
+	/*
+	 * I/O Error handling
+	 */
+	enum error_type continue_on_error;
+
+	/*
+	 * Benchmark profile type
+	 */
+	char *profile;
+
+	/*
+	 * blkio cgroup support
+	 */
+	char *cgroup;
+	unsigned int cgroup_weight;
+	unsigned int cgroup_nodelete;
+
+	unsigned int uid;
+	unsigned int gid;
+
+	int flow_id;
+	int flow;
+	int flow_watermark;
+	unsigned int flow_sleep;
+
+	unsigned long long offset_increment;
+
+	unsigned int sync_file_range;
+};
+
+#endif
diff --git a/verify.c b/verify.c
index daa2f04..787cc37 100644
--- a/verify.c
+++ b/verify.c
@@ -1006,6 +1006,14 @@ int get_next_verify(struct thread_data *td, struct io_u *io_u)
 	return 1;
 }
 
+void fio_verify_init(struct thread_data *td)
+{
+	if (td->o.verify == VERIFY_CRC32C_INTEL ||
+	    td->o.verify == VERIFY_CRC32C) {
+		crc32c_intel_probe();
+	}
+}
+
 static void *verify_async_thread(void *data)
 {
 	struct thread_data *td = data;
diff --git a/verify.h b/verify.h
index 7c238c8..6a81e9b 100644
--- a/verify.h
+++ b/verify.h
@@ -75,6 +75,7 @@ extern int __must_check get_next_verify(struct thread_data *td, struct io_u *);
 extern int __must_check verify_io_u(struct thread_data *, struct io_u *);
 extern int verify_io_u_async(struct thread_data *, struct io_u *);
 extern void fill_pattern(struct thread_data *td, void *p, unsigned int len, struct io_u *io_u, unsigned long seed, int use_seed);
+extern void fio_verify_init(struct thread_data *td);
 
 /*
  * Async verify offload
--
To unsubscribe from this list: send the line "unsubscribe fio" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux Kernel]     [Linux SCSI]     [Linux IDE]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux SCSI]

  Powered by Linux