t/nvmept_trim: increase transfer size for some tests
[fio.git] / init.c
diff --git a/init.c b/init.c
index 6ff7c68d72de1329ceb2ab8e84709dcd3519f1e2..ff3e9a90d551500b3880df95c2505e3a7df9e684 100644 (file)
--- a/init.c
+++ b/init.c
@@ -45,13 +45,12 @@ const char fio_version_string[] = FIO_VERSION;
 #define FIO_RANDSEED           (0xb1899bedUL)
 
 static char **ini_file;
-static int max_jobs = FIO_MAX_JOBS;
 static bool dump_cmdline;
 static bool parse_only;
 static bool merge_blktrace_only;
 
 static struct thread_data def_thread;
-struct thread_data *threads = NULL;
+struct thread_segment segments[REAL_MAX_SEG];
 static char **job_sections;
 static int nr_job_sections;
 
@@ -225,6 +224,13 @@ static struct option l_opts[FIO_NR_OPTIONS] = {
                .has_arg        = optional_argument,
                .val            = 'S',
        },
+#ifdef WIN32
+       {
+               .name           = (char *) "server-internal",
+               .has_arg        = required_argument,
+               .val            = 'N',
+       },
+#endif
        {       .name           = (char *) "daemonize",
                .has_arg        = required_argument,
                .val            = 'D',
@@ -301,25 +307,35 @@ static struct option l_opts[FIO_NR_OPTIONS] = {
 
 void free_threads_shm(void)
 {
-       if (threads) {
-               void *tp = threads;
+       int i;
+
+       for (i = 0; i < nr_segments; i++) {
+               struct thread_segment *seg = &segments[i];
+
+               if (seg->threads) {
+                       void *tp = seg->threads;
 #ifndef CONFIG_NO_SHM
-               struct shmid_ds sbuf;
+                       struct shmid_ds sbuf;
 
-               threads = NULL;
-               shmdt(tp);
-               shmctl(shm_id, IPC_RMID, &sbuf);
-               shm_id = -1;
+                       seg->threads = NULL;
+                       shmdt(tp);
+                       shmctl(seg->shm_id, IPC_RMID, &sbuf);
+                       seg->shm_id = -1;
 #else
-               threads = NULL;
-               free(tp);
+                       seg->threads = NULL;
+                       free(tp);
 #endif
+               }
        }
+
+       nr_segments = 0;
+       cur_segment = 0;
 }
 
 static void free_shm(void)
 {
-       if (threads) {
+#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+       if (nr_segments) {
                flow_exit();
                fio_debug_jobp = NULL;
                fio_warned = NULL;
@@ -335,73 +351,82 @@ static void free_shm(void)
        fio_filelock_exit();
        file_hash_exit();
        scleanup();
+#endif
 }
 
-/*
- * The thread area is shared between the main process and the job
- * threads/processes. So setup a shared memory segment that will hold
- * all the job info. We use the end of the region for keeping track of
- * open files across jobs, for file sharing.
- */
-static int setup_thread_area(void)
+static int add_thread_segment(void)
 {
+       struct thread_segment *seg = &segments[nr_segments];
+       size_t size = JOBS_PER_SEG * sizeof(struct thread_data);
        int i;
 
-       if (threads)
-               return 0;
-
-       /*
-        * 1024 is too much on some machines, scale max_jobs if
-        * we get a failure that looks like too large a shm segment
-        */
-       do {
-               size_t size = max_jobs * sizeof(struct thread_data);
+       if (nr_segments + 1 >= REAL_MAX_SEG) {
+               log_err("error: maximum number of jobs reached.\n");
+               return -1;
+       }
 
-               size += 2 * sizeof(unsigned int);
+       size += 2 * sizeof(unsigned int);
 
 #ifndef CONFIG_NO_SHM
-               shm_id = shmget(0, size, IPC_CREAT | 0600);
-               if (shm_id != -1)
-                       break;
-               if (errno != EINVAL && errno != ENOMEM && errno != ENOSPC) {
+       seg->shm_id = shmget(0, size, IPC_CREAT | 0600);
+       if (seg->shm_id == -1) {
+               if (errno != EINVAL && errno != ENOMEM && errno != ENOSPC)
                        perror("shmget");
-                       break;
-               }
+               return -1;
+       }
 #else
-               threads = malloc(size);
-               if (threads)
-                       break;
+       seg->threads = malloc(size);
+       if (!seg->threads)
+               return -1;
 #endif
 
-               max_jobs >>= 1;
-       } while (max_jobs);
-
 #ifndef CONFIG_NO_SHM
-       if (shm_id == -1)
-               return 1;
-
-       threads = shmat(shm_id, NULL, 0);
-       if (threads == (void *) -1) {
+       seg->threads = shmat(seg->shm_id, NULL, 0);
+       if (seg->threads == (void *) -1) {
                perror("shmat");
                return 1;
        }
        if (shm_attach_to_open_removed())
-               shmctl(shm_id, IPC_RMID, NULL);
+               shmctl(seg->shm_id, IPC_RMID, NULL);
 #endif
 
-       memset(threads, 0, max_jobs * sizeof(struct thread_data));
-       for (i = 0; i < max_jobs; i++)
-               DRD_IGNORE_VAR(threads[i]);
-       fio_debug_jobp = (unsigned int *)(threads + max_jobs);
+       nr_segments++;
+
+       memset(seg->threads, 0, JOBS_PER_SEG * sizeof(struct thread_data));
+       for (i = 0; i < JOBS_PER_SEG; i++)
+               DRD_IGNORE_VAR(seg->threads[i]);
+       seg->nr_threads = 0;
+
+       /* Not first segment, we're done */
+       if (nr_segments != 1) {
+               cur_segment++;
+               return 0;
+       }
+
+       fio_debug_jobp = (unsigned int *)(seg->threads + JOBS_PER_SEG);
        *fio_debug_jobp = -1;
        fio_warned = fio_debug_jobp + 1;
        *fio_warned = 0;
 
        flow_init();
-
        return 0;
 }
 
+/*
+ * The thread areas are shared between the main process and the job
+ * threads/processes, and is split into chunks of JOBS_PER_SEG. If the current
+ * segment has no more room, add a new chunk.
+ */
+static int expand_thread_area(void)
+{
+       struct thread_segment *seg = &segments[cur_segment];
+
+       if (nr_segments && seg->nr_threads < JOBS_PER_SEG)
+               return 0;
+
+       return add_thread_segment();
+}
+
 static void dump_print_option(struct print_option *p)
 {
        const char *delim;
@@ -430,19 +455,6 @@ static void dump_opt_list(struct thread_data *td)
        }
 }
 
-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;
@@ -470,21 +482,19 @@ static void copy_opt_list(struct thread_data *dst, struct thread_data *src)
 static struct thread_data *get_new_job(bool global, struct thread_data *parent,
                                       bool preserve_eo, const char *jobname)
 {
+       struct thread_segment *seg;
        struct thread_data *td;
 
        if (global)
                return &def_thread;
-       if (setup_thread_area()) {
+       if (expand_thread_area()) {
                log_err("error: failed to setup shm segment\n");
                return NULL;
        }
-       if (thread_number >= max_jobs) {
-               log_err("error: maximum number of jobs (%d) reached.\n",
-                               max_jobs);
-               return NULL;
-       }
 
-       td = &threads[thread_number++];
+       seg = &segments[cur_segment];
+       td = &seg->threads[seg->nr_threads++];
+       thread_number++;
        *td = *parent;
 
        INIT_FLIST_HEAD(&td->opt_list);
@@ -534,7 +544,8 @@ static void put_job(struct thread_data *td)
        if (td->o.name)
                free(td->o.name);
 
-       memset(&threads[td->thread_number - 1], 0, sizeof(*td));
+       memset(td, 0, sizeof(*td));
+       segments[cur_segment].nr_threads--;
        thread_number--;
 }
 
@@ -607,6 +618,19 @@ static int fixup_options(struct thread_data *td)
                ret |= 1;
        }
 
+       if (td_trimwrite(td) && o->num_range > 1) {
+               log_err("fio: trimwrite cannot be used with multiple"
+                       " ranges.\n");
+               ret |= 1;
+       }
+
+       if (td_trim(td) && o->num_range > 1 &&
+           !td_ioengine_flagged(td, FIO_MULTI_RANGE_TRIM)) {
+               log_err("fio: can't use multiple ranges with IO engine %s\n",
+                       td->io_ops->name);
+               ret |= 1;
+       }
+
 #ifndef CONFIG_PSHARED
        if (!o->use_thread) {
                log_info("fio: this platform does not support process shared"
@@ -629,6 +653,11 @@ static int fixup_options(struct thread_data *td)
                ret |= 1;
        }
 
+       if (o->zone_mode == ZONE_MODE_ZBD && !o->create_serialize) {
+               log_err("fio: --zonemode=zbd and --create_serialize=0 are not compatible.\n");
+               ret |= 1;
+       }
+
        if (o->zone_mode == ZONE_MODE_STRIDED && !o->zone_size) {
                log_err("fio: --zonesize must be specified when using --zonemode=strided.\n");
                ret |= 1;
@@ -900,12 +929,6 @@ static int fixup_options(struct thread_data *td)
                ret |= 1;
        }
 
-       /*
-        * O_ATOMIC implies O_DIRECT
-        */
-       if (o->oatomic)
-               o->odirect = 1;
-
        /*
         * If randseed is set, that overrides randrepeat
         */
@@ -941,12 +964,66 @@ static int fixup_options(struct thread_data *td)
        if (o->disable_slat)
                o->slat_percentiles = 0;
 
+       /* Do this only for the parent job */
+       if (!td->subjob_number) {
+               /*
+                * Fix these up to be nsec internally
+                */
+               for_each_rw_ddir(ddir)
+                       o->max_latency[ddir] *= 1000ULL;
+
+               o->latency_target *= 1000ULL;
+       }
+
        /*
-        * Fix these up to be nsec internally
+        * Dedupe working set verifications
         */
-       o->max_latency *= 1000ULL;
-       o->latency_target *= 1000ULL;
+       if (o->dedupe_percentage && o->dedupe_mode == DEDUPE_MODE_WORKING_SET) {
+               if (!fio_option_is_set(o, size)) {
+                       log_err("fio: pregenerated dedupe working set "
+                                       "requires size to be set\n");
+                       ret |= 1;
+               } else if (o->nr_files != 1) {
+                       log_err("fio: dedupe working set mode supported with "
+                                       "single file per job, but %d files "
+                                       "provided\n", o->nr_files);
+                       ret |= 1;
+               } else if (o->dedupe_working_set_percentage + o->dedupe_percentage > 100) {
+                       log_err("fio: impossible to reach expected dedupe percentage %u "
+                                       "since %u percentage of size is reserved to dedupe working set "
+                                       "(those are unique pages)\n",
+                                       o->dedupe_percentage, o->dedupe_working_set_percentage);
+                       ret |= 1;
+               }
+       }
+
+       for_each_td(td2) {
+               if (td->o.ss_check_interval != td2->o.ss_check_interval) {
+                       log_err("fio: conflicting ss_check_interval: %llu and %llu, must be globally equal\n",
+                                       td->o.ss_check_interval, td2->o.ss_check_interval);
+                       ret |= 1;
+               }
+       } end_for_each();
+       if (td->o.ss_dur && td->o.ss_check_interval / 1000L < 1000) {
+               log_err("fio: ss_check_interval must be at least 1s\n");
+               ret |= 1;
 
+       }
+       if (td->o.ss_dur && (td->o.ss_dur % td->o.ss_check_interval != 0 || td->o.ss_dur <= td->o.ss_check_interval)) {
+               log_err("fio: ss_duration %lluus must be multiple of ss_check_interval %lluus\n",
+                               td->o.ss_dur, td->o.ss_check_interval);
+               ret |= 1;
+       }
+
+       if (td->o.fdp) {
+               if (fio_option_is_set(&td->o, dp_type) &&
+                       (td->o.dp_type == FIO_DP_STREAMS || td->o.dp_type == FIO_DP_NONE)) {
+                       log_err("fio: fdp=1 is not compatible with dataplacement={streams, none}\n");
+                       ret |= 1;
+               } else {
+                       td->o.dp_type = FIO_DP_FDP;
+               }
+       }
        return ret;
 }
 
@@ -956,19 +1033,23 @@ static void init_rand_file_service(struct thread_data *td)
        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_init(&td->next_file_zipf, nranges, td->zipf_theta, td->random_center, 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);
+               pareto_init(&td->next_file_zipf, nranges, td->pareto_h, td->random_center, 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_init(&td->next_file_gauss, nranges, td->gauss_dev, td->random_center, seed);
                gauss_disable_hash(&td->next_file_gauss);
        }
 }
 
-void td_fill_verify_state_seed(struct thread_data *td)
+void td_fill_rand_seeds(struct thread_data *td)
 {
+       uint64_t read_seed = td->rand_seeds[FIO_RAND_BS_OFF];
+       uint64_t write_seed = td->rand_seeds[FIO_RAND_BS1_OFF];
+       uint64_t trim_seed = td->rand_seeds[FIO_RAND_BS2_OFF];
+       int i;
        bool use64;
 
        if (td->o.random_generator == FIO_RAND_GEN_TAUSWORTHE64)
@@ -976,17 +1057,6 @@ void td_fill_verify_state_seed(struct thread_data *td)
        else
                use64 = false;
 
-       init_rand_seed(&td->verify_state, td->rand_seeds[FIO_RAND_VER_OFF],
-               use64);
-}
-
-static void td_fill_rand_seeds_internal(struct thread_data *td, bool use64)
-{
-       uint64_t read_seed = td->rand_seeds[FIO_RAND_BS_OFF];
-       uint64_t write_seed = td->rand_seeds[FIO_RAND_BS1_OFF];
-       uint64_t trim_seed = td->rand_seeds[FIO_RAND_BS2_OFF];
-       int i;
-
        /*
         * trimwrite is special in that we need to generate the same
         * offsets to get the "write after trim" effect. If we are
@@ -1003,7 +1073,8 @@ static void td_fill_rand_seeds_internal(struct thread_data *td, bool use64)
        init_rand_seed(&td->bsrange_state[DDIR_WRITE], write_seed, use64);
        init_rand_seed(&td->bsrange_state[DDIR_TRIM], trim_seed, use64);
 
-       td_fill_verify_state_seed(td);
+       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)
@@ -1020,12 +1091,7 @@ static void td_fill_rand_seeds_internal(struct thread_data *td, bool use64)
        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);
        init_rand_seed(&td->prio_state, td->rand_seeds[FIO_RAND_PRIO_CMDS], false);
-
-       if (!td_random(td))
-               return;
-
-       if (td->o.rand_repeatable)
-               td->rand_seeds[FIO_RAND_BLOCK_OFF] = FIO_RANDSEED * td->thread_number;
+       init_rand_seed(&td->dedupe_working_set_index_state, td->rand_seeds[FIO_RAND_DEDUPE_WORKING_SET_IX], use64);
 
        init_rand_seed(&td->random_state, td->rand_seeds[FIO_RAND_BLOCK_OFF], use64);
 
@@ -1034,29 +1100,41 @@ static void td_fill_rand_seeds_internal(struct thread_data *td, bool use64)
 
                init_rand_seed(s, td->rand_seeds[FIO_RAND_SEQ_RAND_READ_OFF], false);
        }
+
+       init_rand_seed(&td->buf_state, td->rand_seeds[FIO_RAND_BUF_OFF], use64);
+       frand_copy(&td->buf_state_prev, &td->buf_state);
+
+       init_rand_seed(&td->fdp_state, td->rand_seeds[FIO_RAND_FDP_OFF], use64);
 }
 
-void td_fill_rand_seeds(struct thread_data *td)
+static int setup_random_seeds(struct thread_data *td)
 {
-       bool use64;
-
-       if (td->o.allrand_repeatable) {
-               unsigned int i;
+       uint64_t seed;
+       unsigned int i;
 
-               for (i = 0; i < FIO_RAND_NR_OFFS; i++)
-                       td->rand_seeds[i] = FIO_RANDSEED * td->thread_number
-                               + i;
+       if (!td->o.rand_repeatable && !fio_option_is_set(&td->o, rand_seed)) {
+               int ret = init_random_seeds(td->rand_seeds, sizeof(td->rand_seeds));
+               dprint(FD_RANDOM, "using system RNG for random seeds\n");
+               if (ret)
+                       return ret;
+       } else {
+               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->thread_number + i;
+                       seed *= 0x9e370001UL;
+               }
        }
 
-       if (td->o.random_generator == FIO_RAND_GEN_TAUSWORTHE64)
-               use64 = true;
-       else
-               use64 = false;
+       td_fill_rand_seeds(td);
 
-       td_fill_rand_seeds_internal(td, use64);
+       dprint(FD_RANDOM, "FIO_RAND_NR_OFFS=%d\n", FIO_RAND_NR_OFFS);
+       for (int i = 0; i < FIO_RAND_NR_OFFS; i++)
+               dprint(FD_RANDOM, "rand_seeds[%d]=%" PRIu64 "\n", i, td->rand_seeds[i]);
 
-       init_rand_seed(&td->buf_state, td->rand_seeds[FIO_RAND_BUF_OFF], use64);
-       frand_copy(&td->buf_state_prev, &td->buf_state);
+       return 0;
 }
 
 /*
@@ -1087,18 +1165,15 @@ int ioengine_load(struct thread_data *td)
                 * for this name and see if they match. If they do, then
                 * the engine is unchanged.
                 */
-               dlhandle = td->io_ops_dlhandle;
+               dlhandle = td->io_ops->dlhandle;
                ops = load_ioengine(td);
                if (!ops)
                        goto fail;
 
-               if (ops == td->io_ops && dlhandle == td->io_ops_dlhandle) {
-                       if (dlhandle)
-                               dlclose(dlhandle);
+               if (ops == td->io_ops && dlhandle == td->io_ops->dlhandle)
                        return 0;
-               }
 
-               if (dlhandle && dlhandle != td->io_ops_dlhandle)
+               if (dlhandle && dlhandle != td->io_ops->dlhandle)
                        dlclose(dlhandle);
 
                /* Unload the old engine. */
@@ -1195,36 +1270,12 @@ static void init_flags(struct thread_data *td)
        }
 }
 
-static int setup_random_seeds(struct thread_data *td)
-{
-       uint64_t seed;
-       unsigned int i;
-
-       if (!td->o.rand_repeatable && !fio_option_is_set(&td->o, rand_seed)) {
-               int ret = init_random_seeds(td->rand_seeds, sizeof(td->rand_seeds));
-               if (!ret)
-                       td_fill_rand_seeds(td);
-               return ret;
-       }
-
-       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->thread_number + i;
-               seed *= 0x9e370001UL;
-       }
-
-       td_fill_rand_seeds(td);
-       return 0;
-}
-
 enum {
        FPRE_NONE = 0,
        FPRE_JOBNAME,
        FPRE_JOBNUM,
-       FPRE_FILENUM
+       FPRE_FILENUM,
+       FPRE_CLIENTUID
 };
 
 static struct fpre_keyword {
@@ -1235,6 +1286,7 @@ static struct fpre_keyword {
        { .keyword = "$jobname",        .key = FPRE_JOBNAME, },
        { .keyword = "$jobnum",         .key = FPRE_JOBNUM, },
        { .keyword = "$filenum",        .key = FPRE_FILENUM, },
+       { .keyword = "$clientuid",      .key = FPRE_CLIENTUID, },
        { .keyword = NULL, },
        };
 
@@ -1324,6 +1376,21 @@ static char *make_filename(char *buf, size_t buf_size,struct thread_options *o,
                                }
                                break;
                                }
+                       case FPRE_CLIENTUID: {
+                               int ret;
+                               ret = snprintf(dst, dst_left, "%s", client_sockaddr_str);
+                               if (ret < 0)
+                                       break;
+                               else if (ret > dst_left) {
+                                       log_err("fio: truncated filename\n");
+                                       dst += dst_left;
+                                       dst_left = 0;
+                               } else {
+                                       dst += ret;
+                                       dst_left -= ret;
+                               }
+                               break;
+                               }
                        default:
                                assert(0);
                                break;
@@ -1356,15 +1423,14 @@ static void gen_log_name(char *name, size_t size, const char *logtype,
 
 static int check_waitees(char *waitee)
 {
-       struct thread_data *td;
-       int i, ret = 0;
+       int ret = 0;
 
-       for_each_td(td, i) {
+       for_each_td(td) {
                if (td->subjob_number)
                        continue;
 
                ret += !strcmp(td->o.name, waitee);
-       }
+       } end_for_each();
 
        return ret;
 }
@@ -1397,6 +1463,23 @@ static bool wait_for_ok(const char *jobname, struct thread_options *o)
        return true;
 }
 
+static int verify_per_group_options(struct thread_data *td, const char *jobname)
+{
+       for_each_td(td2) {
+               if (td->groupid != td2->groupid)
+                       continue;
+
+               if (td->o.stats &&
+                   td->o.lat_percentiles != td2->o.lat_percentiles) {
+                       log_err("fio: lat_percentiles in job: %s differs from group\n",
+                               jobname);
+                       return 1;
+               }
+       } end_for_each();
+
+       return 0;
+}
+
 /*
  * Treat an empty log file name the same as a one not given
  */
@@ -1466,6 +1549,9 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num,
        if (fixup_options(td))
                goto err;
 
+       if (!td->o.dedupe_global && init_dedupe_working_set_seeds(td, 0))
+               goto err;
+
        /*
         * Belongs to fixup_options, but o->name is not necessarily set as yet
         */
@@ -1497,17 +1583,15 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num,
        memcpy(td->ts.percentile_list, o->percentile_list, sizeof(o->percentile_list));
        td->ts.sig_figs = o->sig_figs;
 
-       for (i = 0; i < DDIR_RWDIR_CNT; i++) {
-               td->ts.clat_stat[i].min_val = ULONG_MAX;
-               td->ts.slat_stat[i].min_val = ULONG_MAX;
-               td->ts.lat_stat[i].min_val = ULONG_MAX;
-               td->ts.bw_stat[i].min_val = ULONG_MAX;
-               td->ts.iops_stat[i].min_val = ULONG_MAX;
-               td->ts.clat_high_prio_stat[i].min_val = ULONG_MAX;
-               td->ts.clat_low_prio_stat[i].min_val = ULONG_MAX;
-       }
-       td->ts.sync_stat.min_val = ULONG_MAX;
-       td->ddir_seq_nr = o->ddir_seq_nr;
+       init_thread_stat_min_vals(&td->ts);
+
+       /*
+        * td->>ddir_seq_nr needs to be initialized to 1, NOT o->ddir_seq_nr,
+        * so that get_next_offset gets a new random offset the first time it
+        * is called, instead of keeping an initial offset of 0 for the first
+        * nr-1 calls
+        */
+       td->ddir_seq_nr = 1;
 
        if ((o->stonewall || o->new_group) && prev_group_jobs) {
                prev_group_jobs = 0;
@@ -1521,6 +1605,10 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num,
        td->groupid = groupid;
        prev_group_jobs++;
 
+       if (td->o.group_reporting && prev_group_jobs > 1 &&
+           verify_per_group_options(td, jobname))
+               goto err;
+
        if (setup_rate(td))
                goto err;
 
@@ -1532,6 +1620,7 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num,
                        .hist_coarseness = o->log_hist_coarseness,
                        .log_type = IO_LOG_TYPE_LAT,
                        .log_offset = o->log_offset,
+                       .log_prio = o->log_prio,
                        .log_gz = o->log_gz,
                        .log_gz_store = o->log_gz_store,
                };
@@ -1543,17 +1632,23 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num,
                else
                        suf = "log";
 
-               gen_log_name(logname, sizeof(logname), "lat", pre,
-                               td->thread_number, suf, o->per_job_logs);
-               setup_log(&td->lat_log, &p, logname);
+               if (!o->disable_lat) {
+                       gen_log_name(logname, sizeof(logname), "lat", pre,
+                                    td->thread_number, suf, o->per_job_logs);
+                       setup_log(&td->lat_log, &p, logname);
+               }
 
-               gen_log_name(logname, sizeof(logname), "slat", pre,
-                               td->thread_number, suf, o->per_job_logs);
-               setup_log(&td->slat_log, &p, logname);
+               if (!o->disable_slat) {
+                       gen_log_name(logname, sizeof(logname), "slat", pre,
+                                    td->thread_number, suf, o->per_job_logs);
+                       setup_log(&td->slat_log, &p, logname);
+               }
 
-               gen_log_name(logname, sizeof(logname), "clat", pre,
-                               td->thread_number, suf, o->per_job_logs);
-               setup_log(&td->clat_log, &p, logname);
+               if (!o->disable_clat) {
+                       gen_log_name(logname, sizeof(logname), "clat", pre,
+                                    td->thread_number, suf, o->per_job_logs);
+                       setup_log(&td->clat_log, &p, logname);
+               }
 
        }
 
@@ -1565,6 +1660,7 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num,
                        .hist_coarseness = o->log_hist_coarseness,
                        .log_type = IO_LOG_TYPE_HIST,
                        .log_offset = o->log_offset,
+                       .log_prio = o->log_prio,
                        .log_gz = o->log_gz,
                        .log_gz_store = o->log_gz_store,
                };
@@ -1596,6 +1692,7 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num,
                        .hist_coarseness = o->log_hist_coarseness,
                        .log_type = IO_LOG_TYPE_BW,
                        .log_offset = o->log_offset,
+                       .log_prio = o->log_prio,
                        .log_gz = o->log_gz,
                        .log_gz_store = o->log_gz_store,
                };
@@ -1627,6 +1724,7 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num,
                        .hist_coarseness = o->log_hist_coarseness,
                        .log_type = IO_LOG_TYPE_IOPS,
                        .log_offset = o->log_offset,
+                       .log_prio = o->log_prio,
                        .log_gz = o->log_gz,
                        .log_gz_store = o->log_gz_store,
                };
@@ -1735,11 +1833,8 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num,
                if (file_alloced) {
                        if (td_new->files) {
                                struct fio_file *f;
-                               for_each_file(td_new, f, i) {
-                                       if (f->file_name)
-                                               sfree(f->file_name);
-                                       sfree(f);
-                               }
+                               for_each_file(td_new, f, i)
+                                       fio_file_free(f);
                                free(td_new->files);
                                td_new->files = NULL;
                        }
@@ -1838,6 +1933,7 @@ static int __parse_jobs_ini(struct thread_data *td,
                int nested, char *name, char ***popts, int *aopts, int *nopts)
 {
        bool global = false;
+       bool stdin_occupied = false;
        char *string;
        FILE *f;
        char *p;
@@ -1854,9 +1950,10 @@ static int __parse_jobs_ini(struct thread_data *td,
        if (is_buf)
                f = NULL;
        else {
-               if (!strcmp(file, "-"))
+               if (!strcmp(file, "-")) {
                        f = stdin;
-               else
+                       stdin_occupied = true;
+               } else
                        f = fopen(file, "r");
 
                if (!f) {
@@ -1875,8 +1972,7 @@ static int __parse_jobs_ini(struct thread_data *td,
         * it's really 256 + small bit, 280 should suffice
         */
        if (!nested) {
-               name = malloc(280);
-               memset(name, 0, 280);
+               name = calloc(1, 280);
        }
 
        opts = NULL;
@@ -2059,15 +2155,17 @@ static int __parse_jobs_ini(struct thread_data *td,
 
                ret = fio_options_parse(td, opts, num_opts);
 
-               if (!ret) {
-                       if (!strcmp(file, "-") && td->o.read_iolog_file != NULL) {
-                               char *fname = get_name_by_idx(td->o.read_iolog_file,
-                                                             td->subjob_number);
-                               if (!strcmp(fname, "-")) {
-                                       log_err("fio: we can't read both iolog "
-                                               "and job file from stdin.\n");
+               if (!ret && td->o.read_iolog_file != NULL) {
+                       char *fname = get_name_by_idx(td->o.read_iolog_file,
+                                                     td->subjob_number);
+                       if (!strcmp(fname, "-")) {
+                               if (stdin_occupied) {
+                                       log_err("fio: only one user (read_iolog_file/job "
+                                               "file) of stdin is permitted at once but "
+                                               "more than one was found.\n");
                                        ret = 1;
                                }
+                               stdin_occupied = true;
                        }
                }
                if (!ret) {
@@ -2094,6 +2192,10 @@ static int __parse_jobs_ini(struct thread_data *td,
                i++;
        }
 
+       free(job_sections);
+       job_sections = NULL;
+       nr_job_sections = 0;
+
        free(opts);
 out:
        free(string);
@@ -2174,7 +2276,7 @@ static void usage(const char *name)
        printf("  --minimal\t\tMinimal (terse) output\n");
        printf("  --output-format=type\tOutput format (terse,json,json+,normal)\n");
        printf("  --terse-version=type\tSet terse version output format"
-               " (default 3, or 2 or 4)\n");
+               " (default 3, or 2 or 4 or 5)\n");
        printf("  --version\t\tPrint version info and exit\n");
        printf("  --help\t\tPrint this page\n");
        printf("  --cpuclock-test\tPerform test/validation of CPU clock\n");
@@ -2542,7 +2644,7 @@ int parse_cmd_line(int argc, char *argv[], int client_type)
                case 'i':
                        did_arg = true;
                        if (!cur_client) {
-                               fio_show_ioengine_help(optarg);
+                               exit_val = fio_show_ioengine_help(optarg);
                                do_exit++;
                        }
                        break;
@@ -2715,18 +2817,22 @@ int parse_cmd_line(int argc, char *argv[], int client_type)
                                break;
 
                        ret = fio_cmd_ioengine_option_parse(td, opt, val);
+
+                       if (ret) {
+                               if (td) {
+                                       put_job(td);
+                                       td = NULL;
+                               }
+                               do_exit++;
+                               exit_val = 1;
+                       }
                        break;
                }
                case 'w':
                        warnings_fatal = 1;
                        break;
                case 'j':
-                       max_jobs = atoi(optarg);
-                       if (!max_jobs || max_jobs > REAL_MAX_JOBS) {
-                               log_err("fio: invalid max jobs: %d\n", max_jobs);
-                               do_exit++;
-                               exit_val = 1;
-                       }
+                       /* we don't track/need this anymore, ignore it */
                        break;
                case 'S':
                        did_arg = true;
@@ -2747,6 +2853,12 @@ int parse_cmd_line(int argc, char *argv[], int client_type)
                        exit_val = 1;
 #endif
                        break;
+#ifdef WIN32
+               case 'N':
+                       did_arg = true;
+                       fio_server_internal_set(optarg);
+                       break;
+#endif
                case 'D':
                        if (pid_file)
                                free(pid_file);
@@ -2894,7 +3006,7 @@ int parse_cmd_line(int argc, char *argv[], int client_type)
                        log_err("%s: unrecognized option '%s'\n", argv[0],
                                                        argv[optind - 1]);
                        show_closest_option(argv[optind - 1]);
-                       /* fall through */
+                       fio_fallthrough;
                default:
                        do_exit++;
                        exit_val = 1;