X-Git-Url: https://git.kernel.dk/?p=fio.git;a=blobdiff_plain;f=options.c;h=26356ffdf12c9b4ca78dc99f08edbe040c53ac23;hp=cb8182de93c259a8ef2f3bc9141aa9795b2bbda4;hb=75e6bcba7e5824b8852590fa54963db64f05c70d;hpb=9599aaf191d6847010215e6ce0226994760b9f44 diff --git a/options.c b/options.c index cb8182de..26356ffd 100644 --- a/options.c +++ b/options.c @@ -14,12 +14,20 @@ #include "verify.h" #include "parse.h" #include "lib/fls.h" +#include "lib/pattern.h" #include "options.h" - -#include "crc/crc32c.h" +#include "optgroup.h" char client_sockaddr_str[INET6_ADDRSTRLEN] = { 0 }; +struct pattern_fmt_desc fmt_desc[] = { + { + .fmt = "%o", + .len = FIELD_SIZE(struct io_u *, offset), + .paste = paste_blockoff + } +}; + /* * Check if mmap/mmaphuge has a :/foo/bar/file at the end. If so, return that. */ @@ -36,63 +44,36 @@ static char *get_opt_postfix(const char *str) return strdup(p); } -static int converthexchartoint(char a) -{ - int base; - - switch (a) { - case '0'...'9': - base = '0'; - break; - case 'A'...'F': - base = 'A' - 10; - break; - case 'a'...'f': - base = 'a' - 10; - break; - default: - base = 0; - } - return a - base; -} - static int bs_cmp(const void *p1, const void *p2) { const struct bssplit *bsp1 = p1; const struct bssplit *bsp2 = p2; - return bsp1->perc < bsp2->perc; + return (int) bsp1->perc - (int) bsp2->perc; } -static int bssplit_ddir(struct thread_options *o, int ddir, char *str) +struct split { + unsigned int nr; + unsigned int val1[100]; + unsigned int val2[100]; +}; + +static int split_parse_ddir(struct thread_options *o, struct split *split, + enum fio_ddir ddir, char *str) { - struct bssplit *bssplit; - unsigned int i, perc, perc_missing; - unsigned int max_bs, min_bs; + unsigned int i, perc; long long val; char *fname; - o->bssplit_nr[ddir] = 4; - bssplit = malloc(4 * sizeof(struct bssplit)); + split->nr = 0; i = 0; - max_bs = 0; - min_bs = -1; while ((fname = strsep(&str, ":")) != NULL) { char *perc_str; if (!strlen(fname)) break; - /* - * grow struct buffer, if needed - */ - if (i == o->bssplit_nr[ddir]) { - o->bssplit_nr[ddir] <<= 1; - bssplit = realloc(bssplit, o->bssplit_nr[ddir] - * sizeof(struct bssplit)); - } - perc_str = strstr(fname, "/"); if (perc_str) { *perc_str = '\0'; @@ -107,28 +88,53 @@ static int bssplit_ddir(struct thread_options *o, int ddir, char *str) if (str_to_decimal(fname, &val, 1, o, 0, 0)) { log_err("fio: bssplit conversion failed\n"); - free(bssplit); return 1; } - if (val > max_bs) - max_bs = val; - if (val < min_bs) - min_bs = val; - - bssplit[i].bs = val; - bssplit[i].perc = perc; + split->val1[i] = val; + split->val2[i] = perc; i++; + if (i == 100) + break; } - o->bssplit_nr[ddir] = i; + split->nr = i; + return 0; +} + +static int bssplit_ddir(struct thread_options *o, enum fio_ddir ddir, char *str) +{ + unsigned int i, perc, perc_missing; + unsigned int max_bs, min_bs; + struct split split; + + memset(&split, 0, sizeof(split)); + + if (split_parse_ddir(o, &split, ddir, str)) + return 1; + if (!split.nr) + return 0; + + max_bs = 0; + min_bs = -1; + o->bssplit[ddir] = malloc(split.nr * sizeof(struct bssplit)); + o->bssplit_nr[ddir] = split.nr; + for (i = 0; i < split.nr; i++) { + if (split.val1[i] > max_bs) + max_bs = split.val1[i]; + if (split.val1[i] < min_bs) + min_bs = split.val1[i]; + + o->bssplit[ddir][i].bs = split.val1[i]; + o->bssplit[ddir][i].perc =split.val2[i]; + } /* * Now check if the percentages add up, and how much is missing */ perc = perc_missing = 0; for (i = 0; i < o->bssplit_nr[ddir]; i++) { - struct bssplit *bsp = &bssplit[i]; + struct bssplit *bsp = &o->bssplit[ddir][i]; if (bsp->perc == -1U) perc_missing++; @@ -138,7 +144,8 @@ static int bssplit_ddir(struct thread_options *o, int ddir, char *str) if (perc > 100 && perc_missing > 1) { log_err("fio: bssplit percentages add to more than 100%%\n"); - free(bssplit); + free(o->bssplit[ddir]); + o->bssplit[ddir] = NULL; return 1; } @@ -150,7 +157,7 @@ static int bssplit_ddir(struct thread_options *o, int ddir, char *str) if (perc_missing == 1 && o->bssplit_nr[ddir] == 1) perc = 100; for (i = 0; i < o->bssplit_nr[ddir]; i++) { - struct bssplit *bsp = &bssplit[i]; + struct bssplit *bsp = &o->bssplit[ddir][i]; if (bsp->perc == -1U) bsp->perc = (100 - perc) / perc_missing; @@ -163,59 +170,78 @@ static int bssplit_ddir(struct thread_options *o, int ddir, char *str) /* * now sort based on percentages, for ease of lookup */ - qsort(bssplit, o->bssplit_nr[ddir], sizeof(struct bssplit), bs_cmp); - o->bssplit[ddir] = bssplit; + qsort(o->bssplit[ddir], o->bssplit_nr[ddir], sizeof(struct bssplit), bs_cmp); return 0; } -static int str_bssplit_cb(void *data, const char *input) +typedef int (split_parse_fn)(struct thread_options *, enum fio_ddir, char *); + +static int str_split_parse(struct thread_data *td, char *str, split_parse_fn *fn) { - struct thread_data *td = data; - char *str, *p, *odir, *ddir; + char *odir, *ddir; int ret = 0; - if (parse_dryrun()) - return 0; - - p = str = strdup(input); - - strip_blank_front(&str); - strip_blank_end(str); - odir = strchr(str, ','); if (odir) { ddir = strchr(odir + 1, ','); if (ddir) { - ret = bssplit_ddir(&td->o, DDIR_TRIM, ddir + 1); + ret = fn(&td->o, DDIR_TRIM, ddir + 1); if (!ret) *ddir = '\0'; } else { char *op; op = strdup(odir + 1); - ret = bssplit_ddir(&td->o, DDIR_TRIM, op); + ret = fn(&td->o, DDIR_TRIM, op); free(op); } if (!ret) - ret = bssplit_ddir(&td->o, DDIR_WRITE, odir + 1); + ret = fn(&td->o, DDIR_WRITE, odir + 1); if (!ret) { *odir = '\0'; - ret = bssplit_ddir(&td->o, DDIR_READ, str); + ret = fn(&td->o, DDIR_READ, str); } } else { char *op; op = strdup(str); - ret = bssplit_ddir(&td->o, DDIR_WRITE, op); + ret = fn(&td->o, DDIR_WRITE, op); free(op); if (!ret) { op = strdup(str); - ret = bssplit_ddir(&td->o, DDIR_TRIM, op); + ret = fn(&td->o, DDIR_TRIM, op); free(op); } - ret = bssplit_ddir(&td->o, DDIR_READ, str); + if (!ret) + ret = fn(&td->o, DDIR_READ, str); + } + + return ret; +} + +static int str_bssplit_cb(void *data, const char *input) +{ + struct thread_data *td = data; + char *str, *p; + int ret = 0; + + p = str = strdup(input); + + strip_blank_front(&str); + strip_blank_end(str); + + ret = str_split_parse(td, str, bssplit_ddir); + + if (parse_dryrun()) { + int i; + + for (i = 0; i < DDIR_RWDIR_CNT; i++) { + free(td->o.bssplit[i]); + td->o.bssplit[i] = NULL; + td->o.bssplit_nr[i] = 0; + } } free(p); @@ -362,7 +388,8 @@ static int str_mem_cb(void *data, const char *mem) { struct thread_data *td = data; - if (td->o.mem_type == MEM_MMAPHUGE || td->o.mem_type == MEM_MMAP) + if (td->o.mem_type == MEM_MMAPHUGE || td->o.mem_type == MEM_MMAP || + td->o.mem_type == MEM_MMAPSHARED) td->o.mmapfile = get_opt_postfix(mem); return 0; @@ -539,9 +566,25 @@ static int str_verify_cpus_allowed_cb(void *data, const char *input) { struct thread_data *td = data; + if (parse_dryrun()) + return 0; + return set_cpus_allowed(td, &td->o.verify_cpumask, input); } -#endif + +#ifdef CONFIG_ZLIB +static int str_log_cpus_allowed_cb(void *data, const char *input) +{ + struct thread_data *td = data; + + if (parse_dryrun()) + return 0; + + return set_cpus_allowed(td, &td->o.log_gz_cpumask, input); +} +#endif /* CONFIG_ZLIB */ + +#endif /* FIO_HAVE_CPU_AFFINITY */ #ifdef CONFIG_LIBNUMA static int str_numa_cpunodes_cb(void *data, char *input) @@ -681,12 +724,77 @@ out: static int str_fst_cb(void *data, const char *str) { struct thread_data *td = data; - char *nr = get_opt_postfix(str); + double val; + bool done = false; + char *nr; td->file_service_nr = 1; - if (nr) { - td->file_service_nr = atoi(nr); + + switch (td->o.file_service_type) { + case FIO_FSERVICE_RANDOM: + case FIO_FSERVICE_RR: + case FIO_FSERVICE_SEQ: + nr = get_opt_postfix(str); + if (nr) { + td->file_service_nr = atoi(nr); + free(nr); + } + done = true; + break; + case FIO_FSERVICE_ZIPF: + val = FIO_DEF_ZIPF; + break; + case FIO_FSERVICE_PARETO: + val = FIO_DEF_PARETO; + break; + case FIO_FSERVICE_GAUSS: + val = 0.0; + break; + default: + log_err("fio: bad file service type: %d\n", td->o.file_service_type); + return 1; + } + + if (done) + return 0; + + nr = get_opt_postfix(str); + if (nr && !str_to_float(nr, &val, 0)) { + log_err("fio: file service type random postfix parsing failed\n"); free(nr); + return 1; + } + + free(nr); + + switch (td->o.file_service_type) { + case FIO_FSERVICE_ZIPF: + if (val == 1.00) { + log_err("fio: zipf theta must be different than 1.0\n"); + return 1; + } + if (parse_dryrun()) + return 0; + td->zipf_theta = val; + break; + case FIO_FSERVICE_PARETO: + if (val <= 0.00 || val >= 1.00) { + log_err("fio: pareto input out of range (0 < input < 1.0)\n"); + return 1; + } + if (parse_dryrun()) + return 0; + td->pareto_h = val; + break; + case FIO_FSERVICE_GAUSS: + if (val < 0.00 || val >= 100.00) { + log_err("fio: normal deviation out of range (0 <= input < 100.0)\n"); + return 1; + } + if (parse_dryrun()) + return 0; + td->gauss_dev = val; + break; } return 0; @@ -708,21 +816,208 @@ static int str_sfr_cb(void *data, const char *str) } #endif +static int zone_cmp(const void *p1, const void *p2) +{ + const struct zone_split *zsp1 = p1; + const struct zone_split *zsp2 = p2; + + return (int) zsp2->access_perc - (int) zsp1->access_perc; +} + +static int zone_split_ddir(struct thread_options *o, enum fio_ddir ddir, + char *str) +{ + unsigned int i, perc, perc_missing, sperc, sperc_missing; + struct split split; + + memset(&split, 0, sizeof(split)); + + if (split_parse_ddir(o, &split, ddir, str)) + return 1; + if (!split.nr) + return 0; + + o->zone_split[ddir] = malloc(split.nr * sizeof(struct zone_split)); + o->zone_split_nr[ddir] = split.nr; + for (i = 0; i < split.nr; i++) { + o->zone_split[ddir][i].access_perc = split.val1[i]; + o->zone_split[ddir][i].size_perc = split.val2[i]; + } + + /* + * Now check if the percentages add up, and how much is missing + */ + perc = perc_missing = 0; + sperc = sperc_missing = 0; + for (i = 0; i < o->zone_split_nr[ddir]; i++) { + struct zone_split *zsp = &o->zone_split[ddir][i]; + + if (zsp->access_perc == (uint8_t) -1U) + perc_missing++; + else + perc += zsp->access_perc; + + if (zsp->size_perc == (uint8_t) -1U) + sperc_missing++; + else + sperc += zsp->size_perc; + + } + + if (perc > 100 || sperc > 100) { + log_err("fio: zone_split percentages add to more than 100%%\n"); + free(o->zone_split[ddir]); + o->zone_split[ddir] = NULL; + return 1; + } + if (perc < 100) { + log_err("fio: access percentage don't add up to 100 for zoned " + "random distribution (got=%u)\n", perc); + free(o->zone_split[ddir]); + o->zone_split[ddir] = NULL; + return 1; + } + + /* + * If values didn't have a percentage set, divide the remains between + * them. + */ + if (perc_missing) { + if (perc_missing == 1 && o->zone_split_nr[ddir] == 1) + perc = 100; + for (i = 0; i < o->zone_split_nr[ddir]; i++) { + struct zone_split *zsp = &o->zone_split[ddir][i]; + + if (zsp->access_perc == (uint8_t) -1U) + zsp->access_perc = (100 - perc) / perc_missing; + } + } + if (sperc_missing) { + if (sperc_missing == 1 && o->zone_split_nr[ddir] == 1) + sperc = 100; + for (i = 0; i < o->zone_split_nr[ddir]; i++) { + struct zone_split *zsp = &o->zone_split[ddir][i]; + + if (zsp->size_perc == (uint8_t) -1U) + zsp->size_perc = (100 - sperc) / sperc_missing; + } + } + + /* + * now sort based on percentages, for ease of lookup + */ + qsort(o->zone_split[ddir], o->zone_split_nr[ddir], sizeof(struct zone_split), zone_cmp); + return 0; +} + +static void __td_zone_gen_index(struct thread_data *td, enum fio_ddir ddir) +{ + unsigned int i, j, sprev, aprev; + + td->zone_state_index[ddir] = malloc(sizeof(struct zone_split_index) * 100); + + sprev = aprev = 0; + for (i = 0; i < td->o.zone_split_nr[ddir]; i++) { + struct zone_split *zsp = &td->o.zone_split[ddir][i]; + + for (j = aprev; j < aprev + zsp->access_perc; j++) { + struct zone_split_index *zsi = &td->zone_state_index[ddir][j]; + + zsi->size_perc = sprev + zsp->size_perc; + zsi->size_perc_prev = sprev; + } + + aprev += zsp->access_perc; + sprev += zsp->size_perc; + } +} + +/* + * Generate state table for indexes, so we don't have to do it inline from + * the hot IO path + */ +static void td_zone_gen_index(struct thread_data *td) +{ + int i; + + td->zone_state_index = malloc(DDIR_RWDIR_CNT * + sizeof(struct zone_split_index *)); + + for (i = 0; i < DDIR_RWDIR_CNT; i++) + __td_zone_gen_index(td, i); +} + +static int parse_zoned_distribution(struct thread_data *td, const char *input) +{ + char *str, *p; + int i, ret = 0; + + p = str = strdup(input); + + strip_blank_front(&str); + strip_blank_end(str); + + /* We expect it to start like that, bail if not */ + if (strncmp(str, "zoned:", 6)) { + log_err("fio: mismatch in zoned input <%s>\n", str); + free(p); + return 1; + } + str += strlen("zoned:"); + + ret = str_split_parse(td, str, zone_split_ddir); + + free(p); + + for (i = 0; i < DDIR_RWDIR_CNT; i++) { + int j; + + dprint(FD_PARSE, "zone ddir %d (nr=%u): \n", i, td->o.zone_split_nr[i]); + + for (j = 0; j < td->o.zone_split_nr[i]; j++) { + struct zone_split *zsp = &td->o.zone_split[i][j]; + + dprint(FD_PARSE, "\t%d: %u/%u\n", j, zsp->access_perc, + zsp->size_perc); + } + } + + if (parse_dryrun()) { + int i; + + for (i = 0; i < DDIR_RWDIR_CNT; i++) { + free(td->o.zone_split[i]); + td->o.zone_split[i] = NULL; + td->o.zone_split_nr[i] = 0; + } + + return ret; + } + + if (!ret) + td_zone_gen_index(td); + else { + for (i = 0; i < DDIR_RWDIR_CNT; i++) + td->o.zone_split_nr[i] = 0; + } + + return ret; +} + static int str_random_distribution_cb(void *data, const char *str) { struct thread_data *td = data; double val; char *nr; - if (parse_dryrun()) - return 0; - if (td->o.random_distribution == FIO_RAND_DIST_ZIPF) val = FIO_DEF_ZIPF; else if (td->o.random_distribution == FIO_RAND_DIST_PARETO) val = FIO_DEF_PARETO; else if (td->o.random_distribution == FIO_RAND_DIST_GAUSS) val = 0.0; + else if (td->o.random_distribution == FIO_RAND_DIST_ZONED) + return parse_zoned_distribution(td, str); else return 0; @@ -740,18 +1035,24 @@ static int str_random_distribution_cb(void *data, const char *str) log_err("fio: zipf theta must different than 1.0\n"); return 1; } + if (parse_dryrun()) + return 0; td->o.zipf_theta.u.f = val; } else if (td->o.random_distribution == FIO_RAND_DIST_PARETO) { if (val <= 0.00 || val >= 1.00) { log_err("fio: pareto input out of range (0 < input < 1.0)\n"); return 1; } + if (parse_dryrun()) + return 0; td->o.pareto_h.u.f = val; } else { - if (val <= 0.00 || val >= 100.0) { - log_err("fio: normal deviation out of range (0 < input < 100.0)\n"); + if (val < 0.00 || val >= 100.0) { + log_err("fio: normal deviation out of range (0 <= input < 100.0)\n"); return 1; } + if (parse_dryrun()) + return 0; td->o.gauss_dev.u.f = val; } @@ -823,7 +1124,8 @@ static int get_max_name_idx(char *input) * Returns the directory at the index, indexes > entires will be * assigned via modulo division of the index */ -int set_name_idx(char *target, size_t tlen, char *input, int index) +int set_name_idx(char *target, size_t tlen, char *input, int index, + bool unique_filename) { unsigned int cur_idx; int len; @@ -835,7 +1137,7 @@ int set_name_idx(char *target, size_t tlen, char *input, int index) for (cur_idx = 0; cur_idx <= index; cur_idx++) fname = get_next_name(&str); - if (client_sockaddr_str[0]) { + if (client_sockaddr_str[0] && unique_filename) { len = snprintf(target, tlen, "%s/%s.", fname, client_sockaddr_str); } else @@ -914,122 +1216,25 @@ static int str_opendir_cb(void *data, const char fio_unused *str) return add_dir_files(td, td->o.opendir); } -static int pattern_cb(char *pattern, unsigned int max_size, - const char *input, unsigned int *pattern_bytes) -{ - long off; - int i = 0, j = 0, len, k, base = 10; - uint32_t pattern_length; - char *loc1, *loc2; - - /* - * Check if it's a string input - */ - loc1 = strchr(input, '\"'); - if (loc1) { - do { - loc1++; - if (*loc1 == '\0' || *loc1 == '\"') - break; - - pattern[i] = *loc1; - i++; - } while (i < max_size); - - if (!i) - return 1; - - goto fill; - } - - /* - * No string, find out if it's decimal or hexidecimal - */ - loc1 = strstr(input, "0x"); - loc2 = strstr(input, "0X"); - if (loc1 || loc2) - base = 16; - off = strtol(input, NULL, base); - if (off != LONG_MAX || errno != ERANGE) { - while (off) { - pattern[i] = off & 0xff; - off >>= 8; - i++; - } - } else { - len = strlen(input); - k = len - 1; - if (base == 16) { - if (loc1) - j = loc1 - input + 2; - else - j = loc2 - input + 2; - } else - return 1; - if (len - j < max_size * 2) { - while (k >= j) { - off = converthexchartoint(input[k--]); - if (k >= j) - off += (converthexchartoint(input[k--]) - * 16); - pattern[i++] = (char) off; - } - } - } - - /* - * Fill the pattern all the way to the end. This greatly reduces - * the number of memcpy's we have to do when verifying the IO. - */ -fill: - pattern_length = i; - while (i > 1 && i * 2 <= max_size) { - memcpy(&pattern[i], &pattern[0], i); - i *= 2; - } - - /* - * Fill remainder, if the pattern multiple ends up not being - * max_size. - */ - while (i > 1 && i < max_size) { - unsigned int b = min(pattern_length, max_size - i); - - memcpy(&pattern[i], &pattern[0], b); - i += b; - } - - if (i == 1) { - /* - * The code in verify_io_u_pattern assumes a single byte - * pattern fills the whole verify pattern buffer. - */ - memset(pattern, pattern[0], max_size); - } - - *pattern_bytes = i; - return 0; -} - static int str_buffer_pattern_cb(void *data, const char *input) { struct thread_data *td = data; int ret; - ret = pattern_cb(td->o.buffer_pattern, MAX_PATTERN_SIZE, input, - &td->o.buffer_pattern_bytes); + /* 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, 0, NULL, NULL); + if (ret < 0) + return 1; - if (!ret && td->o.buffer_pattern_bytes) { - if (!td->o.compress_percentage) - td->o.refill_buffers = 0; - td->o.scramble_buffers = 0; - td->o.zero_buffers = 0; - } else { - log_err("fio: failed parsing pattern `%s`\n", input); - ret = 1; - } + assert(ret != 0); + td->o.buffer_pattern_bytes = ret; + if (!td->o.compress_percentage) + td->o.refill_buffers = 0; + td->o.scramble_buffers = 0; + td->o.zero_buffers = 0; - return ret; + return 0; } static int str_buffer_compress_cb(void *data, unsigned long long *il) @@ -1056,16 +1261,22 @@ static int str_verify_pattern_cb(void *data, const char *input) struct thread_data *td = data; int ret; - ret = pattern_cb(td->o.verify_pattern, MAX_PATTERN_SIZE, input, - &td->o.verify_pattern_bytes); + td->o.verify_fmt_sz = ARRAY_SIZE(td->o.verify_fmt); + ret = parse_and_fill_pattern(input, strlen(input), td->o.verify_pattern, + MAX_PATTERN_SIZE, fmt_desc, sizeof(fmt_desc), + td->o.verify_fmt, &td->o.verify_fmt_sz); + if (ret < 0) + return 1; + assert(ret != 0); + td->o.verify_pattern_bytes = ret; /* - * VERIFY_META could already be set + * VERIFY_* could already be set */ - if (!ret && td->o.verify == VERIFY_NONE) + if (!fio_option_is_set(&td->o, verify)) td->o.verify = VERIFY_PATTERN; - return ret; + return 0; } static int str_gtod_reduce_cb(void *data, int *il) @@ -1126,169 +1337,6 @@ static int gtod_cpu_verify(struct fio_option *o, void *data) return 0; } -/* - * Option grouping - */ -static struct opt_group fio_opt_groups[] = { - { - .name = "General", - .mask = FIO_OPT_C_GENERAL, - }, - { - .name = "I/O", - .mask = FIO_OPT_C_IO, - }, - { - .name = "File", - .mask = FIO_OPT_C_FILE, - }, - { - .name = "Statistics", - .mask = FIO_OPT_C_STAT, - }, - { - .name = "Logging", - .mask = FIO_OPT_C_LOG, - }, - { - .name = "Profiles", - .mask = FIO_OPT_C_PROFILE, - }, - { - .name = NULL, - }, -}; - -static struct opt_group *__opt_group_from_mask(struct opt_group *ogs, unsigned int *mask, - unsigned int inv_mask) -{ - struct opt_group *og; - int i; - - if (*mask == inv_mask || !*mask) - return NULL; - - for (i = 0; ogs[i].name; i++) { - og = &ogs[i]; - - if (*mask & og->mask) { - *mask &= ~(og->mask); - return og; - } - } - - return NULL; -} - -struct opt_group *opt_group_from_mask(unsigned int *mask) -{ - return __opt_group_from_mask(fio_opt_groups, mask, FIO_OPT_C_INVALID); -} - -static struct opt_group fio_opt_cat_groups[] = { - { - .name = "Latency profiling", - .mask = FIO_OPT_G_LATPROF, - }, - { - .name = "Rate", - .mask = FIO_OPT_G_RATE, - }, - { - .name = "Zone", - .mask = FIO_OPT_G_ZONE, - }, - { - .name = "Read/write mix", - .mask = FIO_OPT_G_RWMIX, - }, - { - .name = "Verify", - .mask = FIO_OPT_G_VERIFY, - }, - { - .name = "Trim", - .mask = FIO_OPT_G_TRIM, - }, - { - .name = "I/O Logging", - .mask = FIO_OPT_G_IOLOG, - }, - { - .name = "I/O Depth", - .mask = FIO_OPT_G_IO_DEPTH, - }, - { - .name = "I/O Flow", - .mask = FIO_OPT_G_IO_FLOW, - }, - { - .name = "Description", - .mask = FIO_OPT_G_DESC, - }, - { - .name = "Filename", - .mask = FIO_OPT_G_FILENAME, - }, - { - .name = "General I/O", - .mask = FIO_OPT_G_IO_BASIC, - }, - { - .name = "Cgroups", - .mask = FIO_OPT_G_CGROUP, - }, - { - .name = "Runtime", - .mask = FIO_OPT_G_RUNTIME, - }, - { - .name = "Process", - .mask = FIO_OPT_G_PROCESS, - }, - { - .name = "Job credentials / priority", - .mask = FIO_OPT_G_CRED, - }, - { - .name = "Clock settings", - .mask = FIO_OPT_G_CLOCK, - }, - { - .name = "I/O Type", - .mask = FIO_OPT_G_IO_TYPE, - }, - { - .name = "I/O Thinktime", - .mask = FIO_OPT_G_THINKTIME, - }, - { - .name = "Randomizations", - .mask = FIO_OPT_G_RANDOM, - }, - { - .name = "I/O buffers", - .mask = FIO_OPT_G_IO_BUF, - }, - { - .name = "Tiobench profile", - .mask = FIO_OPT_G_TIOBENCH, - }, - { - .name = "MTD", - .mask = FIO_OPT_G_MTD, - }, - - { - .name = NULL, - } -}; - -struct opt_group *opt_group_cat_from_mask(unsigned int *mask) -{ - return __opt_group_from_mask(fio_opt_cat_groups, mask, FIO_OPT_G_INVALID); -} - /* * Map of job/command line options */ @@ -1311,6 +1359,15 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_DESC, }, + { + .name = "wait_for", + .lname = "Waitee name", + .type = FIO_OPT_STR_STORE, + .off1 = td_var_offset(wait_for), + .help = "Name of the job this one wants to wait for before starting", + .category = FIO_OPT_C_GENERAL, + .group = FIO_OPT_G_DESC, + }, { .name = "filename", .lname = "Filename(s)", @@ -1334,6 +1391,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { }, { .name = "filename_format", + .lname = "Filename Format", .type = FIO_OPT_STR_STORE, .off1 = td_var_offset(filename_format), .prio = -1, /* must come after "directory" */ @@ -1342,6 +1400,16 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .category = FIO_OPT_C_FILE, .group = FIO_OPT_G_FILENAME, }, + { + .name = "unique_filename", + .lname = "Unique Filename", + .type = FIO_OPT_BOOL, + .off1 = td_var_offset(unique_filename), + .help = "For network clients, prefix file with source IP", + .def = "1", + .category = FIO_OPT_C_FILE, + .group = FIO_OPT_G_FILENAME, + }, { .name = "lockfile", .lname = "Lockfile", @@ -1480,6 +1548,11 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .help = "Use preadv/pwritev", }, #endif +#ifdef FIO_HAVE_PWRITEV2 + { .ival = "pvsync2", + .help = "Use preadv2/pwritev2", + }, +#endif #ifdef CONFIG_LIBAIO { .ival = "libaio", .help = "Linux native asynchronous IO", @@ -1572,6 +1645,12 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { { .ival = "libhdfs", .help = "Hadoop Distributed Filesystem (HDFS) engine" }, +#endif +#ifdef CONFIG_PMEMBLK + { .ival = "pmemblk", + .help = "NVML libpmemblk based IO engine", + }, + #endif { .ival = "external", .help = "Load external engine (append name)", @@ -1599,18 +1678,18 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .help = "Number of IO buffers to submit in one go", .parent = "iodepth", .hide = 1, - .minval = 1, .interval = 1, .def = "1", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_IO_BASIC, }, { - .name = "iodepth_batch_complete", - .lname = "IO Depth batch complete", + .name = "iodepth_batch_complete_min", + .lname = "Min IO depth batch complete", + .alias = "iodepth_batch_complete", .type = FIO_OPT_INT, - .off1 = td_var_offset(iodepth_batch_complete), - .help = "Number of IO buffers to retrieve in one go", + .off1 = td_var_offset(iodepth_batch_complete_min), + .help = "Min number of IO buffers to retrieve in one go", .parent = "iodepth", .hide = 1, .minval = 0, @@ -1619,6 +1698,19 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .category = FIO_OPT_C_IO, .group = FIO_OPT_G_IO_BASIC, }, + { + .name = "iodepth_batch_complete_max", + .lname = "Max IO depth batch complete", + .type = FIO_OPT_INT, + .off1 = td_var_offset(iodepth_batch_complete_max), + .help = "Max number of IO buffers to retrieve in one go", + .parent = "iodepth", + .hide = 1, + .minval = 0, + .interval = 1, + .category = FIO_OPT_C_IO, + .group = FIO_OPT_G_IO_BASIC, + }, { .name = "iodepth_low", .lname = "IO Depth batch low", @@ -1845,6 +1937,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .type = FIO_OPT_STR_VAL, .off1 = td_var_offset(rand_seed), .help = "Set the random generator seed value", + .def = "0x89", .parent = "rw", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_RANDOM, @@ -1883,6 +1976,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { }, { .name = "random_generator", + .lname = "Random Generator", .type = FIO_OPT_STR, .off1 = td_var_offset(random_generator), .help = "Type of random number generator to use", @@ -1896,12 +1990,18 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .oval = FIO_RAND_GEN_LFSR, .help = "Variable length LFSR", }, + { + .ival = "tausworthe64", + .oval = FIO_RAND_GEN_TAUSWORTHE64, + .help = "64-bit Tausworthe variant", + }, }, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_RANDOM, }, { .name = "random_distribution", + .lname = "Random Distribution", .type = FIO_OPT_STR, .off1 = td_var_offset(random_distribution), .cb = str_random_distribution_cb, @@ -1922,8 +2022,13 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { }, { .ival = "normal", .oval = FIO_RAND_DIST_GAUSS, - .help = "Normal (gaussian) distribution", + .help = "Normal (Gaussian) distribution", + }, + { .ival = "zoned", + .oval = FIO_RAND_DIST_ZONED, + .help = "Zoned random distribution", }, + }, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_RANDOM, @@ -1952,6 +2057,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { }, { .name = "allrandrepeat", + .lname = "All Random Repeat", .type = FIO_OPT_BOOL, .off1 = td_var_offset(allrand_repeatable), .help = "Use repeatable random numbers for everything", @@ -1993,7 +2099,19 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .posval = { { .ival = "random", .oval = FIO_FSERVICE_RANDOM, - .help = "Choose a file at random", + .help = "Choose a file at random (uniform)", + }, + { .ival = "zipf", + .oval = FIO_FSERVICE_ZIPF, + .help = "Zipf randomized", + }, + { .ival = "pareto", + .oval = FIO_FSERVICE_PARETO, + .help = "Pareto randomized", + }, + { .ival = "gauss", + .oval = FIO_FSERVICE_GAUSS, + .help = "Normal (guassian) distribution", }, { .ival = "roundrobin", .oval = FIO_FSERVICE_RR, @@ -2310,6 +2428,10 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .oval = MEM_MMAP, .help = "Use mmap(2) (file or anon) for IO buffers", }, + { .ival = "mmapshared", + .oval = MEM_MMAPSHARED, + .help = "Like mmap, but use the shared flag", + }, #ifdef FIO_HAVE_HUGETLB { .ival = "mmaphuge", .oval = MEM_MMAPHUGE, @@ -2390,9 +2512,17 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .oval = VERIFY_XXHASH, .help = "Use xxhash checksums for verification", }, + /* Meta information was included into verify_header, + * 'meta' verification is implied by default. */ { .ival = "meta", - .oval = VERIFY_META, - .help = "Use io information", + .oval = VERIFY_HDR_ONLY, + .help = "Use io information for verification. " + "Now is implied by default, thus option is obsolete, " + "don't use it", + }, + { .ival = "pattern", + .oval = VERIFY_PATTERN_NO_HDR, + .help = "Verify strict pattern", }, { .ival = "null", @@ -2427,6 +2557,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { }, { .name = "verifysort_nr", + .lname = "Verify Sort Nr", .type = FIO_OPT_INT, .off1 = td_var_offset(verifysort_nr), .help = "Pre-load and sort verify blocks for a read workload", @@ -2548,6 +2679,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { #endif { .name = "experimental_verify", + .lname = "Experimental Verify", .off1 = td_var_offset(experimental_verify), .type = FIO_OPT_BOOL, .help = "Enable experimental verification", @@ -2887,7 +3019,8 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .group = FIO_OPT_G_RATE, }, { - .name = "ratemin", + .name = "rate_min", + .alias = "ratemin", .lname = "I/O min rate", .type = FIO_OPT_INT, .off1 = td_var_offset(ratemin[DDIR_READ]), @@ -2925,7 +3058,30 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .group = FIO_OPT_G_RATE, }, { - .name = "ratecycle", + .name = "rate_process", + .lname = "Rate Process", + .type = FIO_OPT_STR, + .off1 = td_var_offset(rate_process), + .help = "What process controls how rated IO is managed", + .def = "linear", + .category = FIO_OPT_C_IO, + .group = FIO_OPT_G_RATE, + .posval = { + { .ival = "linear", + .oval = RATE_PROCESS_LINEAR, + .help = "Linear rate of IO", + }, + { + .ival = "poisson", + .oval = RATE_PROCESS_POISSON, + .help = "Rate follows Poisson process", + }, + }, + .parent = "rate", + }, + { + .name = "rate_cycle", + .alias = "ratecycle", .lname = "I/O rate cycle", .type = FIO_OPT_INT, .off1 = td_var_offset(ratecycle), @@ -2938,6 +3094,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { }, { .name = "max_latency", + .lname = "Max Latency", .type = FIO_OPT_INT, .off1 = td_var_offset(max_latency), .help = "Maximum tolerated IO latency (usec)", @@ -3032,12 +3189,33 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { }, { .name = "create_only", + .lname = "Create Only", .type = FIO_OPT_BOOL, .off1 = td_var_offset(create_only), .help = "Only perform file creation phase", .category = FIO_OPT_C_FILE, .def = "0", }, + { + .name = "allow_file_create", + .lname = "Allow file create", + .type = FIO_OPT_BOOL, + .off1 = td_var_offset(allow_create), + .help = "Permit fio to create files, if they don't exist", + .def = "1", + .category = FIO_OPT_C_FILE, + .group = FIO_OPT_G_FILENAME, + }, + { + .name = "allow_mounted_write", + .lname = "Allow mounted write", + .type = FIO_OPT_BOOL, + .off1 = td_var_offset(allow_mounted_write), + .help = "Allow writes to a mounted partition", + .def = "0", + .category = FIO_OPT_C_FILE, + .group = FIO_OPT_G_FILENAME, + }, { .name = "pre_read", .lname = "Pre-read files", @@ -3094,6 +3272,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { #ifdef CONFIG_LIBNUMA { .name = "numa_cpu_nodes", + .lname = "NUMA CPU Nodes", .type = FIO_OPT_STR, .cb = str_numa_cpunodes_cb, .off1 = td_var_offset(numa_cpunodes), @@ -3103,6 +3282,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { }, { .name = "numa_mem_policy", + .lname = "NUMA Memory Policy", .type = FIO_OPT_STR, .cb = str_numa_mpol_cb, .off1 = td_var_offset(numa_memnodes), @@ -3150,6 +3330,15 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_PROCESS, }, + { + .name = "exitall_on_error", + .lname = "Exit-all on terminate in error", + .type = FIO_OPT_BOOL, + .off1 = td_var_offset(unlink), + .help = "Terminate all jobs when one exits in error", + .category = FIO_OPT_C_GENERAL, + .group = FIO_OPT_G_PROCESS, + }, { .name = "stonewall", .lname = "Wait for previous", @@ -3182,6 +3371,16 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_PROCESS, }, + { + .name = "per_job_logs", + .lname = "Per Job Logs", + .type = FIO_OPT_BOOL, + .off1 = td_var_offset(per_job_logs), + .help = "Include job number in generated log files or not", + .def = "1", + .category = FIO_OPT_C_LOG, + .group = FIO_OPT_G_INVALID, + }, { .name = "write_bw_log", .lname = "Write bandwidth log", @@ -3219,6 +3418,16 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .category = FIO_OPT_C_LOG, .group = FIO_OPT_G_INVALID, }, + { + .name = "log_max_value", + .lname = "Log maximum instead of average", + .type = FIO_OPT_BOOL, + .off1 = td_var_offset(log_max), + .help = "Log max sample in a window instead of average", + .def = "0", + .category = FIO_OPT_C_LOG, + .group = FIO_OPT_G_INVALID, + }, { .name = "log_offset", .lname = "Log offset of IO", @@ -3236,11 +3445,24 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .type = FIO_OPT_INT, .off1 = td_var_offset(log_gz), .help = "Log in compressed chunks of this size", - .minval = 32 * 1024 * 1024ULL, + .minval = 1024ULL, .maxval = 512 * 1024 * 1024ULL, .category = FIO_OPT_C_LOG, .group = FIO_OPT_G_INVALID, }, +#ifdef FIO_HAVE_CPU_AFFINITY + { + .name = "log_compression_cpus", + .lname = "Log Compression CPUs", + .type = FIO_OPT_STR, + .cb = str_log_cpus_allowed_cb, + .off1 = td_var_offset(log_gz_cpumask), + .parent = "log_compression", + .help = "Limit log compression to these CPUs", + .category = FIO_OPT_C_LOG, + .group = FIO_OPT_G_INVALID, + }, +#endif { .name = "log_store_compressed", .lname = "Log store compressed", @@ -3482,6 +3704,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { }, { .name = "unified_rw_reporting", + .lname = "Unified RW Reporting", .type = FIO_OPT_BOOL, .off1 = td_var_offset(unified_rw_rep), .help = "Unify reporting across data direction", @@ -3535,6 +3758,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { }, { .name = "ignore_error", + .lname = "Ignore Error", .type = FIO_OPT_STR, .cb = str_ignore_error_cb, .off1 = td_var_offset(ignore_error_nr), @@ -3545,6 +3769,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { }, { .name = "error_dump", + .lname = "Error Dump", .type = FIO_OPT_BOOL, .off1 = td_var_offset(error_dump), .def = "0", @@ -3822,6 +4047,18 @@ static struct fio_keyword fio_keywords[] = { }, }; +void fio_keywords_exit(void) +{ + struct fio_keyword *kw; + + kw = &fio_keywords[0]; + while (kw->word) { + free(kw->replace); + kw->replace = NULL; + kw++; + } +} + void fio_keywords_init(void) { unsigned long long mb_memory; @@ -4040,14 +4277,14 @@ static void show_closest_option(const char *opt) i++; } - if (best_option != -1) + if (best_option != -1 && string_distance_ok(name, best_distance) && + fio_options[best_option].type != FIO_OPT_UNSUPPORTED) log_err("Did you mean %s?\n", fio_options[best_option].name); free(name); } -int fio_options_parse(struct thread_data *td, char **opts, int num_opts, - int dump_cmdline) +int fio_options_parse(struct thread_data *td, char **opts, int num_opts) { int i, ret, unknown; char **opts_copy; @@ -4058,7 +4295,7 @@ int fio_options_parse(struct thread_data *td, char **opts, int num_opts, for (ret = 0, i = 0, unknown = 0; i < num_opts; i++) { struct fio_option *o; int newret = parse_option(opts_copy[i], opts[i], fio_options, - &o, td, dump_cmdline); + &o, td, &td->opt_list); if (!newret && o) fio_option_mark_set(&td->o, o); @@ -4091,7 +4328,7 @@ int fio_options_parse(struct thread_data *td, char **opts, int num_opts, if (td->eo) newret = parse_option(opts_copy[i], opts[i], td->io_ops->options, &o, - td->eo, dump_cmdline); + td->eo, &td->opt_list); ret |= newret; if (!o) { @@ -4111,7 +4348,7 @@ int fio_cmd_option_parse(struct thread_data *td, const char *opt, char *val) { int ret; - ret = parse_cmd_option(opt, val, fio_options, td); + ret = parse_cmd_option(opt, val, fio_options, td, &td->opt_list); if (!ret) { struct fio_option *o; @@ -4126,7 +4363,8 @@ int fio_cmd_option_parse(struct thread_data *td, const char *opt, char *val) int fio_cmd_ioengine_option_parse(struct thread_data *td, const char *opt, char *val) { - return parse_cmd_option(opt, val, td->io_ops->options, td->eo); + return parse_cmd_option(opt, val, td->io_ops->options, td->eo, + &td->opt_list); } void fio_fill_default_options(struct thread_data *td) @@ -4312,22 +4550,22 @@ static int opt_is_set(struct thread_options *o, struct fio_option *opt) opt_off = opt - &fio_options[0]; index = opt_off / (8 * sizeof(uint64_t)); offset = opt_off & ((8 * sizeof(uint64_t)) - 1); - return (o->set_options[index] & (1UL << offset)) != 0; + return (o->set_options[index] & ((uint64_t)1 << offset)) != 0; } -int __fio_option_is_set(struct thread_options *o, unsigned int off1) +bool __fio_option_is_set(struct thread_options *o, unsigned int off1) { struct fio_option *opt, *next; next = NULL; while ((opt = find_next_opt(o, next, off1)) != NULL) { if (opt_is_set(o, opt)) - return 1; + return true; next = opt; } - return 0; + return false; } void fio_option_mark_set(struct thread_options *o, struct fio_option *opt) @@ -4337,5 +4575,5 @@ void fio_option_mark_set(struct thread_options *o, struct fio_option *opt) opt_off = opt - &fio_options[0]; index = opt_off / (8 * sizeof(uint64_t)); offset = opt_off & ((8 * sizeof(uint64_t)) - 1); - o->set_options[index] |= 1UL << offset; + o->set_options[index] |= (uint64_t)1 << offset; }