init: do not create lat logs when not needed
[fio.git] / init.c
diff --git a/init.c b/init.c
index ede0a8b97c773b2dd971c259c2f05c306dd84afa..07daaa84b17c6318727846e9634919c328fe6a07 100644 (file)
--- a/init.c
+++ b/init.c
@@ -30,7 +30,9 @@
 #include "idletime.h"
 #include "filelock.h"
 #include "steadystate.h"
+#include "blktrace.h"
 
+#include "oslib/asprintf.h"
 #include "oslib/getopt.h"
 #include "oslib/strcasestr.h"
 
@@ -43,16 +45,16 @@ const char fio_version_string[] = FIO_VERSION;
 #define FIO_RANDSEED           (0xb1899bedUL)
 
 static char **ini_file;
-static int max_jobs = FIO_MAX_JOBS;
-static int dump_cmdline;
-static int parse_only;
+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;
 
-int exitall_on_terminate = 0;
+bool exitall_on_terminate = false;
 int output_format = FIO_OUTPUT_NORMAL;
 int eta_print = FIO_ETA_AUTO;
 unsigned int eta_interval_msec = 1000;
@@ -62,12 +64,13 @@ FILE *f_err = NULL;
 char *exec_profile = NULL;
 int warnings_fatal = 0;
 int terse_version = 3;
-int is_backend = 0;
+bool is_backend = false;
+bool is_local_backend = false;
 int nr_clients = 0;
-int log_syslog = 0;
+bool log_syslog = false;
 
-int write_bw_log = 0;
-int read_only = 0;
+bool write_bw_log = false;
+bool read_only = false;
 int status_interval = 0;
 
 char *trigger_file = NULL;
@@ -258,7 +261,7 @@ static struct option l_opts[FIO_NR_OPTIONS] = {
        {
                .name           = (char *) "status-interval",
                .has_arg        = required_argument,
-               .val            = 'L',
+               .val            = 'L' | FIO_CLIENT_FLAG,
        },
        {
                .name           = (char *) "trigger-file",
@@ -285,6 +288,11 @@ static struct option l_opts[FIO_NR_OPTIONS] = {
                .has_arg        = required_argument,
                .val            = 'K',
        },
+       {
+               .name           = (char *) "merge-blktrace-only",
+               .has_arg        = no_argument,
+               .val            = 'A' | FIO_CLIENT_FLAG,
+       },
        {
                .name           = NULL,
        },
@@ -292,25 +300,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;
@@ -326,73 +344,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;
@@ -421,19 +448,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;
@@ -461,21 +475,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);
@@ -525,7 +537,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--;
 }
 
@@ -555,13 +568,11 @@ static int setup_rate(struct thread_data *td)
 {
        int ret = 0;
 
-       if (td->o.rate[DDIR_READ] || td->o.rate_iops[DDIR_READ])
-               ret = __setup_rate(td, DDIR_READ);
-       if (td->o.rate[DDIR_WRITE] || td->o.rate_iops[DDIR_WRITE])
-               ret |= __setup_rate(td, DDIR_WRITE);
-       if (td->o.rate[DDIR_TRIM] || td->o.rate_iops[DDIR_TRIM])
-               ret |= __setup_rate(td, DDIR_TRIM);
-
+       for_each_rw_ddir(ddir) {
+               if (td->o.rate[ddir] || td->o.rate_iops[ddir]) {
+                       ret |= __setup_rate(td, ddir);
+               }
+       }
        return ret;
 }
 
@@ -617,17 +628,39 @@ static int fixup_options(struct thread_data *td)
                ret |= warnings_fatal;
        }
 
+       if (o->zone_mode == ZONE_MODE_NONE && o->zone_size) {
+               log_err("fio: --zonemode=none and --zonesize are not compatible.\n");
+               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;
+       }
+
+       if (o->zone_mode == ZONE_MODE_NOT_SPECIFIED) {
+               if (o->zone_size)
+                       o->zone_mode = ZONE_MODE_STRIDED;
+               else
+                       o->zone_mode = ZONE_MODE_NONE;
+       }
+
        /*
-        * only really works with 1 file
+        * Strided zone mode only really works with 1 file.
         */
-       if (o->zone_size && o->open_files > 1)
-               o->zone_size = 0;
+       if (o->zone_mode == ZONE_MODE_STRIDED && o->open_files > 1)
+               o->zone_mode = ZONE_MODE_NONE;
 
        /*
         * If zone_range isn't specified, backward compatibility dictates it
         * should be made equal to zone_size.
         */
-       if (o->zone_size && !o->zone_range)
+       if (o->zone_mode == ZONE_MODE_STRIDED && !o->zone_range)
                o->zone_range = o->zone_size;
 
        /*
@@ -636,31 +669,25 @@ static int fixup_options(struct thread_data *td)
        if (td_read(td))
                o->overwrite = 1;
 
-       if (!o->min_bs[DDIR_READ])
-               o->min_bs[DDIR_READ] = o->bs[DDIR_READ];
-       if (!o->max_bs[DDIR_READ])
-               o->max_bs[DDIR_READ] = o->bs[DDIR_READ];
-       if (!o->min_bs[DDIR_WRITE])
-               o->min_bs[DDIR_WRITE] = o->bs[DDIR_WRITE];
-       if (!o->max_bs[DDIR_WRITE])
-               o->max_bs[DDIR_WRITE] = o->bs[DDIR_WRITE];
-       if (!o->min_bs[DDIR_TRIM])
-               o->min_bs[DDIR_TRIM] = o->bs[DDIR_TRIM];
-       if (!o->max_bs[DDIR_TRIM])
-               o->max_bs[DDIR_TRIM] = o->bs[DDIR_TRIM];
-
-       o->rw_min_bs = min(o->min_bs[DDIR_READ], o->min_bs[DDIR_WRITE]);
-       o->rw_min_bs = min(o->min_bs[DDIR_TRIM], o->rw_min_bs);
+       for_each_rw_ddir(ddir) {
+               if (!o->min_bs[ddir])
+                       o->min_bs[ddir] = o->bs[ddir];
+               if (!o->max_bs[ddir])
+                       o->max_bs[ddir] = o->bs[ddir];
+       }
+
+       o->rw_min_bs = -1;
+       for_each_rw_ddir(ddir) {
+               o->rw_min_bs = min(o->rw_min_bs, o->min_bs[ddir]);
+       }
 
        /*
         * For random IO, allow blockalign offset other than min_bs.
         */
-       if (!o->ba[DDIR_READ] || !td_random(td))
-               o->ba[DDIR_READ] = o->min_bs[DDIR_READ];
-       if (!o->ba[DDIR_WRITE] || !td_random(td))
-               o->ba[DDIR_WRITE] = o->min_bs[DDIR_WRITE];
-       if (!o->ba[DDIR_TRIM] || !td_random(td))
-               o->ba[DDIR_TRIM] = o->min_bs[DDIR_TRIM];
+       for_each_rw_ddir(ddir) {
+               if (!o->ba[ddir] || !td_random(td))
+                       o->ba[ddir] = o->min_bs[ddir];
+       }
 
        if ((o->ba[DDIR_READ] != o->min_bs[DDIR_READ] ||
            o->ba[DDIR_WRITE] != o->min_bs[DDIR_WRITE] ||
@@ -719,19 +746,12 @@ static int fixup_options(struct thread_data *td)
        /*
         * There's no need to check for in-flight overlapping IOs if the job
         * isn't changing data or the maximum iodepth is guaranteed to be 1
+        * when we are not in offload mode
         */
        if (o->serialize_overlap && !(td->flags & TD_F_READ_IOLOG) &&
-           (!(td_write(td) || td_trim(td)) || o->iodepth == 1))
+           (!(td_write(td) || td_trim(td)) || o->iodepth == 1) &&
+           o->io_submit_mode != IO_MODE_OFFLOAD)
                o->serialize_overlap = 0;
-       /*
-        * Currently can't check for overlaps in offload mode
-        */
-       if (o->serialize_overlap && o->io_submit_mode == IO_MODE_OFFLOAD) {
-               log_err("fio: checking for in-flight overlaps when the "
-                       "io_submit_mode is offload is not supported\n");
-               o->serialize_overlap = 0;
-               ret |= warnings_fatal;
-       }
 
        if (o->nr_files > td->files_index)
                o->nr_files = td->files_index;
@@ -746,14 +766,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->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;
+       for_each_rw_ddir(ddir) {
+               if ((o->rate[ddir] && (o->rate[ddir] < o->ratemin[ddir])) ||
+                   (o->rate_iops[ddir] && (o->rate_iops[ddir] < o->rate_iops_min[ddir]))) {
+                       log_err("fio: minimum rate exceeds rate, ddir %d\n", +ddir);
+                       ret |= 1;
+               }
        }
 
        if (!o->timeout && o->time_based) {
@@ -834,11 +852,6 @@ static int fixup_options(struct thread_data *td)
                        o->unit_base = N2S_BYTEPERSEC;
        }
 
-#ifndef FIO_HAVE_ANY_FALLOCATE
-       /* Platform doesn't support any fallocate so force it to none */
-       o->fallocate_mode = FIO_FALLOCATE_NONE;
-#endif
-
 #ifndef CONFIG_FDATASYNC
        if (o->fdatasync_blocks) {
                log_info("fio: this platform does not support fdatasync()"
@@ -930,31 +943,42 @@ static int fixup_options(struct thread_data *td)
                ret |= 1;
        }
 
-       if (fio_option_is_set(o, clat_percentiles) &&
-           !fio_option_is_set(o, lat_percentiles)) {
-               o->lat_percentiles = !o->clat_percentiles;
-       } else if (fio_option_is_set(o, lat_percentiles) &&
-                  !fio_option_is_set(o, clat_percentiles)) {
-               o->clat_percentiles = !o->lat_percentiles;
-       } else if (fio_option_is_set(o, lat_percentiles) &&
-                  fio_option_is_set(o, clat_percentiles) &&
-                  o->lat_percentiles && o->clat_percentiles) {
-               log_err("fio: lat_percentiles and clat_percentiles are "
-                       "mutually exclusive\n");
-               ret |= 1;
-       }
-
        if (o->disable_lat)
                o->lat_percentiles = 0;
        if (o->disable_clat)
                o->clat_percentiles = 0;
+       if (o->disable_slat)
+               o->slat_percentiles = 0;
 
        /*
         * Fix these up to be nsec internally
         */
-       o->max_latency *= 1000ULL;
+       for_each_rw_ddir(ddir)
+               o->max_latency[ddir] *= 1000ULL;
+
        o->latency_target *= 1000ULL;
-       o->latency_window *= 1000ULL;
+
+       /*
+        * Dedupe working set verifications
+        */
+       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;
+               }
+       }
 
        return ret;
 }
@@ -965,13 +989,13 @@ 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);
        }
 }
@@ -991,9 +1015,9 @@ void td_fill_verify_state_seed(struct thread_data *td)
 
 static void td_fill_rand_seeds_internal(struct thread_data *td, bool use64)
 {
-       unsigned int read_seed = td->rand_seeds[FIO_RAND_BS_OFF];
-       unsigned int write_seed = td->rand_seeds[FIO_RAND_BS1_OFF];
-       unsigned int trim_seed = td->rand_seeds[FIO_RAND_BS2_OFF];
+       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;
 
        /*
@@ -1028,6 +1052,8 @@ static void td_fill_rand_seeds_internal(struct thread_data *td, bool use64)
        init_rand_seed(&td->poisson_state[2], td->rand_seeds[FIO_RAND_POISSON3_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);
+       init_rand_seed(&td->prio_state, td->rand_seeds[FIO_RAND_PRIO_CMDS], false);
+       init_rand_seed(&td->dedupe_working_set_index_state, td->rand_seeds[FIO_RAND_DEDUPE_WORKING_SET_IX], use64);
 
        if (!td_random(td))
                return;
@@ -1095,15 +1121,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 == td->io_ops && dlhandle == td->io_ops_dlhandle) {
-                       if (dlhandle)
-                               dlclose(dlhandle);
+               if (!ops)
+                       goto fail;
+
+               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. */
@@ -1111,10 +1137,8 @@ int ioengine_load(struct thread_data *td)
        }
 
        td->io_ops = load_ioengine(td);
-       if (!td->io_ops) {
-               log_err("fio: failed to load engine\n");
-               return 1;
-       }
+       if (!td->io_ops)
+               goto fail;
 
        if (td->io_ops->option_struct_size && td->io_ops->options) {
                /*
@@ -1153,6 +1177,11 @@ int ioengine_load(struct thread_data *td)
 
        td_set_ioengine_flags(td);
        return 0;
+
+fail:
+       log_err("fio: failed to load engine\n");
+       return 1;
+
 }
 
 static void init_flags(struct thread_data *td)
@@ -1199,7 +1228,7 @@ static void init_flags(struct thread_data *td)
 
 static int setup_random_seeds(struct thread_data *td)
 {
-       unsigned long seed;
+       uint64_t seed;
        unsigned int i;
 
        if (!td->o.rand_repeatable && !fio_option_is_set(&td->o, rand_seed)) {
@@ -1226,7 +1255,8 @@ enum {
        FPRE_NONE = 0,
        FPRE_JOBNAME,
        FPRE_JOBNUM,
-       FPRE_FILENUM
+       FPRE_FILENUM,
+       FPRE_CLIENTUID
 };
 
 static struct fpre_keyword {
@@ -1237,6 +1267,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, },
        };
 
@@ -1255,8 +1286,7 @@ static char *make_filename(char *buf, size_t buf_size,struct thread_options *o,
        for (f = &fpre_keywords[0]; f->keyword; f++)
                f->strlen = strlen(f->keyword);
 
-       buf[buf_size - 1] = '\0';
-       strncpy(buf, o->filename_format, buf_size - 1);
+       snprintf(buf, buf_size, "%s", o->filename_format);
 
        memset(copy, 0, sizeof(copy));
        for (f = &fpre_keywords[0]; f->keyword; f++) {
@@ -1327,6 +1357,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;
@@ -1335,7 +1380,7 @@ static char *make_filename(char *buf, size_t buf_size,struct thread_options *o,
                        if (post_start)
                                strncpy(dst, buf + post_start, dst_left);
 
-                       strncpy(buf, copy, buf_size - 1);
+                       snprintf(buf, buf_size, "%s", copy);
                } while (1);
        }
 
@@ -1400,6 +1445,17 @@ static bool wait_for_ok(const char *jobname, struct thread_options *o)
        return true;
 }
 
+/*
+ * Treat an empty log file name the same as a one not given
+ */
+static const char *make_log_name(const char *logname, const char *jobname)
+{
+       if (logname && strcmp(logname, ""))
+               return logname;
+
+       return jobname;
+}
+
 /*
  * Adds a job to the list of things todo. Sanitizes the various options
  * to make sure we don't have conflicts, and initializes various
@@ -1409,7 +1465,7 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num,
                   int recursed, int client_type)
 {
        unsigned int i;
-       char fname[PATH_MAX];
+       char fname[PATH_MAX + 1];
        int numjobs, file_alloced;
        struct thread_options *o = &td->o;
        char logname[PATH_MAX + 32];
@@ -1458,6 +1514,9 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num,
        if (fixup_options(td))
                goto err;
 
+       if (init_dedupe_working_set_seeds(td))
+               goto err;
+
        /*
         * Belongs to fixup_options, but o->name is not necessarily set as yet
         */
@@ -1484,18 +1543,12 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num,
 
        td->ts.clat_percentiles = o->clat_percentiles;
        td->ts.lat_percentiles = o->lat_percentiles;
+       td->ts.slat_percentiles = o->slat_percentiles;
        td->ts.percentile_precision = o->percentile_precision;
        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.sync_stat.min_val = ULONG_MAX;
+       init_thread_stat_min_vals(&td->ts);
        td->ddir_seq_nr = o->ddir_seq_nr;
 
        if ((o->stonewall || o->new_group) && prev_group_jobs) {
@@ -1521,10 +1574,11 @@ 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,
                };
-               const char *pre = o->lat_log_file ? o->lat_log_file : o->name;
+               const char *pre = make_log_name(o->lat_log_file, o->name);
                const char *suf;
 
                if (p.log_gz_store)
@@ -1532,17 +1586,24 @@ 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);
+               }
+
+               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);
+               }
 
-               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->write_hist_log) {
@@ -1553,10 +1614,11 @@ 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,
                };
-               const char *pre = o->hist_log_file ? o->hist_log_file : o->name;
+               const char *pre = make_log_name(o->hist_log_file, o->name);
                const char *suf;
 
 #ifndef CONFIG_ZLIB
@@ -1584,10 +1646,11 @@ 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,
                };
-               const char *pre = o->bw_log_file ? o->bw_log_file : o->name;
+               const char *pre = make_log_name(o->bw_log_file, o->name);
                const char *suf;
 
                if (fio_option_is_set(o, bw_avg_time))
@@ -1615,10 +1678,11 @@ 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,
                };
-               const char *pre = o->iops_log_file ? o->iops_log_file : o->name;
+               const char *pre = make_log_name(o->iops_log_file, o->name);
                const char *suf;
 
                if (fio_option_is_set(o, iops_avg_time))
@@ -1651,6 +1715,7 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num,
                                char *c1, *c2, *c3, *c4;
                                char *c5 = NULL, *c6 = NULL;
                                int i2p = is_power_of_2(o->kb_base);
+                               struct buf_output out;
 
                                c1 = num2str(o->min_bs[DDIR_READ], o->sig_figs, 1, i2p, N2S_BYTE);
                                c2 = num2str(o->max_bs[DDIR_READ], o->sig_figs, 1, i2p, N2S_BYTE);
@@ -1662,19 +1727,22 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num,
                                        c6 = num2str(o->max_bs[DDIR_TRIM], o->sig_figs, 1, i2p, N2S_BYTE);
                                }
 
-                               log_info("%s: (g=%d): rw=%s, ", td->o.name,
+                               buf_output_init(&out);
+                               __log_buf(&out, "%s: (g=%d): rw=%s, ", td->o.name,
                                                        td->groupid,
                                                        ddir_str(o->td_ddir));
 
                                if (o->bs_is_seq_rand)
-                                       log_info("bs=(R) %s-%s, (W) %s-%s, bs_is_seq_rand, ",
+                                       __log_buf(&out, "bs=(R) %s-%s, (W) %s-%s, bs_is_seq_rand, ",
                                                        c1, c2, c3, c4);
                                else
-                                       log_info("bs=(R) %s-%s, (W) %s-%s, (T) %s-%s, ",
+                                       __log_buf(&out, "bs=(R) %s-%s, (W) %s-%s, (T) %s-%s, ",
                                                        c1, c2, c3, c4, c5, c6);
 
-                               log_info("ioengine=%s, iodepth=%u\n",
+                               __log_buf(&out, "ioengine=%s, iodepth=%u\n",
                                                td->io_ops->name, o->iodepth);
+                               log_info_buf(out.buf, out.buflen);
+                               buf_output_free(&out);
 
                                free(c1);
                                free(c2);
@@ -1690,6 +1758,14 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num,
        if (td_steadystate_init(td))
                goto err;
 
+       if (o->merge_blktrace_file && !merge_blktrace_iologs(td))
+               goto err;
+
+       if (merge_blktrace_only) {
+               put_job(td);
+               return 0;
+       }
+
        /*
         * recurse add identical jobs, clear numjobs and stonewall options
         * as they don't apply to sub-jobs
@@ -1711,11 +1787,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;
                        }
@@ -1814,6 +1887,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;
@@ -1830,9 +1904,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) {
@@ -1845,7 +1920,7 @@ static int __parse_jobs_ini(struct thread_data *td,
                }
        }
 
-       string = malloc(4096);
+       string = malloc(OPT_LEN_MAX);
 
        /*
         * it's really 256 + small bit, 280 should suffice
@@ -1878,7 +1953,7 @@ static int __parse_jobs_ini(struct thread_data *td,
                        if (is_buf)
                                p = strsep(&file, "\n");
                        else
-                               p = fgets(string, 4096, f);
+                               p = fgets(string, OPT_LEN_MAX, f);
                        if (!p)
                                break;
                }
@@ -1947,7 +2022,7 @@ static int __parse_jobs_ini(struct thread_data *td,
                                if (is_buf)
                                        p = strsep(&file, "\n");
                                else
-                                       p = fgets(string, 4096, f);
+                                       p = fgets(string, OPT_LEN_MAX, f);
                                if (!p)
                                        break;
                                dprint(FD_PARSE, "%s", p);
@@ -1987,19 +2062,12 @@ static int __parse_jobs_ini(struct thread_data *td,
                                 */
                                if (access(filename, F_OK) &&
                                    (ts = strrchr(file, '/'))) {
-                                       int len = ts - file +
-                                               strlen(filename) + 2;
-
-                                       if (!(full_fn = calloc(1, len))) {
+                                       if (asprintf(&full_fn, "%.*s%s",
+                                                (int)(ts - file + 1), file,
+                                                filename) < 0) {
                                                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;
                                }
 
@@ -2041,6 +2109,20 @@ static int __parse_jobs_ini(struct thread_data *td,
                }
 
                ret = fio_options_parse(td, opts, num_opts);
+
+               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) {
                        if (dump_cmdline)
                                dump_opt_list(td);
@@ -2139,6 +2221,7 @@ static void usage(const char *name)
        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("  --merge-blktrace-only\tMerge blktraces only, don't start any IO\n");
        printf("  --output\t\tWrite output to file\n");
        printf("  --bandwidth-log\tGenerate aggregate bandwidth logs\n");
        printf("  --minimal\t\tMinimal (terse) output\n");
@@ -2262,6 +2345,10 @@ const struct debug_level debug_levels[] = {
          .help = "Helper thread logging",
          .shift = FD_HELPERTHREAD,
        },
+       { .name = "zbd",
+         .help = "Zoned Block Device logging",
+         .shift = FD_ZBD,
+       },
        { .name = NULL, },
 };
 
@@ -2433,7 +2520,7 @@ int parse_cmd_line(int argc, char *argv[], int client_type)
        char *ostr = cmd_optstr;
        char *pid_file = NULL;
        void *cur_client = NULL;
-       int backend = 0;
+       bool backend = false;
 
        /*
         * Reset optind handling, since we may call this multiple times
@@ -2459,7 +2546,7 @@ int parse_cmd_line(int argc, char *argv[], int client_type)
                        exit_val = 1;
                        break;
                case 'b':
-                       write_bw_log = 1;
+                       write_bw_log = true;
                        break;
                case 'o': {
                        FILE *tmp;
@@ -2508,13 +2595,13 @@ 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;
                case 's':
                        did_arg = true;
-                       dump_cmdline = 1;
+                       dump_cmdline = true;
                        break;
                case 'r':
                        read_only = 1;
@@ -2580,7 +2667,7 @@ int parse_cmd_line(int argc, char *argv[], int client_type)
                        break;
                case 'P':
                        did_arg = true;
-                       parse_only = 1;
+                       parse_only = true;
                        break;
                case 'x': {
                        size_t new_size;
@@ -2687,12 +2774,7 @@ int parse_cmd_line(int argc, char *argv[], int client_type)
                        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;
@@ -2705,8 +2787,8 @@ int parse_cmd_line(int argc, char *argv[], int client_type)
                        }
                        if (optarg)
                                fio_server_set_arg(optarg);
-                       is_backend = 1;
-                       backend = 1;
+                       is_backend = true;
+                       backend = true;
 #else
                        log_err("fio: client/server requires SHM support\n");
                        do_exit++;
@@ -2851,10 +2933,16 @@ int parse_cmd_line(int argc, char *argv[], int client_type)
                        }
                        trigger_timeout /= 1000000;
                        break;
+
+               case 'A':
+                       did_arg = true;
+                       merge_blktrace_only = true;
+                       break;
                case '?':
                        log_err("%s: unrecognized option '%s'\n", argv[0],
                                                        argv[optind - 1]);
                        show_closest_option(argv[optind - 1]);
+                       fallthrough;
                default:
                        do_exit++;
                        exit_val = 1;