t/zbd: avoid test case 31 failure with small devices
[fio.git] / options.c
index 625112c5345dbb219fb06cdd0aa1e59e9ee92a6c..61ea41cc4e0fc673893c3b9ecc9ec647c4216e3a 100644 (file)
--- a/options.c
+++ b/options.c
@@ -4,6 +4,7 @@
 #include <ctype.h>
 #include <string.h>
 #include <assert.h>
+#include <fcntl.h>
 #include <sys/stat.h>
 #include <netinet/in.h>
 
@@ -73,13 +74,7 @@ static int bs_cmp(const void *p1, const void *p2)
        return (int) bsp1->perc - (int) bsp2->perc;
 }
 
-struct split {
-       unsigned int nr;
-       unsigned long long val1[ZONESPLIT_MAX];
-       unsigned long long val2[ZONESPLIT_MAX];
-};
-
-static int split_parse_ddir(struct thread_options *o, struct split *split,
+int split_parse_ddir(struct thread_options *o, struct split *split,
                            char *str, bool absolute, unsigned int max_splits)
 {
        unsigned long long perc;
@@ -138,8 +133,8 @@ static int split_parse_ddir(struct thread_options *o, struct split *split,
        return 0;
 }
 
-static int bssplit_ddir(struct thread_options *o, enum fio_ddir ddir, char *str,
-                       bool data)
+static int bssplit_ddir(struct thread_options *o, void *eo,
+                       enum fio_ddir ddir, char *str, bool data)
 {
        unsigned int i, perc, perc_missing;
        unsigned long long max_bs, min_bs;
@@ -211,10 +206,8 @@ static int bssplit_ddir(struct thread_options *o, enum fio_ddir ddir, char *str,
        return 0;
 }
 
-typedef int (split_parse_fn)(struct thread_options *, enum fio_ddir, char *, bool);
-
-static int str_split_parse(struct thread_data *td, char *str,
-                          split_parse_fn *fn, bool data)
+int str_split_parse(struct thread_data *td, char *str,
+                   split_parse_fn *fn, void *eo, bool data)
 {
        char *odir, *ddir;
        int ret = 0;
@@ -223,42 +216,77 @@ static int str_split_parse(struct thread_data *td, char *str,
        if (odir) {
                ddir = strchr(odir + 1, ',');
                if (ddir) {
-                       ret = fn(&td->o, DDIR_TRIM, ddir + 1, data);
+                       ret = fn(&td->o, eo, DDIR_TRIM, ddir + 1, data);
                        if (!ret)
                                *ddir = '\0';
                } else {
                        char *op;
 
                        op = strdup(odir + 1);
-                       ret = fn(&td->o, DDIR_TRIM, op, data);
+                       ret = fn(&td->o, eo, DDIR_TRIM, op, data);
 
                        free(op);
                }
                if (!ret)
-                       ret = fn(&td->o, DDIR_WRITE, odir + 1, data);
+                       ret = fn(&td->o, eo, DDIR_WRITE, odir + 1, data);
                if (!ret) {
                        *odir = '\0';
-                       ret = fn(&td->o, DDIR_READ, str, data);
+                       ret = fn(&td->o, eo, DDIR_READ, str, data);
                }
        } else {
                char *op;
 
                op = strdup(str);
-               ret = fn(&td->o, DDIR_WRITE, op, data);
+               ret = fn(&td->o, eo, DDIR_WRITE, op, data);
                free(op);
 
                if (!ret) {
                        op = strdup(str);
-                       ret = fn(&td->o, DDIR_TRIM, op, data);
+                       ret = fn(&td->o, eo, DDIR_TRIM, op, data);
                        free(op);
                }
                if (!ret)
-                       ret = fn(&td->o, DDIR_READ, str, data);
+                       ret = fn(&td->o, eo, DDIR_READ, str, data);
        }
 
        return ret;
 }
 
+static int fio_fdp_cmp(const void *p1, const void *p2)
+{
+       const uint16_t *t1 = p1;
+       const uint16_t *t2 = p2;
+
+       return *t1 - *t2;
+}
+
+static int str_fdp_pli_cb(void *data, const char *input)
+{
+       struct thread_data *td = cb_data_to_td(data);
+       char *str, *p, *v;
+       int i = 0;
+
+       p = str = strdup(input);
+       strip_blank_front(&str);
+       strip_blank_end(str);
+
+       while ((v = strsep(&str, ",")) != NULL && i < FIO_MAX_DP_IDS) {
+               unsigned long long id = strtoull(v, NULL, 0);
+               if (id > 0xFFFF) {
+                       log_err("Placement IDs cannot exceed 0xFFFF\n");
+                       free(p);
+                       return 1;
+               }
+               td->o.dp_ids[i++] = id;
+       }
+       free(p);
+
+       qsort(td->o.dp_ids, i, sizeof(*td->o.dp_ids), fio_fdp_cmp);
+       td->o.dp_nr_ids = i;
+
+       return 0;
+}
+
 static int str_bssplit_cb(void *data, const char *input)
 {
        struct thread_data *td = cb_data_to_td(data);
@@ -270,7 +298,7 @@ static int str_bssplit_cb(void *data, const char *input)
        strip_blank_front(&str);
        strip_blank_end(str);
 
-       ret = str_split_parse(td, str, bssplit_ddir, false);
+       ret = str_split_parse(td, str, bssplit_ddir, NULL, false);
 
        if (parse_dryrun()) {
                int i;
@@ -286,6 +314,135 @@ static int str_bssplit_cb(void *data, const char *input)
        return ret;
 }
 
+static int parse_cmdprio_bssplit_entry(struct thread_options *o,
+                                      struct split_prio *entry, char *str)
+{
+       int matches = 0;
+       char *bs_str = NULL;
+       long long bs_val;
+       unsigned int perc = 0, class, level, hint;
+
+       /*
+        * valid entry formats:
+        * bs/ - %s/ - set perc to 0, prio to -1.
+        * bs/perc - %s/%u - set prio to -1.
+        * bs/perc/class/level - %s/%u/%u/%u
+        * bs/perc/class/level/hint - %s/%u/%u/%u/%u
+        */
+       matches = sscanf(str, "%m[^/]/%u/%u/%u/%u",
+                        &bs_str, &perc, &class, &level, &hint);
+       if (matches < 1) {
+               log_err("fio: invalid cmdprio_bssplit format\n");
+               return 1;
+       }
+
+       if (str_to_decimal(bs_str, &bs_val, 1, o, 0, 0)) {
+               log_err("fio: split conversion failed\n");
+               free(bs_str);
+               return 1;
+       }
+       free(bs_str);
+
+       entry->bs = bs_val;
+       entry->perc = min(perc, 100u);
+       entry->prio = -1;
+       switch (matches) {
+       case 1: /* bs/ case */
+       case 2: /* bs/perc case */
+               break;
+       case 4: /* bs/perc/class/level case */
+       case 5: /* bs/perc/class/level/hint case */
+               class = min(class, (unsigned int) IOPRIO_MAX_PRIO_CLASS);
+               level = min(level, (unsigned int) IOPRIO_MAX_PRIO);
+               if (matches == 5)
+                       hint = min(hint, (unsigned int) IOPRIO_MAX_PRIO_HINT);
+               else
+                       hint = 0;
+               entry->prio = ioprio_value(class, level, hint);
+               break;
+       default:
+               log_err("fio: invalid cmdprio_bssplit format\n");
+               return 1;
+       }
+
+       return 0;
+}
+
+/*
+ * Returns a negative integer if the first argument should be before the second
+ * argument in the sorted list. A positive integer if the first argument should
+ * be after the second argument in the sorted list. A zero if they are equal.
+ */
+static int fio_split_prio_cmp(const void *p1, const void *p2)
+{
+       const struct split_prio *tmp1 = p1;
+       const struct split_prio *tmp2 = p2;
+
+       if (tmp1->bs > tmp2->bs)
+               return 1;
+       if (tmp1->bs < tmp2->bs)
+               return -1;
+       return 0;
+}
+
+int split_parse_prio_ddir(struct thread_options *o, struct split_prio **entries,
+                         int *nr_entries, char *str)
+{
+       struct split_prio *tmp_entries;
+       unsigned int nr_bssplits;
+       char *str_cpy, *p, *fname;
+
+       /* strsep modifies the string, dup it so that we can use strsep twice */
+       p = str_cpy = strdup(str);
+       if (!p)
+               return 1;
+
+       nr_bssplits = 0;
+       while ((fname = strsep(&str_cpy, ":")) != NULL) {
+               if (!strlen(fname))
+                       break;
+               nr_bssplits++;
+       }
+       free(p);
+
+       if (nr_bssplits > BSSPLIT_MAX) {
+               log_err("fio: too many cmdprio_bssplit entries\n");
+               return 1;
+       }
+
+       tmp_entries = calloc(nr_bssplits, sizeof(*tmp_entries));
+       if (!tmp_entries)
+               return 1;
+
+       nr_bssplits = 0;
+       while ((fname = strsep(&str, ":")) != NULL) {
+               struct split_prio *entry;
+
+               if (!strlen(fname))
+                       break;
+
+               entry = &tmp_entries[nr_bssplits];
+
+               if (parse_cmdprio_bssplit_entry(o, entry, fname)) {
+                       log_err("fio: failed to parse cmdprio_bssplit entry\n");
+                       free(tmp_entries);
+                       return 1;
+               }
+
+               /* skip zero perc entries, they provide no useful information */
+               if (entry->perc)
+                       nr_bssplits++;
+       }
+
+       qsort(tmp_entries, nr_bssplits, sizeof(*tmp_entries),
+             fio_split_prio_cmp);
+
+       *entries = tmp_entries;
+       *nr_entries = nr_bssplits;
+
+       return 0;
+}
+
 static int str2error(char *str)
 {
        const char *err[] = { "EPERM", "ENOENT", "ESRCH", "EINTR", "EIO",
@@ -446,9 +603,21 @@ static int str_rw_cb(void *data, const char *str)
        if (!nr)
                return 0;
 
-       if (td_random(td))
-               o->ddir_seq_nr = atoi(nr);
-       else {
+       if (td_random(td)) {
+               long long val;
+
+               if (str_to_decimal(nr, &val, 1, o, 0, 0)) {
+                       log_err("fio: randrw postfix parsing failed\n");
+                       free(nr);
+                       return 1;
+               }
+               if ((val <= 0) || (val > UINT_MAX)) {
+                       log_err("fio: randrw postfix parsing out of range\n");
+                       free(nr);
+                       return 1;
+               }
+               o->ddir_seq_nr = (unsigned int) val;
+       } else {
                long long val;
 
                if (str_to_decimal(nr, &val, 1, o, 0, 0)) {
@@ -485,7 +654,7 @@ static int fio_clock_source_cb(void *data, const char *str)
        return 0;
 }
 
-static int str_rwmix_read_cb(void *data, unsigned long long *val)
+static int str_rwmix_read_cb(void *data, long long *val)
 {
        struct thread_data *td = cb_data_to_td(data);
 
@@ -494,7 +663,7 @@ static int str_rwmix_read_cb(void *data, unsigned long long *val)
        return 0;
 }
 
-static int str_rwmix_write_cb(void *data, unsigned long long *val)
+static int str_rwmix_write_cb(void *data, long long *val)
 {
        struct thread_data *td = cb_data_to_td(data);
 
@@ -513,7 +682,7 @@ static int str_exitall_cb(void)
 int fio_cpus_split(os_cpu_mask_t *mask, unsigned int cpu_index)
 {
        unsigned int i, index, cpus_in_mask;
-       const long max_cpu = cpus_online();
+       const long max_cpu = cpus_configured();
 
        cpus_in_mask = fio_cpu_count(mask);
        if (!cpus_in_mask)
@@ -552,7 +721,7 @@ static int str_cpumask_cb(void *data, unsigned long long *val)
                return 1;
        }
 
-       max_cpu = cpus_online();
+       max_cpu = cpus_configured();
 
        for (i = 0; i < sizeof(int) * 8; i++) {
                if ((1 << i) & *val) {
@@ -588,7 +757,7 @@ static int set_cpus_allowed(struct thread_data *td, os_cpu_mask_t *mask,
        strip_blank_front(&str);
        strip_blank_end(str);
 
-       max_cpu = cpus_online();
+       max_cpu = cpus_configured();
 
        while ((cpu = strsep(&str, ",")) != NULL) {
                char *str2, *cpu2;
@@ -906,8 +1075,8 @@ static int str_sfr_cb(void *data, const char *str)
 }
 #endif
 
-static int zone_split_ddir(struct thread_options *o, enum fio_ddir ddir,
-                          char *str, bool absolute)
+static int zone_split_ddir(struct thread_options *o, void *eo,
+                          enum fio_ddir ddir, char *str, bool absolute)
 {
        unsigned int i, perc, perc_missing, sperc, sperc_missing;
        struct split split;
@@ -1012,7 +1181,7 @@ static int parse_zoned_distribution(struct thread_data *td, const char *input,
        }
        str += strlen(pre);
 
-       ret = str_split_parse(td, str, zone_split_ddir, absolute);
+       ret = str_split_parse(td, str, zone_split_ddir, NULL, absolute);
 
        free(p);
 
@@ -1252,7 +1421,7 @@ int get_max_str_idx(char *input)
 }
 
 /*
- * Returns the directory at the index, indexes > entires will be
+ * Returns the directory at the index, indexes > entries will be
  * assigned via modulo division of the index
  */
 int set_name_idx(char *target, size_t tlen, char *input, int index,
@@ -1374,8 +1543,8 @@ static int str_buffer_pattern_cb(void *data, const char *input)
        int ret;
 
        /* FIXME: for now buffer pattern does not support formats */
-       ret = parse_and_fill_pattern(input, strlen(input), td->o.buffer_pattern,
-                                    MAX_PATTERN_SIZE, NULL, NULL, NULL);
+       ret = parse_and_fill_pattern_alloc(input, strlen(input),
+                               &td->o.buffer_pattern, NULL, NULL, NULL);
        if (ret < 0)
                return 1;
 
@@ -1423,9 +1592,9 @@ static int str_verify_pattern_cb(void *data, const char *input)
        int ret;
 
        td->o.verify_fmt_sz = FIO_ARRAY_SIZE(td->o.verify_fmt);
-       ret = parse_and_fill_pattern(input, strlen(input), td->o.verify_pattern,
-                                    MAX_PATTERN_SIZE, fmt_desc,
-                                    td->o.verify_fmt, &td->o.verify_fmt_sz);
+       ret = parse_and_fill_pattern_alloc(input, strlen(input),
+                       &td->o.verify_pattern, fmt_desc, td->o.verify_fmt,
+                       &td->o.verify_fmt_sz);
        if (ret < 0)
                return 1;
 
@@ -1446,7 +1615,7 @@ static int str_gtod_reduce_cb(void *data, int *il)
        int val = *il;
 
        /*
-        * Only modfiy options if gtod_reduce==1
+        * Only modify options if gtod_reduce==1
         * Otherwise leave settings alone.
         */
        if (val) {
@@ -1463,7 +1632,7 @@ static int str_gtod_reduce_cb(void *data, int *il)
        return 0;
 }
 
-static int str_offset_cb(void *data, unsigned long long *__val)
+static int str_offset_cb(void *data, long long *__val)
 {
        struct thread_data *td = cb_data_to_td(data);
        unsigned long long v = *__val;
@@ -1471,15 +1640,20 @@ static int str_offset_cb(void *data, unsigned long long *__val)
        if (parse_is_percent(v)) {
                td->o.start_offset = 0;
                td->o.start_offset_percent = -1ULL - v;
+               td->o.start_offset_nz = 0;
                dprint(FD_PARSE, "SET start_offset_percent %d\n",
                                        td->o.start_offset_percent);
+       } else if (parse_is_zone(v)) {
+               td->o.start_offset = 0;
+               td->o.start_offset_percent = 0;
+               td->o.start_offset_nz = v - ZONE_BASE_VAL;
        } else
                td->o.start_offset = v;
 
        return 0;
 }
 
-static int str_offset_increment_cb(void *data, unsigned long long *__val)
+static int str_offset_increment_cb(void *data, long long *__val)
 {
        struct thread_data *td = cb_data_to_td(data);
        unsigned long long v = *__val;
@@ -1487,15 +1661,20 @@ static int str_offset_increment_cb(void *data, unsigned long long *__val)
        if (parse_is_percent(v)) {
                td->o.offset_increment = 0;
                td->o.offset_increment_percent = -1ULL - v;
+               td->o.offset_increment_nz = 0;
                dprint(FD_PARSE, "SET offset_increment_percent %d\n",
                                        td->o.offset_increment_percent);
+       } else if (parse_is_zone(v)) {
+               td->o.offset_increment = 0;
+               td->o.offset_increment_percent = 0;
+               td->o.offset_increment_nz = v - ZONE_BASE_VAL;
        } else
                td->o.offset_increment = v;
 
        return 0;
 }
 
-static int str_size_cb(void *data, unsigned long long *__val)
+static int str_size_cb(void *data, long long *__val)
 {
        struct thread_data *td = cb_data_to_td(data);
        unsigned long long v = *__val;
@@ -1505,6 +1684,10 @@ static int str_size_cb(void *data, unsigned long long *__val)
                td->o.size_percent = -1ULL - v;
                dprint(FD_PARSE, "SET size_percent %d\n",
                                        td->o.size_percent);
+       } else if (parse_is_zone(v)) {
+               td->o.size = 0;
+               td->o.size_percent = 0;
+               td->o.size_nz = v - ZONE_BASE_VAL;
        } else
                td->o.size = v;
 
@@ -1525,12 +1708,30 @@ static int str_io_size_cb(void *data, unsigned long long *__val)
                }
                dprint(FD_PARSE, "SET io_size_percent %d\n",
                                        td->o.io_size_percent);
+       } else if (parse_is_zone(v)) {
+               td->o.io_size = 0;
+               td->o.io_size_percent = 0;
+               td->o.io_size_nz = v - ZONE_BASE_VAL;
        } else
                td->o.io_size = v;
 
        return 0;
 }
 
+static int str_zoneskip_cb(void *data, long long *__val)
+{
+       struct thread_data *td = cb_data_to_td(data);
+       unsigned long long v = *__val;
+
+       if (parse_is_zone(v)) {
+               td->o.zone_skip = 0;
+               td->o.zone_skip_nz = v - ZONE_BASE_VAL;
+       } else
+               td->o.zone_skip = v;
+
+       return 0;
+}
+
 static int str_write_bw_log_cb(void *data, const char *str)
 {
        struct thread_data *td = cb_data_to_td(data);
@@ -1801,6 +2002,10 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                            .oval = TD_DDIR_TRIMWRITE,
                            .help = "Trim and write mix, trims preceding writes"
                          },
+                         { .ival = "randtrimwrite",
+                           .oval = TD_DDIR_RANDTRIMWRITE,
+                           .help = "Randomly trim and write mix, trims preceding writes"
+                         },
                },
        },
        {
@@ -1946,12 +2151,6 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                            .help = "Hadoop Distributed Filesystem (HDFS) engine"
                          },
 #endif
-#ifdef CONFIG_PMEMBLK
-                         { .ival = "pmemblk",
-                           .help = "PMDK libpmemblk based IO engine",
-                         },
-
-#endif
 #ifdef CONFIG_IME
                          { .ival = "ime_psync",
                            .help = "DDN's IME synchronous IO engine",
@@ -1989,6 +2188,21 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                          { .ival = "nbd",
                            .help = "Network Block Device (NBD) IO engine"
                          },
+#ifdef CONFIG_DFS
+                         { .ival = "dfs",
+                           .help = "DAOS File System (dfs) IO engine",
+                         },
+#endif
+#ifdef CONFIG_LIBNFS
+                         { .ival = "nfs",
+                           .help = "NFS IO engine",
+                         },
+#endif
+#ifdef CONFIG_LIBXNVME
+                         { .ival = "xnvme",
+                           .help = "XNVME IO engine",
+                         },
+#endif
                },
        },
        {
@@ -2091,11 +2305,10 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
        {
                .name   = "size",
                .lname  = "Size",
-               .type   = FIO_OPT_STR_VAL,
+               .type   = FIO_OPT_STR_VAL_ZONE,
                .cb     = str_size_cb,
                .off1   = offsetof(struct thread_options, size),
                .help   = "Total size of device or files",
-               .interval = 1024 * 1024,
                .category = FIO_OPT_C_IO,
                .group  = FIO_OPT_G_INVALID,
        },
@@ -2103,11 +2316,10 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .name   = "io_size",
                .alias  = "io_limit",
                .lname  = "IO Size",
-               .type   = FIO_OPT_STR_VAL,
+               .type   = FIO_OPT_STR_VAL_ZONE,
                .cb     = str_io_size_cb,
                .off1   = offsetof(struct thread_options, io_size),
                .help   = "Total size of I/O to be performed",
-               .interval = 1024 * 1024,
                .category = FIO_OPT_C_IO,
                .group  = FIO_OPT_G_INVALID,
        },
@@ -2148,12 +2360,11 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .name   = "offset",
                .lname  = "IO offset",
                .alias  = "fileoffset",
-               .type   = FIO_OPT_STR_VAL,
+               .type   = FIO_OPT_STR_VAL_ZONE,
                .cb     = str_offset_cb,
                .off1   = offsetof(struct thread_options, start_offset),
                .help   = "Start IO from this offset",
                .def    = "0",
-               .interval = 1024 * 1024,
                .category = FIO_OPT_C_IO,
                .group  = FIO_OPT_G_INVALID,
        },
@@ -2171,14 +2382,13 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
        {
                .name   = "offset_increment",
                .lname  = "IO offset increment",
-               .type   = FIO_OPT_STR_VAL,
+               .type   = FIO_OPT_STR_VAL_ZONE,
                .cb     = str_offset_increment_cb,
                .off1   = offsetof(struct thread_options, offset_increment),
                .help   = "What is the increment from one offset to the next",
                .parent = "offset",
                .hide   = 1,
                .def    = "0",
-               .interval = 1024 * 1024,
                .category = FIO_OPT_C_IO,
                .group  = FIO_OPT_G_INVALID,
        },
@@ -2192,6 +2402,17 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .category = FIO_OPT_C_IO,
                .group  = FIO_OPT_G_INVALID,
        },
+       {
+               .name   = "num_range",
+               .lname  = "Number of ranges",
+               .type   = FIO_OPT_INT,
+               .off1   = offsetof(struct thread_options, num_range),
+               .maxval = MAX_TRIM_RANGE,
+               .help   = "Number of ranges for trim command",
+               .def    = "1",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_INVALID,
+       },
        {
                .name   = "bs",
                .lname  = "Block size",
@@ -2281,6 +2502,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
        },
        {
                .name   = "randrepeat",
+               .alias  = "allrandrepeat",
                .lname  = "Random repeatable",
                .type   = FIO_OPT_BOOL,
                .off1   = offsetof(struct thread_options, rand_repeatable),
@@ -2410,16 +2632,6 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .category = FIO_OPT_C_IO,
                .group  = FIO_OPT_G_RANDOM,
        },
-       {
-               .name   = "allrandrepeat",
-               .lname  = "All Random Repeat",
-               .type   = FIO_OPT_BOOL,
-               .off1   = offsetof(struct thread_options, allrand_repeatable),
-               .help   = "Use repeatable random numbers for everything",
-               .def    = "0",
-               .category = FIO_OPT_C_IO,
-               .group  = FIO_OPT_G_RANDOM,
-       },
        {
                .name   = "nrfiles",
                .lname  = "Number of files",
@@ -2557,6 +2769,12 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                            .oval = F_ADV_SEQUENTIAL,
                            .help = "Advise using FADV_SEQUENTIAL",
                          },
+#ifdef POSIX_FADV_NOREUSE
+                         { .ival = "noreuse",
+                           .oval = F_ADV_NOREUSE,
+                           .help = "Advise using FADV_NOREUSE",
+                         },
+#endif
                },
                .help   = "Use fadvise() to advise the kernel on IO pattern",
                .def    = "1",
@@ -3414,11 +3632,11 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
        {
                .name   = "zoneskip",
                .lname  = "Zone skip",
-               .type   = FIO_OPT_STR_VAL,
+               .type   = FIO_OPT_STR_VAL_ZONE,
+               .cb     = str_zoneskip_cb,
                .off1   = offsetof(struct thread_options, zone_skip),
                .help   = "Space between IO zones",
                .def    = "0",
-               .interval = 1024 * 1024,
                .category = FIO_OPT_C_IO,
                .group  = FIO_OPT_G_ZONE,
        },
@@ -3437,7 +3655,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .lname  = "Per device/file maximum number of open zones",
                .type   = FIO_OPT_INT,
                .off1   = offsetof(struct thread_options, max_open_zones),
-               .maxval = ZBD_MAX_OPEN_ZONES,
+               .maxval = ZBD_MAX_WRITE_ZONES,
                .help   = "Limit on the number of simultaneously opened sequential write zones with zonemode=zbd",
                .def    = "0",
                .category = FIO_OPT_C_IO,
@@ -3448,12 +3666,22 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .lname  = "Job maximum number of open zones",
                .type   = FIO_OPT_INT,
                .off1   = offsetof(struct thread_options, job_max_open_zones),
-               .maxval = ZBD_MAX_OPEN_ZONES,
+               .maxval = ZBD_MAX_WRITE_ZONES,
                .help   = "Limit on the number of simultaneously opened sequential write zones with zonemode=zbd by one thread/process",
                .def    = "0",
                .category = FIO_OPT_C_IO,
                .group  = FIO_OPT_G_INVALID,
        },
+       {
+               .name   = "ignore_zone_limits",
+               .lname  = "Ignore zone resource limits",
+               .type   = FIO_OPT_BOOL,
+               .off1   = offsetof(struct thread_options, ignore_zone_limits),
+               .def    = "0",
+               .help   = "Ignore the zone resource limits (max open/active zones) reported by the device",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_INVALID,
+       },
        {
                .name   = "zone_reset_threshold",
                .lname  = "Zone reset threshold",
@@ -3478,6 +3706,74 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .category = FIO_OPT_C_IO,
                .group  = FIO_OPT_G_ZONE,
        },
+       {
+               .name   = "fdp",
+               .lname  = "Flexible data placement",
+               .type   = FIO_OPT_BOOL,
+               .off1   = offsetof(struct thread_options, fdp),
+               .help   = "Use Data placement directive (FDP)",
+               .def    = "0",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_INVALID,
+       },
+       {
+               .name   = "dataplacement",
+               .alias  = "data_placement",
+               .lname  = "Data Placement interface",
+               .type   = FIO_OPT_STR,
+               .off1   = offsetof(struct thread_options, dp_type),
+               .help   = "Data Placement interface to use",
+               .def    = "none",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_INVALID,
+               .posval = {
+                         { .ival = "none",
+                           .oval = FIO_DP_NONE,
+                           .help = "Do not specify a data placement interface",
+                         },
+                         { .ival = "fdp",
+                           .oval = FIO_DP_FDP,
+                           .help = "Use Flexible Data Placement interface",
+                         },
+                         { .ival = "streams",
+                           .oval = FIO_DP_STREAMS,
+                           .help = "Use Streams interface",
+                         },
+               },
+       },
+       {
+               .name   = "plid_select",
+               .alias  = "fdp_pli_select",
+               .lname  = "Data Placement ID selection strategy",
+               .type   = FIO_OPT_STR,
+               .off1   = offsetof(struct thread_options, dp_id_select),
+               .help   = "Strategy for selecting next Data Placement ID",
+               .def    = "roundrobin",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_INVALID,
+               .posval = {
+                         { .ival = "random",
+                           .oval = FIO_DP_RANDOM,
+                           .help = "Choose a Placement ID at random (uniform)",
+                         },
+                         { .ival = "roundrobin",
+                           .oval = FIO_DP_RR,
+                           .help = "Round robin select Placement IDs",
+                         },
+               },
+       },
+       {
+               .name   = "plids",
+               .alias  = "fdp_pli",
+               .lname  = "Stream IDs/Data Placement ID indices",
+               .type   = FIO_OPT_STR,
+               .cb     = str_fdp_pli_cb,
+               .off1   = offsetof(struct thread_options, dp_ids),
+               .help   = "Sets which Data Placement ids to use (defaults to all for FDP)",
+               .hide   = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_INVALID,
+       },
        {
                .name   = "lockmem",
                .lname  = "Lock memory",
@@ -3574,6 +3870,18 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .category = FIO_OPT_C_GENERAL,
                .group  = FIO_OPT_G_CRED,
        },
+       {
+               .name   = "priohint",
+               .lname  = "I/O nice priority hint",
+               .type   = FIO_OPT_INT,
+               .off1   = offsetof(struct thread_options, ioprio_hint),
+               .help   = "Set job IO priority hint",
+               .minval = IOPRIO_MIN_PRIO_HINT,
+               .maxval = IOPRIO_MAX_PRIO_HINT,
+               .interval = 1,
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_CRED,
+       },
 #else
        {
                .name   = "prioclass",
@@ -3581,6 +3889,12 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .type   = FIO_OPT_UNSUPPORTED,
                .help   = "Your platform does not support IO priority classes",
        },
+       {
+               .name   = "priohint",
+               .lname  = "I/O nice priority hint",
+               .type   = FIO_OPT_UNSUPPORTED,
+               .help   = "Your platform does not support IO priority hints",
+       },
 #endif
        {
                .name   = "thinktime",
@@ -3606,6 +3920,18 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .category = FIO_OPT_C_IO,
                .group  = FIO_OPT_G_THINKTIME,
        },
+       {
+               .name   = "thinkcycles",
+               .lname  = "Think cycles",
+               .type   = FIO_OPT_INT,
+               .off1   = offsetof(struct thread_options, thinkcycles),
+               .help   = "Spin for a constant amount of cycles between requests",
+               .def    = "0",
+               .parent = "thinktime",
+               .hide   = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_THINKTIME,
+       },
        {
                .name   = "thinktime_blocks",
                .lname  = "Thinktime blocks",
@@ -3640,6 +3966,20 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                },
                .parent = "thinktime",
        },
+       {
+               .name   = "thinktime_iotime",
+               .lname  = "Thinktime interval",
+               .type   = FIO_OPT_INT,
+               .off1   = offsetof(struct thread_options, thinktime_iotime),
+               .help   = "IO time interval between 'thinktime'",
+               .def    = "0",
+               .parent = "thinktime",
+               .hide   = 1,
+               .is_seconds = 1,
+               .is_time = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_THINKTIME,
+       },
        {
                .name   = "rate",
                .lname  = "I/O rate",
@@ -3738,8 +4078,10 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
        {
                .name   = "max_latency",
                .lname  = "Max Latency (usec)",
-               .type   = FIO_OPT_STR_VAL_TIME,
-               .off1   = offsetof(struct thread_options, max_latency),
+               .type   = FIO_OPT_ULL,
+               .off1   = offsetof(struct thread_options, max_latency[DDIR_READ]),
+               .off2   = offsetof(struct thread_options, max_latency[DDIR_WRITE]),
+               .off3   = offsetof(struct thread_options, max_latency[DDIR_TRIM]),
                .help   = "Maximum tolerated IO latency (usec)",
                .is_time = 1,
                .category = FIO_OPT_C_IO,
@@ -4188,6 +4530,18 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .category = FIO_OPT_C_LOG,
                .group  = FIO_OPT_G_INVALID,
        },
+       {
+               .name   = "log_entries",
+               .lname  = "Log entries",
+               .type   = FIO_OPT_INT,
+               .off1   = offsetof(struct thread_options, log_entries),
+               .help   = "Initial number of entries in a job IO log",
+               .def    = __fio_stringify(DEF_LOG_ENTRIES),
+               .minval = DEF_LOG_ENTRIES,
+               .maxval = MAX_LOG_ENTRIES,
+               .category = FIO_OPT_C_LOG,
+               .group  = FIO_OPT_G_INVALID,
+       },
        {
                .name   = "log_avg_msec",
                .lname  = "Log averaging (msec)",
@@ -4231,14 +4585,38 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .group  = FIO_OPT_G_INVALID,
        },
        {
-               .name   = "log_max_value",
-               .lname  = "Log maximum instead of average",
-               .type   = FIO_OPT_BOOL,
+               .name   = "log_window_value",
+               .alias  = "log_max_value",
+               .lname  = "Log maximum, average or both values",
+               .type   = FIO_OPT_STR,
                .off1   = offsetof(struct thread_options, log_max),
-               .help   = "Log max sample in a window instead of average",
-               .def    = "0",
+               .help   = "Log max, average or both sample in a window",
+               .def    = "avg",
                .category = FIO_OPT_C_LOG,
                .group  = FIO_OPT_G_INVALID,
+               .posval = {
+                         { .ival = "avg",
+                           .oval = IO_LOG_SAMPLE_AVG,
+                           .help = "Log average value over the window",
+                         },
+                         { .ival = "max",
+                           .oval = IO_LOG_SAMPLE_MAX,
+                           .help = "Log maximum value in the window",
+                         },
+                         { .ival = "both",
+                           .oval = IO_LOG_SAMPLE_BOTH,
+                           .help = "Log both average and maximum values over the window"
+                         },
+                         /* Compatibility with former boolean values */
+                         { .ival = "0",
+                           .oval = IO_LOG_SAMPLE_AVG,
+                           .help = "Alias for 'avg'",
+                         },
+                         { .ival = "1",
+                           .oval = IO_LOG_SAMPLE_MAX,
+                           .help = "Alias for 'max'",
+                         },
+               },
        },
        {
                .name   = "log_offset",
@@ -4250,6 +4628,16 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .category = FIO_OPT_C_LOG,
                .group  = FIO_OPT_G_INVALID,
        },
+       {
+               .name   = "log_prio",
+               .lname  = "Log priority of IO",
+               .type   = FIO_OPT_BOOL,
+               .off1   = offsetof(struct thread_options, log_prio),
+               .help   = "Include priority value of IO for each log entry",
+               .def    = "0",
+               .category = FIO_OPT_C_LOG,
+               .group  = FIO_OPT_G_INVALID,
+       },
 #ifdef CONFIG_ZLIB
        {
                .name   = "log_compression",
@@ -4306,11 +4694,21 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
        },
 #endif
        {
-               .name = "log_unix_epoch",
-               .lname = "Log epoch unix",
+               .name = "log_alternate_epoch",
+               .alias = "log_unix_epoch",
+               .lname = "Log epoch alternate",
                .type = FIO_OPT_BOOL,
-               .off1 = offsetof(struct thread_options, log_unix_epoch),
-               .help = "Use Unix time in log files",
+               .off1 = offsetof(struct thread_options, log_alternate_epoch),
+               .help = "Use alternate epoch time in log files. Uses the same epoch as that is used by clock_gettime with specified log_alternate_epoch_clock_id.",
+               .category = FIO_OPT_C_LOG,
+               .group = FIO_OPT_G_INVALID,
+       },
+       {
+               .name = "log_alternate_epoch_clock_id",
+               .lname = "Log alternate epoch clock_id",
+               .type = FIO_OPT_INT,
+               .off1 = offsetof(struct thread_options, log_alternate_epoch_clock_id),
+               .help = "If log_alternate_epoch is true, this option specifies the clock_id from clock_gettime whose epoch should be used. If log_alternate_epoch is false, this option has no effect. Default value is 0, or CLOCK_REALTIME",
                .category = FIO_OPT_C_LOG,
                .group = FIO_OPT_G_INVALID,
        },
@@ -4447,6 +4845,50 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .category = FIO_OPT_C_IO,
                .group  = FIO_OPT_G_IO_BUF,
        },
+       {
+               .name   = "dedupe_global",
+               .lname  = "Global deduplication",
+               .type   = FIO_OPT_BOOL,
+               .off1   = offsetof(struct thread_options, dedupe_global),
+               .help   = "Share deduplication buffers across jobs",
+               .def    = "0",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_IO_BUF,
+       },
+       {
+               .name   = "dedupe_mode",
+               .lname  = "Dedupe mode",
+               .help   = "Mode for the deduplication buffer generation",
+               .type   = FIO_OPT_STR,
+               .off1   = offsetof(struct thread_options, dedupe_mode),
+               .parent = "dedupe_percentage",
+               .def    = "repeat",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_IO_BUF,
+               .posval = {
+                          { .ival = "repeat",
+                            .oval = DEDUPE_MODE_REPEAT,
+                            .help = "repeat previous page",
+                          },
+                          { .ival = "working_set",
+                            .oval = DEDUPE_MODE_WORKING_SET,
+                            .help = "choose a page randomly from limited working set defined in dedupe_working_set_percentage",
+                          },
+               },
+       },
+       {
+               .name   = "dedupe_working_set_percentage",
+               .lname  = "Dedupe working set percentage",
+               .help   = "Dedupe working set size in percentages from file or device size used to generate dedupe patterns from",
+               .type   = FIO_OPT_INT,
+               .off1   = offsetof(struct thread_options, dedupe_working_set_percentage),
+               .parent = "dedupe_percentage",
+               .def    = "5",
+               .maxval = 100,
+               .minval = 0,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_IO_BUF,
+       },
        {
                .name   = "clat_percentiles",
                .lname  = "Completion latency percentiles",
@@ -4595,15 +5037,52 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .category = FIO_OPT_C_GENERAL,
                .group  = FIO_OPT_G_CLOCK,
        },
+       {
+               .name   = "job_start_clock_id",
+               .lname  = "Job start clock_id",
+               .type   = FIO_OPT_INT,
+               .off1   = offsetof(struct thread_options, job_start_clock_id),
+               .help   = "The clock_id passed to the call to clock_gettime used to record job_start in the json output format. Default is 0, or CLOCK_REALTIME",
+               .verify = gtod_cpu_verify,
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_CLOCK,
+       },
        {
                .name   = "unified_rw_reporting",
                .lname  = "Unified RW Reporting",
-               .type   = FIO_OPT_BOOL,
+               .type   = FIO_OPT_STR,
                .off1   = offsetof(struct thread_options, unified_rw_rep),
                .help   = "Unify reporting across data direction",
-               .def    = "0",
+               .def    = "none",
                .category = FIO_OPT_C_GENERAL,
                .group  = FIO_OPT_G_INVALID,
+               .posval = {
+                         { .ival = "none",
+                           .oval = UNIFIED_SPLIT,
+                           .help = "Normal statistics reporting",
+                         },
+                         { .ival = "mixed",
+                           .oval = UNIFIED_MIXED,
+                           .help = "Statistics are summed per data direction and reported together",
+                         },
+                         { .ival = "both",
+                           .oval = UNIFIED_BOTH,
+                           .help = "Statistics are reported normally, followed by the mixed statistics"
+                         },
+                         /* Compatibility with former boolean values */
+                         { .ival = "0",
+                           .oval = UNIFIED_SPLIT,
+                           .help = "Alias for 'none'",
+                         },
+                         { .ival = "1",
+                           .oval = UNIFIED_MIXED,
+                           .help = "Alias for 'mixed'",
+                         },
+                         { .ival = "2",
+                           .oval = UNIFIED_BOTH,
+                           .help = "Alias for 'both'",
+                         },
+               },
        },
        {
                .name   = "continue_on_error",
@@ -4887,6 +5366,20 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .category = FIO_OPT_C_GENERAL,
                .group  = FIO_OPT_G_RUNTIME,
        },
+        {
+               .name   = "steadystate_check_interval",
+               .lname  = "Steady state check interval",
+               .alias  = "ss_interval",
+               .parent = "steadystate",
+               .type   = FIO_OPT_STR_VAL_TIME,
+               .off1   = offsetof(struct thread_options, ss_check_interval),
+               .help   = "Polling interval for the steady state check (too low means steadystate will not converge)",
+               .def    = "1",
+               .is_seconds = 1,
+               .is_time = 1,
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_RUNTIME,
+       },
        {
                .name = NULL,
        },
@@ -5007,7 +5500,7 @@ void fio_keywords_init(void)
        sprintf(buf, "%llu", mb_memory);
        fio_keywords[1].replace = strdup(buf);
 
-       l = cpus_online();
+       l = cpus_configured();
        sprintf(buf, "%lu", l);
        fio_keywords[2].replace = strdup(buf);
 }
@@ -5436,6 +5929,19 @@ void fio_options_free(struct thread_data *td)
        }
 }
 
+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);
+       }
+}
+
 struct fio_option *fio_option_find(const char *name)
 {
        return find_option(fio_options, name);