init: cleanup random inits
[fio.git] / init.c
diff --git a/init.c b/init.c
index c210ad257ce3125a1ecb62152fe26648a5d320bc..7166ea766d8a669bddf619dd105b3b8ce6b3f58d 100644 (file)
--- a/init.c
+++ b/init.c
@@ -26,8 +26,8 @@
 #include "idletime.h"
 #include "filelock.h"
 
-#include "lib/getopt.h"
-#include "lib/strcasestr.h"
+#include "oslib/getopt.h"
+#include "oslib/strcasestr.h"
 
 #include "crc/test.h"
 
@@ -47,8 +47,8 @@ static char **job_sections;
 static int nr_job_sections;
 
 int exitall_on_terminate = 0;
+int exitall_on_terminate_error = 0;
 int output_format = FIO_OUTPUT_NORMAL;
-int append_terse_output = 0;
 int eta_print = FIO_ETA_AUTO;
 int eta_new_line = 0;
 FILE *f_out = NULL;
@@ -69,6 +69,8 @@ long long trigger_timeout = 0;
 char *trigger_cmd = NULL;
 char *trigger_remote_cmd = NULL;
 
+char *aux_path = NULL;
+
 static int prev_group_jobs;
 
 unsigned long fio_debug = 0;
@@ -266,6 +268,11 @@ static struct option l_opts[FIO_NR_OPTIONS] = {
                .has_arg        = required_argument,
                .val            = 'J',
        },
+       {
+               .name           = (char *) "aux-path",
+               .has_arg        = required_argument,
+               .val            = 'K',
+       },
        {
                .name           = NULL,
        },
@@ -378,6 +385,68 @@ static void set_cmd_options(struct thread_data *td)
                o->timeout = def_timeout;
 }
 
+static void dump_print_option(struct print_option *p)
+{
+       const char *delim;
+
+       if (!strcmp("description", p->name))
+               delim = "\"";
+       else
+               delim = "";
+
+       log_info("--%s%s", p->name, p->value ? "" : " ");
+       if (p->value)
+               log_info("=%s%s%s ", delim, p->value, delim);
+}
+
+static void dump_opt_list(struct thread_data *td)
+{
+       struct flist_head *entry;
+       struct print_option *p;
+
+       if (flist_empty(&td->opt_list))
+               return;
+
+       flist_for_each(entry, &td->opt_list) {
+               p = flist_entry(entry, struct print_option, list);
+               dump_print_option(p);
+       }
+}
+
+static void fio_dump_options_free(struct thread_data *td)
+{
+       while (!flist_empty(&td->opt_list)) {
+               struct print_option *p;
+
+               p = flist_first_entry(&td->opt_list, struct print_option, list);
+               flist_del_init(&p->list);
+               free(p->name);
+               free(p->value);
+               free(p);
+       }
+}
+
+static void copy_opt_list(struct thread_data *dst, struct thread_data *src)
+{
+       struct flist_head *entry;
+
+       if (flist_empty(&src->opt_list))
+               return;
+
+       flist_for_each(entry, &src->opt_list) {
+               struct print_option *srcp, *dstp;
+
+               srcp = flist_entry(entry, struct print_option, list);
+               dstp = malloc(sizeof(*dstp));
+               dstp->name = strdup(srcp->name);
+               if (srcp->value)
+                       dstp->value = strdup(srcp->value);
+               else
+                       dstp->value = NULL;
+               flist_add_tail(&dstp->list, &dst->opt_list);
+       }
+}
+
 /*
  * Return a free job structure.
  */
@@ -403,6 +472,10 @@ static struct thread_data *get_new_job(int global, struct thread_data *parent,
        td = &threads[thread_number++];
        *td = *parent;
 
+       INIT_FLIST_HEAD(&td->opt_list);
+       if (parent != &def_thread)
+               copy_opt_list(td, parent);
+
        td->io_ops = NULL;
        if (!preserve_eo)
                td->eo = NULL;
@@ -420,7 +493,7 @@ static struct thread_data *get_new_job(int global, struct thread_data *parent,
        if (jobname)
                td->o.name = strdup(jobname);
 
-       if (!parent->o.group_reporting)
+       if (!parent->o.group_reporting || parent == &def_thread)
                stat_number++;
 
        set_cmd_options(td);
@@ -439,6 +512,7 @@ static void put_job(struct thread_data *td)
                log_info("fio: %s\n", td->verror);
 
        fio_options_free(td);
+       fio_dump_options_free(td);
        if (td->io_ops)
                free_ioengine(td);
 
@@ -458,14 +532,16 @@ static int __setup_rate(struct thread_data *td, enum fio_ddir ddir)
        if (td->o.rate[ddir])
                td->rate_bps[ddir] = td->o.rate[ddir];
        else
-               td->rate_bps[ddir] = td->o.rate_iops[ddir] * bs;
+               td->rate_bps[ddir] = (uint64_t) td->o.rate_iops[ddir] * bs;
 
        if (!td->rate_bps[ddir]) {
                log_err("rate lower than supported\n");
                return -1;
        }
 
-       td->rate_pending_usleep[ddir] = 0;
+       td->rate_next_io_time[ddir] = 0;
+       td->rate_io_issue_bytes[ddir] = 0;
+       td->last_usec = 0;
        return 0;
 }
 
@@ -496,12 +572,14 @@ static int fixed_block_size(struct thread_options *o)
 static unsigned long long get_rand_start_delay(struct thread_data *td)
 {
        unsigned long long delayrange;
+       uint64_t frand_max;
        unsigned long r;
 
        delayrange = td->o.start_delay_high - td->o.start_delay;
 
+       frand_max = rand_max(&td->delay_state);
        r = __rand(&td->delay_state);
-       delayrange = (unsigned long long) ((double) delayrange * (r / (FRAND_MAX + 1.0)));
+       delayrange = (unsigned long long) ((double) delayrange * (r / (frand_max + 1.0)));
 
        delayrange += td->o.start_delay;
        return delayrange;
@@ -620,6 +698,13 @@ static int fixup_options(struct thread_data *td)
        if (o->iodepth_batch > o->iodepth || !o->iodepth_batch)
                o->iodepth_batch = o->iodepth;
 
+       /*
+        * If max batch complete number isn't set or set incorrectly,
+        * default to the same as iodepth_batch_complete_min
+        */
+       if (o->iodepth_batch_complete_min > o->iodepth_batch_complete_max)
+               o->iodepth_batch_complete_max = o->iodepth_batch_complete_min;
+
        if (o->nr_files > td->files_index)
                o->nr_files = td->files_index;
 
@@ -633,12 +718,12 @@ static int fixup_options(struct thread_data *td)
                log_err("fio: rate and rate_iops are mutually exclusive\n");
                ret = 1;
        }
-       if ((o->rate[DDIR_READ] < o->ratemin[DDIR_READ]) ||
-           (o->rate[DDIR_WRITE] < o->ratemin[DDIR_WRITE]) ||
-           (o->rate[DDIR_TRIM] < o->ratemin[DDIR_TRIM]) ||
-           (o->rate_iops[DDIR_READ] < o->rate_iops_min[DDIR_READ]) ||
-           (o->rate_iops[DDIR_WRITE] < o->rate_iops_min[DDIR_WRITE]) ||
-           (o->rate_iops[DDIR_TRIM] < o->rate_iops_min[DDIR_TRIM])) {
+       if ((o->rate[DDIR_READ] && (o->rate[DDIR_READ] < o->ratemin[DDIR_READ])) ||
+           (o->rate[DDIR_WRITE] && (o->rate[DDIR_WRITE] < o->ratemin[DDIR_WRITE])) ||
+           (o->rate[DDIR_TRIM] && (o->rate[DDIR_TRIM] < o->ratemin[DDIR_TRIM])) ||
+           (o->rate_iops[DDIR_READ] && (o->rate_iops[DDIR_READ] < o->rate_iops_min[DDIR_READ])) ||
+           (o->rate_iops[DDIR_WRITE] && (o->rate_iops[DDIR_WRITE] < o->rate_iops_min[DDIR_WRITE])) ||
+           (o->rate_iops[DDIR_TRIM] && (o->rate_iops[DDIR_TRIM] < o->rate_iops_min[DDIR_TRIM]))) {
                log_err("fio: minimum rate exceeds rate\n");
                ret = 1;
        }
@@ -660,7 +745,9 @@ static int fixup_options(struct thread_data *td)
                        ret = warnings_fatal;
                }
 
-               o->refill_buffers = 1;
+               if (!fio_option_is_set(o, refill_buffers))
+                       o->refill_buffers = 1;
+
                if (o->max_bs[DDIR_WRITE] != o->min_bs[DDIR_WRITE] &&
                    !o->verify_interval)
                        o->verify_interval = o->min_bs[DDIR_WRITE];
@@ -718,11 +805,16 @@ static int fixup_options(struct thread_data *td)
 
        /*
         * For fully compressible data, just zero them at init time.
-        * It's faster than repeatedly filling it.
+        * It's faster than repeatedly filling it. For non-zero
+        * compression, we should have refill_buffers set. Set it, unless
+        * the job file already changed it.
         */
-       if (td->o.compress_percentage == 100) {
-               td->o.zero_buffers = 1;
-               td->o.compress_percentage = 0;
+       if (o->compress_percentage) {
+               if (o->compress_percentage == 100) {
+                       o->zero_buffers = 1;
+                       o->compress_percentage = 0;
+               } else if (!fio_option_is_set(o, refill_buffers))
+                       o->refill_buffers = 1;
        }
 
        /*
@@ -749,7 +841,7 @@ static int fixup_options(struct thread_data *td)
        /*
         * If randseed is set, that overrides randrepeat
         */
-       if (td->o.rand_seed)
+       if (fio_option_is_set(&td->o, rand_seed))
                td->o.rand_repeatable = 0;
 
        if ((td->io_ops->flags & FIO_NOEXTEND) && td->o.file_append) {
@@ -763,6 +855,17 @@ static int fixup_options(struct thread_data *td)
                fio_gtod_offload = 1;
        }
 
+       td->loops = o->loops;
+       if (!td->loops)
+               td->loops = 1;
+
+       if (td->o.block_error_hist && td->o.nr_files != 1) {
+               log_err("fio: block error histogram only available with "
+                       "with a single file per job, but %d files "
+                       "provided\n", td->o.nr_files);
+               ret = 1;
+       }
+
        return ret;
 }
 
@@ -816,18 +919,42 @@ static int exists_and_not_file(const char *filename)
        return 1;
 }
 
-static void td_fill_rand_seeds_internal(struct thread_data *td)
+static void init_rand_file_service(struct thread_data *td)
 {
-       init_rand_seed(&td->bsrange_state, td->rand_seeds[FIO_RAND_BS_OFF]);
-       init_rand_seed(&td->verify_state, td->rand_seeds[FIO_RAND_VER_OFF]);
-       init_rand_seed(&td->rwmix_state, td->rand_seeds[FIO_RAND_MIX_OFF]);
+       unsigned long nranges = td->o.nr_files << FIO_FSERVICE_SHIFT;
+       const unsigned int seed = td->rand_seeds[FIO_RAND_FILE_OFF];
+
+       if (td->o.file_service_type == FIO_FSERVICE_ZIPF) {
+               zipf_init(&td->next_file_zipf, nranges, td->zipf_theta, seed);
+               zipf_disable_hash(&td->next_file_zipf);
+       } else if (td->o.file_service_type == FIO_FSERVICE_PARETO) {
+               pareto_init(&td->next_file_zipf, nranges, td->pareto_h, seed);
+               zipf_disable_hash(&td->next_file_zipf);
+       } else if (td->o.file_service_type == FIO_FSERVICE_GAUSS) {
+               gauss_init(&td->next_file_gauss, nranges, td->gauss_dev, seed);
+               gauss_disable_hash(&td->next_file_gauss);
+       }
+}
+
+static void td_fill_rand_seeds_internal(struct thread_data *td, bool use64)
+{
+       int i;
+
+       init_rand_seed(&td->bsrange_state, td->rand_seeds[FIO_RAND_BS_OFF], use64);
+       init_rand_seed(&td->verify_state, td->rand_seeds[FIO_RAND_VER_OFF], use64);
+       init_rand_seed(&td->rwmix_state, td->rand_seeds[FIO_RAND_MIX_OFF], false);
 
        if (td->o.file_service_type == FIO_FSERVICE_RANDOM)
-               init_rand_seed(&td->next_file_state, td->rand_seeds[FIO_RAND_FILE_OFF]);
+               init_rand_seed(&td->next_file_state, td->rand_seeds[FIO_RAND_FILE_OFF], use64);
+       else if (td->o.file_service_type & __FIO_FSERVICE_NONUNIFORM)
+               init_rand_file_service(td);
 
-       init_rand_seed(&td->file_size_state, td->rand_seeds[FIO_RAND_FILE_SIZE_OFF]);
-       init_rand_seed(&td->trim_state, td->rand_seeds[FIO_RAND_TRIM_OFF]);
-       init_rand_seed(&td->delay_state, td->rand_seeds[FIO_RAND_START_DELAY]);
+       init_rand_seed(&td->file_size_state, td->rand_seeds[FIO_RAND_FILE_SIZE_OFF], use64);
+       init_rand_seed(&td->trim_state, td->rand_seeds[FIO_RAND_TRIM_OFF], use64);
+       init_rand_seed(&td->delay_state, td->rand_seeds[FIO_RAND_START_DELAY], use64);
+       init_rand_seed(&td->poisson_state, td->rand_seeds[FIO_RAND_POISSON_OFF], 0);
+       init_rand_seed(&td->dedupe_state, td->rand_seeds[FIO_DEDUPE_OFF], false);
+       init_rand_seed(&td->zone_state, td->rand_seeds[FIO_RAND_ZONE_OFF], false);
 
        if (!td_random(td))
                return;
@@ -835,14 +962,19 @@ static void td_fill_rand_seeds_internal(struct thread_data *td)
        if (td->o.rand_repeatable)
                td->rand_seeds[FIO_RAND_BLOCK_OFF] = FIO_RANDSEED * td->thread_number;
 
-       init_rand_seed(&td->random_state, td->rand_seeds[FIO_RAND_BLOCK_OFF]);
-       init_rand_seed(&td->seq_rand_state[DDIR_READ], td->rand_seeds[FIO_RAND_SEQ_RAND_READ_OFF]);
-       init_rand_seed(&td->seq_rand_state[DDIR_WRITE], td->rand_seeds[FIO_RAND_SEQ_RAND_WRITE_OFF]);
-       init_rand_seed(&td->seq_rand_state[DDIR_TRIM], td->rand_seeds[FIO_RAND_SEQ_RAND_TRIM_OFF]);
+       init_rand_seed(&td->random_state, td->rand_seeds[FIO_RAND_BLOCK_OFF], use64);
+
+       for (i = 0; i < DDIR_RWDIR_CNT; i++) {
+               struct frand_state *s = &td->seq_rand_state[i];
+
+               init_rand_seed(s, td->rand_seeds[FIO_RAND_SEQ_RAND_READ_OFF], false);
+       }
 }
 
 void td_fill_rand_seeds(struct thread_data *td)
 {
+       bool use64;
+
        if (td->o.allrand_repeatable) {
                unsigned int i;
 
@@ -851,12 +983,15 @@ void td_fill_rand_seeds(struct thread_data *td)
                                + i;
        }
 
-       td_fill_rand_seeds_internal(td);
+       if (td->o.random_generator == FIO_RAND_GEN_TAUSWORTHE64)
+               use64 = 1;
+       else
+               use64 = 0;
 
-       init_rand_seed(&td->buf_state, td->rand_seeds[FIO_RAND_BUF_OFF]);
-       frand_copy(&td->buf_state_prev, &td->buf_state);
+       td_fill_rand_seeds_internal(td, use64);
 
-       init_rand_seed(&td->dedupe_state, td->rand_seeds[FIO_DEDUPE_OFF]);
+       init_rand_seed(&td->buf_state, td->rand_seeds[FIO_RAND_BUF_OFF], use64);
+       frand_copy(&td->buf_state_prev, &td->buf_state);
 }
 
 /*
@@ -945,6 +1080,9 @@ static void init_flags(struct thread_data *td)
                td->flags |= TD_F_SCRAMBLE_BUFFERS;
        if (o->verify != VERIFY_NONE)
                td->flags |= TD_F_VER_NONE;
+
+       if (o->verify_async || o->io_submit_mode == IO_MODE_OFFLOAD)
+               td->flags |= TD_F_NEED_LOCK;
 }
 
 static int setup_random_seeds(struct thread_data *td)
@@ -952,19 +1090,15 @@ static int setup_random_seeds(struct thread_data *td)
        unsigned long seed;
        unsigned int i;
 
-       if (!td->o.rand_repeatable && !td->o.rand_seed)
+       if (!td->o.rand_repeatable && !fio_option_is_set(&td->o, rand_seed))
                return init_random_state(td, td->rand_seeds, sizeof(td->rand_seeds));
 
-       if (!td->o.rand_seed)
-               seed = 0x89;
-       else
-               seed = td->o.rand_seed;
-
+       seed = td->o.rand_seed;
        for (i = 0; i < 4; i++)
                seed *= 0x9e370001UL;
 
        for (i = 0; i < FIO_RAND_NR_OFFS; i++) {
-               td->rand_seeds[i] = seed;
+               td->rand_seeds[i] = seed * td->thread_number + i;
                seed *= 0x9e370001UL;
        }
 
@@ -1097,6 +1231,59 @@ int parse_dryrun(void)
        return dump_cmdline || parse_only;
 }
 
+static void gen_log_name(char *name, size_t size, const char *logtype,
+                        const char *logname, unsigned int num,
+                        const char *suf, int per_job)
+{
+       if (per_job)
+               snprintf(name, size, "%s_%s.%d.%s", logname, logtype, num, suf);
+       else
+               snprintf(name, size, "%s_%s.%s", logname, logtype, suf);
+}
+
+static int check_waitees(char *waitee)
+{
+       struct thread_data *td;
+       int i, ret = 0;
+
+       for_each_td(td, i) {
+               if (td->subjob_number)
+                       continue;
+
+               ret += !strcmp(td->o.name, waitee);
+       }
+
+       return ret;
+}
+
+static bool wait_for_ok(const char *jobname, struct thread_options *o)
+{
+       int nw;
+
+       if (!o->wait_for)
+               return true;
+
+       if (!strcmp(jobname, o->wait_for)) {
+               log_err("%s: a job cannot wait for itself (wait_for=%s).\n",
+                               jobname, o->wait_for);
+               return false;
+       }
+
+       if (!(nw = check_waitees(o->wait_for))) {
+               log_err("%s: waitee job %s unknown.\n", jobname, o->wait_for);
+               return false;
+       }
+
+       if (nw > 1) {
+               log_err("%s: multiple waitees %s found,\n"
+                       "please avoid duplicates when using wait_for option.\n",
+                               jobname, o->wait_for);
+               return false;
+       }
+
+       return true;
+}
+
 /*
  * Adds a job to the list of things todo. Sanitizes the various options
  * to make sure we don't have conflicts, and initializes various
@@ -1153,6 +1340,12 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num,
        if (fixup_options(td))
                goto err;
 
+       /*
+        * Belongs to fixup_options, but o->name is not necessarily set as yet
+        */
+       if (!wait_for_ok(jobname, o))
+               goto err;
+
        flow_init_job(td);
 
        /*
@@ -1186,6 +1379,10 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num,
        if ((o->stonewall || o->new_group) && prev_group_jobs) {
                prev_group_jobs = 0;
                groupid++;
+               if (groupid == INT_MAX) {
+                       log_err("fio: too many groups defined\n");
+                       goto err;
+               }
        }
 
        td->groupid = groupid;
@@ -1215,14 +1412,16 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num,
                else
                        suf = "log";
 
-               snprintf(logname, sizeof(logname), "%s_lat.%d.%s",
-                               o->lat_log_file, td->thread_number, suf);
+               gen_log_name(logname, sizeof(logname), "lat", o->lat_log_file,
+                               td->thread_number, suf, o->per_job_logs);
                setup_log(&td->lat_log, &p, logname);
-               snprintf(logname, sizeof(logname), "%s_slat.%d.%s",
-                               o->lat_log_file, td->thread_number, suf);
+
+               gen_log_name(logname, sizeof(logname), "slat", o->lat_log_file,
+                               td->thread_number, suf, o->per_job_logs);
                setup_log(&td->slat_log, &p, logname);
-               snprintf(logname, sizeof(logname), "%s_clat.%d.%s",
-                               o->lat_log_file, td->thread_number, suf);
+
+               gen_log_name(logname, sizeof(logname), "clat", o->lat_log_file,
+                               td->thread_number, suf, o->per_job_logs);
                setup_log(&td->clat_log, &p, logname);
        }
        if (o->bw_log_file) {
@@ -1236,13 +1435,18 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num,
                };
                const char *suf;
 
+               if (fio_option_is_set(o, bw_avg_time))
+                       p.avg_msec = min(o->log_avg_msec, o->bw_avg_time);
+               else
+                       o->bw_avg_time = p.avg_msec;
+
                if (p.log_gz_store)
                        suf = "log.fz";
                else
                        suf = "log";
 
-               snprintf(logname, sizeof(logname), "%s_bw.%d.%s",
-                               o->bw_log_file, td->thread_number, suf);
+               gen_log_name(logname, sizeof(logname), "bw", o->bw_log_file,
+                               td->thread_number, suf, o->per_job_logs);
                setup_log(&td->bw_log, &p, logname);
        }
        if (o->iops_log_file) {
@@ -1256,20 +1460,25 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num,
                };
                const char *suf;
 
+               if (fio_option_is_set(o, iops_avg_time))
+                       p.avg_msec = min(o->log_avg_msec, o->iops_avg_time);
+               else
+                       o->iops_avg_time = p.avg_msec;
+
                if (p.log_gz_store)
                        suf = "log.fz";
                else
                        suf = "log";
 
-               snprintf(logname, sizeof(logname), "%s_iops.%d.%s",
-                               o->iops_log_file, td->thread_number, suf);
+               gen_log_name(logname, sizeof(logname), "iops", o->iops_log_file,
+                               td->thread_number, suf, o->per_job_logs);
                setup_log(&td->iops_log, &p, logname);
        }
 
        if (!o->name)
                o->name = strdup(jobname);
 
-       if (output_format == FIO_OUTPUT_NORMAL) {
+       if (output_format & FIO_OUTPUT_NORMAL) {
                if (!job_add_num) {
                        if (is_backend && !recursed)
                                fio_server_send_add_job(td);
@@ -1385,9 +1594,9 @@ void add_job_opts(const char **o, int client_type)
                        td = get_new_job(0, td_parent, 0, jobname);
                }
                if (in_global)
-                       fio_options_parse(td_parent, (char **) &o[i], 1, 0);
+                       fio_options_parse(td_parent, (char **) &o[i], 1);
                else
-                       fio_options_parse(td, (char **) &o[i], 1, 0);
+                       fio_options_parse(td, (char **) &o[i], 1);
                i++;
        }
 
@@ -1598,15 +1807,48 @@ int __parse_jobs_ini(struct thread_data *td,
                        strip_blank_end(p);
 
                        if (!strncmp(p, "include", strlen("include"))) {
-                               char *filename = p + strlen("include") + 1;
+                               char *filename = p + strlen("include") + 1,
+                                       *ts, *full_fn = NULL;
+
+                               /*
+                                * Allow for the include filename
+                                * specification to be relative.
+                                */
+                               if (access(filename, F_OK) &&
+                                   (ts = strrchr(file, '/'))) {
+                                       int len = ts - file +
+                                               strlen(filename) + 2;
+
+                                       if (!(full_fn = calloc(1, len))) {
+                                               ret = ENOMEM;
+                                               break;
+                                       }
+
+                                       strncpy(full_fn,
+                                               file, (ts - file) + 1);
+                                       strncpy(full_fn + (ts - file) + 1,
+                                               filename, strlen(filename));
+                                       full_fn[len - 1] = 0;
+                                       filename = full_fn;
+                               }
 
-                               if ((ret = __parse_jobs_ini(td, filename,
-                                               is_buf, stonewall_flag, type, 1,
-                                               name, &opts, &alloc_opts, &num_opts))) {
-                                       log_err("Error %d while parsing include file %s\n",
+                               ret = __parse_jobs_ini(td, filename, is_buf,
+                                                      stonewall_flag, type, 1,
+                                                      name, &opts,
+                                                      &alloc_opts, &num_opts);
+
+                               if (ret) {
+                                       log_err("Error %d while parsing "
+                                               "include file %s\n",
                                                ret, filename);
-                                       break;
                                }
+
+                               if (full_fn)
+                                       free(full_fn);
+
+                               if (ret)
+                                       break;
+
                                continue;
                        }
 
@@ -1627,10 +1869,13 @@ int __parse_jobs_ini(struct thread_data *td,
                        goto out;
                }
 
-               ret = fio_options_parse(td, opts, num_opts, dump_cmdline);
-               if (!ret)
+               ret = fio_options_parse(td, opts, num_opts);
+               if (!ret) {
+                       if (dump_cmdline)
+                               dump_opt_list(td);
+
                        ret = add_job(td, name, 0, 0, type);
-               else {
+               else {
                        log_err("fio: job %s dropped\n", name);
                        put_job(td);
                }
@@ -1668,6 +1913,7 @@ int parse_jobs_ini(char *file, int is_buf, int stonewall_flag, int type)
 static int fill_def_thread(void)
 {
        memset(&def_thread, 0, sizeof(def_thread));
+       INIT_FLIST_HEAD(&def_thread.opt_list);
 
        fio_getaffinity(getpid(), &def_thread.o.cpumask);
        def_thread.o.error_dump = 1;
@@ -1679,19 +1925,49 @@ static int fill_def_thread(void)
        return 0;
 }
 
+static void show_debug_categories(void)
+{
+#ifdef FIO_INC_DEBUG
+       struct debug_level *dl = &debug_levels[0];
+       int curlen, first = 1;
+
+       curlen = 0;
+       while (dl->name) {
+               int has_next = (dl + 1)->name != NULL;
+
+               if (first || curlen + strlen(dl->name) >= 80) {
+                       if (!first) {
+                               printf("\n");
+                               curlen = 0;
+                       }
+                       curlen += printf("\t\t\t%s", dl->name);
+                       curlen += 3 * (8 - 1);
+                       if (has_next)
+                               curlen += printf(",");
+               } else {
+                       curlen += printf("%s", dl->name);
+                       if (has_next)
+                               curlen += printf(",");
+               }
+               dl++;
+               first = 0;
+       }
+       printf("\n");
+#endif
+}
+
 static void usage(const char *name)
 {
        printf("%s\n", fio_version_string);
        printf("%s [options] [job options] <job file(s)>\n", name);
-       printf("  --debug=options\tEnable debug logging. May be one/more of:\n"
-               "\t\t\tprocess,file,io,mem,blktrace,verify,random,parse,\n"
-               "\t\t\tdiskutil,job,mutex,profile,time,net,rate,compress\n");
+       printf("  --debug=options\tEnable debug logging. May be one/more of:\n");
+       show_debug_categories();
        printf("  --parse-only\t\tParse options only, don't start any IO\n");
        printf("  --output\t\tWrite output to file\n");
        printf("  --runtime\t\tRuntime in seconds\n");
        printf("  --bandwidth-log\tGenerate per-job bandwidth logs\n");
        printf("  --minimal\t\tMinimal (terse) output\n");
-       printf("  --output-format=x\tOutput format (terse,json,normal)\n");
+       printf("  --output-format=x\tOutput format (terse,json,json+,normal)\n");
        printf("  --terse-version=x\tSet terse version output format to 'x'\n");
        printf("  --version\t\tPrint version info and exit\n");
        printf("  --help\t\tPrint this page\n");
@@ -1731,6 +2007,7 @@ static void usage(const char *name)
        printf("  --trigger-timeout=t\tExecute trigger af this time\n");
        printf("  --trigger=cmd\t\tSet this command as local trigger\n");
        printf("  --trigger-remote=cmd\tSet this command as remote trigger\n");
+       printf("  --aux-path=path\tUse this path for fio state generated files\n");
        printf("\nFio was written by Jens Axboe <jens.axboe@oracle.com>");
        printf("\n                   Jens Axboe <jaxboe@fusionio.com>");
        printf("\n                   Jens Axboe <axboe@fb.com>\n");
@@ -1812,6 +2089,9 @@ static int set_debug(const char *string)
        char *opt;
        int i;
 
+       if (!string)
+               return 0;
+
        if (!strcmp(string, "?") || !strcmp(string, "help")) {
                log_info("fio: dumping debug options:");
                for (i = 0; debug_levels[i].name; i++) {
@@ -1928,10 +2208,41 @@ static void show_closest_option(const char *name)
                i++;
        }
 
-       if (best_option != -1)
+       if (best_option != -1 && string_distance_ok(name, best_distance))
                log_err("Did you mean %s?\n", l_opts[best_option].name);
 }
 
+static int parse_output_format(const char *optarg)
+{
+       char *p, *orig, *opt;
+       int ret = 0;
+
+       p = orig = strdup(optarg);
+
+       output_format = 0;
+
+       while ((opt = strsep(&p, ",")) != NULL) {
+               if (!strcmp(opt, "minimal") ||
+                   !strcmp(opt, "terse") ||
+                   !strcmp(opt, "csv"))
+                       output_format |= FIO_OUTPUT_TERSE;
+               else if (!strcmp(opt, "json"))
+                       output_format |= FIO_OUTPUT_JSON;
+               else if (!strcmp(opt, "json+"))
+                       output_format |= (FIO_OUTPUT_JSON | FIO_OUTPUT_JSON_PLUS);
+               else if (!strcmp(opt, "normal"))
+                       output_format |= FIO_OUTPUT_NORMAL;
+               else {
+                       log_err("fio: invalid output format %s\n", opt);
+                       ret = 1;
+                       break;
+               }
+       }
+
+       free(orig);
+       return ret;
+}
+
 int parse_cmd_line(int argc, char *argv[], int client_type)
 {
        struct thread_data *td = NULL;
@@ -1993,17 +2304,15 @@ int parse_cmd_line(int argc, char *argv[], int client_type)
                                do_exit++;
                                break;
                        }
-                       if (!strcmp(optarg, "minimal") ||
-                           !strcmp(optarg, "terse") ||
-                           !strcmp(optarg, "csv"))
-                               output_format = FIO_OUTPUT_TERSE;
-                       else if (!strcmp(optarg, "json"))
-                               output_format = FIO_OUTPUT_JSON;
-                       else
-                               output_format = FIO_OUTPUT_NORMAL;
+                       if (parse_output_format(optarg)) {
+                               log_err("fio: failed parsing output-format\n");
+                               exit_val = 1;
+                               do_exit++;
+                               break;
+                       }
                        break;
                case 'f':
-                       append_terse_output = 1;
+                       output_format |= FIO_OUTPUT_TERSE;
                        break;
                case 'h':
                        did_arg = 1;
@@ -2131,6 +2440,7 @@ int parse_cmd_line(int argc, char *argv[], int client_type)
                                                td = NULL;
                                        }
                                        do_exit++;
+                                       exit_val = 1;
                                        break;
                                }
                                fio_options_set_ioengine_opts(l_opts, td);
@@ -2149,6 +2459,7 @@ int parse_cmd_line(int argc, char *argv[], int client_type)
                                        td = NULL;
                                }
                                do_exit++;
+                               exit_val = 1;
                        }
 
                        if (!ret && !strcmp(opt, "ioengine")) {
@@ -2157,6 +2468,7 @@ int parse_cmd_line(int argc, char *argv[], int client_type)
                                        put_job(td);
                                        td = NULL;
                                        do_exit++;
+                                       exit_val = 1;
                                        break;
                                }
                                fio_options_set_ioengine_opts(l_opts, td);
@@ -2225,6 +2537,35 @@ int parse_cmd_line(int argc, char *argv[], int client_type)
                                exit_val = 1;
                                break;
                        }
+                       /* if --client parameter contains a pathname */
+                       if (0 == access(optarg, R_OK)) {
+                               /* file contains a list of host addrs or names */
+                               char hostaddr[PATH_MAX] = {0};
+                               char formatstr[8];
+                               FILE * hostf = fopen(optarg, "r");
+                               if (!hostf) {
+                                       log_err("fio: could not open client list file %s for read\n", optarg);
+                                       do_exit++;
+                                       exit_val = 1;
+                                       break;
+                               }
+                               sprintf(formatstr, "%%%ds", PATH_MAX - 1);
+                               /*
+                                * read at most PATH_MAX-1 chars from each
+                                * record in this file
+                                */
+                               while (fscanf(hostf, formatstr, hostaddr) == 1) {
+                                       /* expect EVERY host in file to be valid */
+                                       if (fio_client_add(&fio_client_ops, hostaddr, &cur_client)) {
+                                               log_err("fio: failed adding client %s from file %s\n", hostaddr, optarg);
+                                               do_exit++;
+                                               exit_val = 1;
+                                               break;
+                                       }
+                               }
+                               fclose(hostf);
+                               break; /* no possibility of job file for "this client only" */
+                       }
                        if (fio_client_add(&fio_client_ops, optarg, &cur_client)) {
                                log_err("fio: failed adding client %s\n", optarg);
                                do_exit++;
@@ -2240,14 +2581,14 @@ int parse_cmd_line(int argc, char *argv[], int client_type)
                                    !strncmp(argv[optind], "-", 1))
                                        break;
 
-                               if (fio_client_add_ini_file(cur_client, argv[optind], 0))
+                               if (fio_client_add_ini_file(cur_client, argv[optind], false))
                                        break;
                                optind++;
                        }
                        break;
                case 'R':
                        did_arg = 1;
-                       if (fio_client_add_ini_file(cur_client, optarg, 1)) {
+                       if (fio_client_add_ini_file(cur_client, optarg, true)) {
                                do_exit++;
                                exit_val = 1;
                        }
@@ -2289,6 +2630,11 @@ int parse_cmd_line(int argc, char *argv[], int client_type)
                                free(trigger_remote_cmd);
                        trigger_remote_cmd = strdup(optarg);
                        break;
+               case 'K':
+                       if (aux_path)
+                               free(aux_path);
+                       aux_path = strdup(optarg);
+                       break;
                case 'B':
                        if (check_str_time(optarg, &trigger_timeout, 1)) {
                                log_err("fio: failed parsing time %s\n", optarg);
@@ -2418,7 +2764,7 @@ int parse_options(int argc, char *argv[])
                return 0;
        }
 
-       if (output_format == FIO_OUTPUT_NORMAL)
+       if (output_format & FIO_OUTPUT_NORMAL)
                log_info("%s\n", fio_version_string);
 
        return 0;
@@ -2428,3 +2774,8 @@ void options_default_fill(struct thread_options *o)
 {
        memcpy(o, &def_thread.o, sizeof(*o));
 }
+
+struct thread_data *get_global_options(void)
+{
+       return &def_thread;
+}