{
int base;
- switch(a) {
+ switch (a) {
case '0'...'9':
base = '0';
break;
default:
base = 0;
}
- return (a - base);
+ return a - base;
}
static int bs_cmp(const void *p1, const void *p2)
return bsp1->perc < bsp2->perc;
}
-static int bssplit_ddir(struct thread_data *td, int ddir, char *str)
+static int bssplit_ddir(struct thread_options *o, int ddir, char *str)
{
struct bssplit *bssplit;
unsigned int i, perc, perc_missing;
long long val;
char *fname;
- td->o.bssplit_nr[ddir] = 4;
+ o->bssplit_nr[ddir] = 4;
bssplit = malloc(4 * sizeof(struct bssplit));
i = 0;
/*
* grow struct buffer, if needed
*/
- if (i == td->o.bssplit_nr[ddir]) {
- td->o.bssplit_nr[ddir] <<= 1;
- bssplit = realloc(bssplit, td->o.bssplit_nr[ddir]
+ if (i == o->bssplit_nr[ddir]) {
+ o->bssplit_nr[ddir] <<= 1;
+ bssplit = realloc(bssplit, o->bssplit_nr[ddir]
* sizeof(struct bssplit));
}
if (perc > 100)
perc = 100;
else if (!perc)
- perc = -1;
+ perc = -1U;
} else
- perc = -1;
+ perc = -1U;
- if (str_to_decimal(fname, &val, 1, td)) {
+ if (str_to_decimal(fname, &val, 1, o, 0)) {
log_err("fio: bssplit conversion failed\n");
- free(td->o.bssplit);
+ free(bssplit);
return 1;
}
i++;
}
- td->o.bssplit_nr[ddir] = i;
+ o->bssplit_nr[ddir] = i;
/*
* Now check if the percentages add up, and how much is missing
*/
perc = perc_missing = 0;
- for (i = 0; i < td->o.bssplit_nr[ddir]; i++) {
+ for (i = 0; i < o->bssplit_nr[ddir]; i++) {
struct bssplit *bsp = &bssplit[i];
- if (bsp->perc == (unsigned char) -1)
+ if (bsp->perc == -1U)
perc_missing++;
else
perc += bsp->perc;
}
- if (perc > 100) {
+ if (perc > 100 && perc_missing > 1) {
log_err("fio: bssplit percentages add to more than 100%%\n");
free(bssplit);
return 1;
}
+
/*
* If values didn't have a percentage set, divide the remains between
* them.
*/
if (perc_missing) {
- for (i = 0; i < td->o.bssplit_nr[ddir]; i++) {
+ 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];
- if (bsp->perc == (unsigned char) -1)
+ if (bsp->perc == -1U)
bsp->perc = (100 - perc) / perc_missing;
}
}
- td->o.min_bs[ddir] = min_bs;
- td->o.max_bs[ddir] = max_bs;
+ o->min_bs[ddir] = min_bs;
+ o->max_bs[ddir] = max_bs;
/*
* now sort based on percentages, for ease of lookup
*/
- qsort(bssplit, td->o.bssplit_nr[ddir], sizeof(struct bssplit), bs_cmp);
- td->o.bssplit[ddir] = bssplit;
+ qsort(bssplit, o->bssplit_nr[ddir], sizeof(struct bssplit), bs_cmp);
+ o->bssplit[ddir] = bssplit;
return 0;
-
}
static int str_bssplit_cb(void *data, const char *input)
char *str, *p, *odir, *ddir;
int ret = 0;
+ if (parse_dryrun())
+ return 0;
+
p = str = strdup(input);
strip_blank_front(&str);
if (odir) {
ddir = strchr(odir + 1, ',');
if (ddir) {
- ret = bssplit_ddir(td, DDIR_TRIM, ddir + 1);
+ ret = bssplit_ddir(&td->o, DDIR_TRIM, ddir + 1);
if (!ret)
*ddir = '\0';
} else {
char *op;
op = strdup(odir + 1);
- ret = bssplit_ddir(td, DDIR_TRIM, op);
+ ret = bssplit_ddir(&td->o, DDIR_TRIM, op);
free(op);
}
- if (!ret)
- ret = bssplit_ddir(td, DDIR_WRITE, odir + 1);
+ if (!ret)
+ ret = bssplit_ddir(&td->o, DDIR_WRITE, odir + 1);
if (!ret) {
*odir = '\0';
- ret = bssplit_ddir(td, DDIR_READ, str);
+ ret = bssplit_ddir(&td->o, DDIR_READ, str);
}
} else {
char *op;
op = strdup(str);
- ret = bssplit_ddir(td, DDIR_WRITE, op);
+ ret = bssplit_ddir(&td->o, DDIR_WRITE, op);
free(op);
if (!ret) {
op = strdup(str);
- ret = bssplit_ddir(td, DDIR_TRIM, op);
+ ret = bssplit_ddir(&td->o, DDIR_TRIM, op);
free(op);
}
- ret = bssplit_ddir(td, DDIR_READ, str);
+ ret = bssplit_ddir(&td->o, DDIR_READ, str);
}
free(p);
static int str2error(char *str)
{
- const char * err[] = {"EPERM", "ENOENT", "ESRCH", "EINTR", "EIO",
+ const char *err[] = { "EPERM", "ENOENT", "ESRCH", "EINTR", "EIO",
"ENXIO", "E2BIG", "ENOEXEC", "EBADF",
"ECHILD", "EAGAIN", "ENOMEM", "EACCES",
"EFAULT", "ENOTBLK", "EBUSY", "EEXIST",
"EXDEV", "ENODEV", "ENOTDIR", "EISDIR",
"EINVAL", "ENFILE", "EMFILE", "ENOTTY",
"ETXTBSY","EFBIG", "ENOSPC", "ESPIPE",
- "EROFS","EMLINK", "EPIPE", "EDOM", "ERANGE"};
+ "EROFS","EMLINK", "EPIPE", "EDOM", "ERANGE" };
int i = 0, num = sizeof(err) / sizeof(void *);
- while( i < num) {
+ while (i < num) {
if (!strcmp(err[i], str))
return i + 1;
i++;
} else {
error[i] = atoi(fname);
if (error[i] < 0)
- error[i] = error[i];
+ error[i] = -error[i];
}
if (!error[i]) {
log_err("Unknown error %s, please use number value \n",
fname);
+ free(error);
return 1;
}
i++;
td->o.continue_on_error |= 1 << etype;
td->o.ignore_error_nr[etype] = i;
td->o.ignore_error[etype] = error;
- }
+ } else
+ free(error);
+
return 0;
}
struct thread_data *td = data;
char *str, *p, *n;
int type = 0, ret = 1;
+
+ if (parse_dryrun())
+ return 0;
+
p = str = strdup(input);
strip_blank_front(&str);
static int str_rw_cb(void *data, const char *str)
{
struct thread_data *td = data;
- char *nr = get_opt_postfix(str);
+ struct thread_options *o = &td->o;
+ char *nr;
- td->o.ddir_seq_nr = 1;
- td->o.ddir_seq_add = 0;
+ if (parse_dryrun())
+ return 0;
+ o->ddir_seq_nr = 1;
+ o->ddir_seq_add = 0;
+
+ nr = get_opt_postfix(str);
if (!nr)
return 0;
if (td_random(td))
- td->o.ddir_seq_nr = atoi(nr);
+ o->ddir_seq_nr = atoi(nr);
else {
long long val;
- if (str_to_decimal(nr, &val, 1, td)) {
+ if (str_to_decimal(nr, &val, 1, o, 0)) {
log_err("fio: rw postfix parsing failed\n");
free(nr);
return 1;
}
- td->o.ddir_seq_add = val;
+ o->ddir_seq_add = val;
}
free(nr);
struct thread_data *td = data;
if (td->o.mem_type == MEM_MMAPHUGE || td->o.mem_type == MEM_MMAP)
- td->mmapfile = get_opt_postfix(mem);
-
- return 0;
-}
-
-static int str_verify_cb(void *data, const char *mem)
-{
- struct thread_data *td = data;
-
- if (td->o.verify == VERIFY_CRC32C_INTEL ||
- td->o.verify == VERIFY_CRC32C) {
- crc32c_intel_probe();
- }
+ td->o.mmapfile = get_opt_postfix(mem);
return 0;
}
return 0;
}
-#ifdef FIO_HAVE_IOPRIO
-static int str_prioclass_cb(void *data, unsigned long long *val)
+static int str_exitall_cb(void)
{
- struct thread_data *td = data;
- unsigned short mask;
-
- /*
- * mask off old class bits, str_prio_cb() may have set a default class
- */
- mask = (1 << IOPRIO_CLASS_SHIFT) - 1;
- td->ioprio &= mask;
-
- td->ioprio |= *val << IOPRIO_CLASS_SHIFT;
- td->ioprio_set = 1;
+ exitall_on_terminate = 1;
return 0;
}
-static int str_prio_cb(void *data, unsigned long long *val)
+#ifdef FIO_HAVE_CPU_AFFINITY
+int fio_cpus_split(os_cpu_mask_t *mask, unsigned int cpu_index)
{
- struct thread_data *td = data;
+ unsigned int i, index, cpus_in_mask;
+ const long max_cpu = cpus_online();
- td->ioprio |= *val;
+ cpus_in_mask = fio_cpu_count(mask);
+ cpu_index = cpu_index % cpus_in_mask;
- /*
- * If no class is set, assume BE
- */
- if ((td->ioprio >> IOPRIO_CLASS_SHIFT) == 0)
- td->ioprio |= IOPRIO_CLASS_BE << IOPRIO_CLASS_SHIFT;
+ index = 0;
+ for (i = 0; i < max_cpu; i++) {
+ if (!fio_cpu_isset(mask, i))
+ continue;
- td->ioprio_set = 1;
- return 0;
-}
-#endif
+ if (cpu_index != index)
+ fio_cpu_clear(mask, i);
-static int str_exitall_cb(void)
-{
- exitall_on_terminate = 1;
- return 0;
+ index++;
+ }
+
+ return fio_cpu_count(mask);
}
-#ifdef FIO_HAVE_CPU_AFFINITY
static int str_cpumask_cb(void *data, unsigned long long *val)
{
struct thread_data *td = data;
long max_cpu;
int ret;
+ if (parse_dryrun())
+ return 0;
+
ret = fio_cpuset_init(&td->o.cpumask);
if (ret < 0) {
log_err("fio: cpuset_init failed\n");
struct thread_data *td = data;
int ret;
+ if (parse_dryrun())
+ return 0;
+
ret = set_cpus_allowed(td, &td->o.cpumask, input);
if (!ret)
td->o.cpumask_set = 1;
static int str_numa_cpunodes_cb(void *data, char *input)
{
struct thread_data *td = data;
+ struct bitmask *verify_bitmask;
+
+ if (parse_dryrun())
+ return 0;
/* numa_parse_nodestring() parses a character string list
* of nodes into a bit mask. The bit mask is allocated by
* numa_allocate_nodemask(), so it should be freed by
* numa_free_nodemask().
*/
- td->o.numa_cpunodesmask = numa_parse_nodestring(input);
- if (td->o.numa_cpunodesmask == NULL) {
+ verify_bitmask = numa_parse_nodestring(input);
+ if (verify_bitmask == NULL) {
log_err("fio: numa_parse_nodestring failed\n");
td_verror(td, 1, "str_numa_cpunodes_cb");
return 1;
}
+ numa_free_nodemask(verify_bitmask);
+ td->o.numa_cpunodes = strdup(input);
td->o.numa_cpumask_set = 1;
return 0;
}
{
struct thread_data *td = data;
const char * const policy_types[] =
- { "default", "prefer", "bind", "interleave", "local" };
+ { "default", "prefer", "bind", "interleave", "local", NULL };
int i;
+ char *nodelist;
+ struct bitmask *verify_bitmask;
- char *nodelist = strchr(input, ':');
+ if (parse_dryrun())
+ return 0;
+
+ nodelist = strchr(input, ':');
if (nodelist) {
/* NUL-terminate mode */
*nodelist++ = '\0';
break;
case MPOL_INTERLEAVE:
case MPOL_BIND:
- td->o.numa_memnodesmask = numa_parse_nodestring(nodelist);
- if (td->o.numa_memnodesmask == NULL) {
+ verify_bitmask = numa_parse_nodestring(nodelist);
+ if (verify_bitmask == NULL) {
log_err("fio: numa_parse_nodestring failed\n");
td_verror(td, 1, "str_numa_memnodes_cb");
return 1;
}
+ td->o.numa_memnodes = strdup(nodelist);
+ numa_free_nodemask(verify_bitmask);
+
break;
case MPOL_LOCAL:
case MPOL_DEFAULT:
double val;
char *nr;
+ if (parse_dryrun())
+ return 0;
+
if (td->o.random_distribution == FIO_RAND_DIST_ZIPF)
val = 1.1;
else if (td->o.random_distribution == FIO_RAND_DIST_PARETO)
return 0;
}
-static int check_dir(struct thread_data *td, char *fname)
-{
-#if 0
- char file[PATH_MAX], *dir;
- int elen = 0;
-
- if (td->o.directory) {
- strcpy(file, td->o.directory);
- strcat(file, "/");
- elen = strlen(file);
- }
-
- sprintf(file + elen, "%s", fname);
- dir = dirname(file);
-
- {
- struct stat sb;
- /*
- * We can't do this on FIO_DISKLESSIO engines. The engine isn't loaded
- * yet, so we can't do this check right here...
- */
- if (lstat(dir, &sb) < 0) {
- int ret = errno;
-
- log_err("fio: %s is not a directory\n", dir);
- td_verror(td, ret, "lstat");
- return 1;
- }
-
- if (!S_ISDIR(sb.st_mode)) {
- log_err("fio: %s is not a directory\n", dir);
- return 1;
- }
- }
-#endif
-
- return 0;
-}
-
/*
- * Return next file in the string. Files are separated with ':'. If the ':'
+ * Return next name in the string. Files are separated with ':'. If the ':'
* is escaped with a '\', then that ':' is part of the filename and does not
* indicate a new file.
*/
-static char *get_next_file_name(char **ptr)
+static char *get_next_name(char **ptr)
{
char *str = *ptr;
char *p, *start;
return start;
}
+
+static int get_max_name_idx(char *input)
+{
+ unsigned int cur_idx;
+ char *str, *p;
+
+ p = str = strdup(input);
+ for (cur_idx = 0; ; cur_idx++)
+ if (get_next_name(&str) == NULL)
+ break;
+
+ free(p);
+ return cur_idx;
+}
+
+/*
+ * Returns the directory at the index, indexes > entires will be
+ * assigned via modulo division of the index
+ */
+int set_name_idx(char *target, char *input, int index)
+{
+ unsigned int cur_idx;
+ int len;
+ char *fname, *str, *p;
+
+ p = str = strdup(input);
+
+ index %= get_max_name_idx(input);
+ for (cur_idx = 0; cur_idx <= index; cur_idx++)
+ fname = get_next_name(&str);
+
+ len = sprintf(target, "%s/", fname);
+ free(p);
+
+ return len;
+}
+
static int str_filename_cb(void *data, const char *input)
{
struct thread_data *td = data;
if (!td->files_index)
td->o.nr_files = 0;
- while ((fname = get_next_file_name(&str)) != NULL) {
+ while ((fname = get_next_name(&str)) != NULL) {
if (!strlen(fname))
break;
- if (check_dir(td, fname)) {
- free(p);
- return 1;
- }
- add_file(td, fname);
- td->o.nr_files++;
+ add_file(td, fname, 0, 1);
}
free(p);
return 0;
}
-static int str_directory_cb(void *data, const char fio_unused *str)
+static int str_directory_cb(void *data, const char fio_unused *unused)
{
struct thread_data *td = data;
struct stat sb;
+ char *dirname, *str, *p;
+ int ret = 0;
- if (lstat(td->o.directory, &sb) < 0) {
- int ret = errno;
+ if (parse_dryrun())
+ return 0;
- log_err("fio: %s is not a directory\n", td->o.directory);
- td_verror(td, ret, "lstat");
- return 1;
+ p = str = strdup(td->o.directory);
+ while ((dirname = get_next_name(&str)) != NULL) {
+ if (lstat(dirname, &sb) < 0) {
+ ret = errno;
+
+ log_err("fio: %s is not a directory\n", dirname);
+ td_verror(td, ret, "lstat");
+ goto out;
+ }
+ if (!S_ISDIR(sb.st_mode)) {
+ log_err("fio: %s is not a directory\n", dirname);
+ ret = 1;
+ goto out;
+ }
}
- if (!S_ISDIR(sb.st_mode)) {
- log_err("fio: %s is not a directory\n", td->o.directory);
+
+out:
+ free(p);
+ return ret;
+}
+
+static int str_lockfile_cb(void *data, const char fio_unused *str)
+{
+ struct thread_data *td = data;
+
+ if (td->files_index) {
+ log_err("fio: lockfile= option must precede filename=\n");
return 1;
}
{
struct thread_data *td = data;
+ if (parse_dryrun())
+ return 0;
+
if (!td->files_index)
td->o.nr_files = 0;
return add_dir_files(td, td->o.opendir);
}
-static int str_verify_pattern_cb(void *data, const char *input)
+static int pattern_cb(char *pattern, unsigned int max_size,
+ const char *input, unsigned int *pattern_bytes)
{
- struct thread_data *td = data;
long off;
int i = 0, j = 0, len, k, base = 10;
- char* loc1, * loc2;
+ 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)
off = strtol(input, NULL, base);
if (off != LONG_MAX || errno != ERANGE) {
while (off) {
- td->o.verify_pattern[i] = off & 0xff;
+ pattern[i] = off & 0xff;
off >>= 8;
i++;
}
j = loc2 - input + 2;
} else
return 1;
- if (len - j < MAX_PATTERN_SIZE * 2) {
+ if (len - j < max_size * 2) {
while (k >= j) {
off = converthexchartoint(input[k--]);
if (k >= j)
off += (converthexchartoint(input[k--])
* 16);
- td->o.verify_pattern[i++] = (char) off;
+ 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.
*/
- while (i > 1 && i * 2 <= MAX_PATTERN_SIZE) {
- memcpy(&td->o.verify_pattern[i], &td->o.verify_pattern[0], i);
+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.
+ * The code in verify_io_u_pattern assumes a single byte
+ * pattern fills the whole verify pattern buffer.
*/
- memset(td->o.verify_pattern, td->o.verify_pattern[0],
- MAX_PATTERN_SIZE);
+ memset(pattern, pattern[0], max_size);
}
- td->o.verify_pattern_bytes = i;
-
- /*
- * VERIFY_META could already be set
- */
- if (td->o.verify == VERIFY_NONE)
- td->o.verify = VERIFY_PATTERN;
-
+ *pattern_bytes = i;
return 0;
}
-static int str_write_bw_log_cb(void *data, const char *str)
+static int str_buffer_pattern_cb(void *data, const char *input)
{
struct thread_data *td = data;
+ int ret;
- if (str)
- td->o.bw_log_file = strdup(str);
+ ret = pattern_cb(td->o.buffer_pattern, MAX_PATTERN_SIZE, input,
+ &td->o.buffer_pattern_bytes);
- td->o.write_bw_log = 1;
- return 0;
+ if (!ret && td->o.buffer_pattern_bytes) {
+ 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;
+ }
+
+ return ret;
}
-static int str_write_lat_log_cb(void *data, const char *str)
+static int str_buffer_compress_cb(void *data, unsigned long long *il)
{
struct thread_data *td = data;
- if (str)
- td->o.lat_log_file = strdup(str);
+ td->flags |= TD_F_COMPRESS;
+ td->o.compress_percentage = *il;
+ return 0;
+}
- td->o.write_lat_log = 1;
+static int str_dedupe_cb(void *data, unsigned long long *il)
+{
+ struct thread_data *td = data;
+
+ td->flags |= TD_F_COMPRESS;
+ td->o.dedupe_percentage = *il;
+ td->o.refill_buffers = 1;
return 0;
}
-static int str_write_iops_log_cb(void *data, const char *str)
+static int str_verify_pattern_cb(void *data, const char *input)
{
struct thread_data *td = data;
+ int ret;
- if (str)
- td->o.iops_log_file = strdup(str);
+ ret = pattern_cb(td->o.verify_pattern, MAX_PATTERN_SIZE, input,
+ &td->o.verify_pattern_bytes);
- td->o.write_iops_log = 1;
- return 0;
+ /*
+ * VERIFY_META could already be set
+ */
+ if (!ret && td->o.verify == VERIFY_NONE)
+ td->o.verify = VERIFY_PATTERN;
+
+ return ret;
}
static int str_gtod_reduce_cb(void *data, int *il)
}
static struct opt_group fio_opt_cat_groups[] = {
+ {
+ .name = "Latency profiling",
+ .mask = FIO_OPT_G_LATPROF,
+ },
{
.name = "Rate",
.mask = FIO_OPT_G_RATE,
/*
* Map of job/command line options
*/
-static struct fio_option fio_options[FIO_MAX_OPTS] = {
+struct fio_option fio_options[FIO_MAX_OPTS] = {
{
.name = "description",
.lname = "Description of job",
.type = FIO_OPT_STR,
.off1 = td_var_offset(file_lock_mode),
.help = "Lock file when doing IO to it",
+ .prio = 1,
.parent = "filename",
.hide = 0,
.def = "none",
+ .cb = str_lockfile_cb,
.category = FIO_OPT_C_FILE,
.group = FIO_OPT_G_FILENAME,
.posval = {
{ .ival = "vsync",
.help = "Use readv/writev",
},
+#ifdef CONFIG_PWRITEV
+ { .ival = "pvsync",
+ .help = "Use preadv/pwritev",
+ },
+#endif
#ifdef CONFIG_LIBAIO
{ .ival = "libaio",
.help = "Linux native asynchronous IO",
{ .ival = "windowsaio",
.help = "Windows native asynchronous IO"
},
+#endif
+#ifdef CONFIG_RBD
+ { .ival = "rbd",
+ .help = "Rados Block Device asynchronous IO"
+ },
#endif
{ .ival = "mmap",
.help = "Memory mapped IO"
{ .ival = "falloc",
.help = "fallocate() file based engine",
},
+#endif
+#ifdef CONFIG_GFAPI
+ { .ival = "gfapi",
+ .help = "Glusterfs libgfapi(sync) based engine"
+ },
+ { .ival = "gfapi_async",
+ .help = "Glusterfs libgfapi(async) based engine"
+ },
+#endif
+#ifdef CONFIG_LIBHDFS
+ { .ival = "libhdfs",
+ .help = "Hadoop Distributed Filesystem (HDFS) engine"
+ },
#endif
{ .ival = "external",
.help = "Load external engine (append name)",
.category = FIO_OPT_C_IO,
.group = FIO_OPT_G_INVALID,
},
+ {
+ .name = "io_limit",
+ .lname = "IO Limit",
+ .type = FIO_OPT_STR_VAL,
+ .off1 = td_var_offset(io_limit),
+ .interval = 1024 * 1024,
+ .category = FIO_OPT_C_IO,
+ .group = FIO_OPT_G_INVALID,
+ },
{
.name = "fill_device",
.lname = "Fill device",
.category = FIO_OPT_C_FILE,
.group = FIO_OPT_G_INVALID,
},
+ {
+ .name = "file_append",
+ .lname = "File append",
+ .type = FIO_OPT_BOOL,
+ .off1 = td_var_offset(file_append),
+ .help = "IO will start at the end of the file(s)",
+ .def = "0",
+ .category = FIO_OPT_C_FILE,
+ .group = FIO_OPT_G_INVALID,
+ },
{
.name = "offset",
.lname = "IO offset",
.category = FIO_OPT_C_IO,
.group = FIO_OPT_G_INVALID,
},
+ {
+ .name = "number_ios",
+ .lname = "Number of IOs to perform",
+ .type = FIO_OPT_STR_VAL,
+ .off1 = td_var_offset(number_ios),
+ .help = "Force job completion after this number of IOs",
+ .def = "0",
+ .category = FIO_OPT_C_IO,
+ .group = FIO_OPT_G_INVALID,
+ },
{
.name = "bs",
.lname = "Block size",
.category = FIO_OPT_C_IO,
.group = FIO_OPT_G_INVALID,
},
+ {
+ .name = "bs_is_seq_rand",
+ .lname = "Block size division is seq/random (not read/write)",
+ .type = FIO_OPT_BOOL,
+ .off1 = td_var_offset(bs_is_seq_rand),
+ .help = "Consider any blocksize setting to be sequential,random",
+ .def = "0",
+ .parent = "blocksize",
+ .category = FIO_OPT_C_IO,
+ .group = FIO_OPT_G_INVALID,
+ },
{
.name = "randrepeat",
.lname = "Random repeatable",
.category = FIO_OPT_C_IO,
.group = FIO_OPT_G_RANDOM,
},
+ {
+ .name = "randseed",
+ .lname = "The random generator seed",
+ .type = FIO_OPT_STR_VAL,
+ .off1 = td_var_offset(rand_seed),
+ .help = "Set the random generator seed value",
+ .parent = "rw",
+ .category = FIO_OPT_C_IO,
+ .group = FIO_OPT_G_RANDOM,
+ },
{
.name = "use_os_rand",
.lname = "Use OS random",
.category = FIO_OPT_C_IO,
.group = FIO_OPT_G_RANDOM,
},
+ {
+ .name = "percentage_random",
+ .lname = "Percentage Random",
+ .type = FIO_OPT_INT,
+ .off1 = td_var_offset(perc_rand[DDIR_READ]),
+ .off2 = td_var_offset(perc_rand[DDIR_WRITE]),
+ .off3 = td_var_offset(perc_rand[DDIR_TRIM]),
+ .maxval = 100,
+ .help = "Percentage of seq/random mix that should be random",
+ .def = "100,100,100",
+ .interval = 5,
+ .inverse = "percentage_sequential",
+ .category = FIO_OPT_C_IO,
+ .group = FIO_OPT_G_RANDOM,
+ },
+ {
+ .name = "percentage_sequential",
+ .lname = "Percentage Sequential",
+ .type = FIO_OPT_DEPRECATED,
+ .category = FIO_OPT_C_IO,
+ .group = FIO_OPT_G_RANDOM,
+ },
+ {
+ .name = "allrandrepeat",
+ .type = FIO_OPT_BOOL,
+ .off1 = td_var_offset(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",
{ .ival = "wait_before",
.oval = SYNC_FILE_RANGE_WAIT_BEFORE,
.help = "SYNC_FILE_RANGE_WAIT_BEFORE",
- .or = 1,
+ .orval = 1,
},
{ .ival = "write",
.oval = SYNC_FILE_RANGE_WRITE,
.help = "SYNC_FILE_RANGE_WRITE",
- .or = 1,
+ .orval = 1,
},
{
.ival = "wait_after",
.oval = SYNC_FILE_RANGE_WAIT_AFTER,
.help = "SYNC_FILE_RANGE_WAIT_AFTER",
- .or = 1,
+ .orval = 1,
},
},
.type = FIO_OPT_STR_MULTI,
.category = FIO_OPT_C_IO,
.group = FIO_OPT_G_IO_TYPE,
},
+ {
+ .name = "atomic",
+ .lname = "Atomic I/O",
+ .type = FIO_OPT_BOOL,
+ .off1 = td_var_offset(oatomic),
+ .help = "Use Atomic IO with O_DIRECT (implies O_DIRECT)",
+ .def = "0",
+ .category = FIO_OPT_C_IO,
+ .group = FIO_OPT_G_IO_TYPE,
+ },
{
.name = "buffered",
.lname = "Buffered I/O",
.lname = "Start delay",
.type = FIO_OPT_STR_VAL_TIME,
.off1 = td_var_offset(start_delay),
+ .off2 = td_var_offset(start_delay_high),
.help = "Only start job when this period has passed",
.def = "0",
+ .is_seconds = 1,
.category = FIO_OPT_C_GENERAL,
.group = FIO_OPT_G_RUNTIME,
},
.off1 = td_var_offset(timeout),
.help = "Stop workload when this amount of time has passed",
.def = "0",
+ .is_seconds = 1,
.category = FIO_OPT_C_GENERAL,
.group = FIO_OPT_G_RUNTIME,
},
.category = FIO_OPT_C_GENERAL,
.group = FIO_OPT_G_RUNTIME,
},
+ {
+ .name = "verify_only",
+ .lname = "Verify only",
+ .type = FIO_OPT_STR_SET,
+ .off1 = td_var_offset(verify_only),
+ .help = "Verifies previously written data is still valid",
+ .category = FIO_OPT_C_GENERAL,
+ .group = FIO_OPT_G_RUNTIME,
+ },
{
.name = "ramp_time",
.lname = "Ramp time",
.type = FIO_OPT_STR_VAL_TIME,
.off1 = td_var_offset(ramp_time),
.help = "Ramp up time before measuring performance",
+ .is_seconds = 1,
.category = FIO_OPT_C_GENERAL,
.group = FIO_OPT_G_RUNTIME,
},
.oval = MEM_MALLOC,
.help = "Use malloc(3) for IO buffers",
},
+#ifndef CONFIG_NO_SHM
{ .ival = "shm",
.oval = MEM_SHM,
.help = "Use shared memory segments for IO buffers",
.oval = MEM_SHMHUGE,
.help = "Like shm, but use huge pages",
},
+#endif
#endif
{ .ival = "mmap",
.oval = MEM_MMAP,
.type = FIO_OPT_STR,
.off1 = td_var_offset(verify),
.help = "Verify data written",
- .cb = str_verify_cb,
.def = "0",
.category = FIO_OPT_C_IO,
.group = FIO_OPT_G_VERIFY,
.oval = VERIFY_SHA512,
.help = "Use sha512 checksums for verification",
},
+ { .ival = "xxhash",
+ .oval = VERIFY_XXHASH,
+ .help = "Use xxhash checksums for verification",
+ },
{ .ival = "meta",
.oval = VERIFY_META,
.help = "Use io information",
.name = "prio",
.lname = "I/O nice priority",
.type = FIO_OPT_INT,
- .cb = str_prio_cb,
+ .off1 = td_var_offset(ioprio),
.help = "Set job IO priority value",
.minval = 0,
.maxval = 7,
.name = "prioclass",
.lname = "I/O nice priority class",
.type = FIO_OPT_INT,
- .cb = str_prioclass_cb,
+ .off1 = td_var_offset(ioprio_class),
.help = "Set job IO priority class",
.minval = 0,
.maxval = 3,
.off1 = td_var_offset(max_latency),
.help = "Maximum tolerated IO latency (usec)",
.category = FIO_OPT_C_IO,
- .group = FIO_OPT_G_RATE,
+ .group = FIO_OPT_G_LATPROF,
+ },
+ {
+ .name = "latency_target",
+ .lname = "Latency Target (usec)",
+ .type = FIO_OPT_STR_VAL_TIME,
+ .off1 = td_var_offset(latency_target),
+ .help = "Ramp to max queue depth supporting this latency",
+ .category = FIO_OPT_C_IO,
+ .group = FIO_OPT_G_LATPROF,
+ },
+ {
+ .name = "latency_window",
+ .lname = "Latency Window (usec)",
+ .type = FIO_OPT_STR_VAL_TIME,
+ .off1 = td_var_offset(latency_window),
+ .help = "Time to sustain latency_target",
+ .category = FIO_OPT_C_IO,
+ .group = FIO_OPT_G_LATPROF,
+ },
+ {
+ .name = "latency_percentile",
+ .lname = "Latency Percentile",
+ .type = FIO_OPT_FLOAT_LIST,
+ .off1 = td_var_offset(latency_percentile),
+ .help = "Percentile of IOs must be below latency_target",
+ .def = "100",
+ .maxlen = 1,
+ .minfp = 0.0,
+ .maxfp = 100.0,
+ .category = FIO_OPT_C_IO,
+ .group = FIO_OPT_G_LATPROF,
},
{
.name = "invalidate",
.category = FIO_OPT_C_GENERAL,
.group = FIO_OPT_G_CRED,
},
+ {
+ .name = "cpus_allowed_policy",
+ .lname = "CPUs allowed distribution policy",
+ .type = FIO_OPT_STR,
+ .off1 = td_var_offset(cpus_allowed_policy),
+ .help = "Distribution policy for cpus_allowed",
+ .parent = "cpus_allowed",
+ .prio = 1,
+ .posval = {
+ { .ival = "shared",
+ .oval = FIO_CPUS_SHARED,
+ .help = "Mask shared between threads",
+ },
+ { .ival = "split",
+ .oval = FIO_CPUS_SPLIT,
+ .help = "Mask split between threads",
+ },
+ },
+ .category = FIO_OPT_C_GENERAL,
+ .group = FIO_OPT_G_CRED,
+ },
#endif
#ifdef CONFIG_LIBNUMA
{
.type = FIO_OPT_STR_SET,
.off1 = td_var_offset(use_thread),
.help = "Use threads instead of processes",
+#ifdef CONFIG_NO_SHM
+ .def = "1",
+ .no_warn_def = 1,
+#endif
.category = FIO_OPT_C_GENERAL,
.group = FIO_OPT_G_PROCESS,
},
{
.name = "write_bw_log",
.lname = "Write bandwidth log",
- .type = FIO_OPT_STR,
+ .type = FIO_OPT_STR_STORE,
.off1 = td_var_offset(bw_log_file),
- .cb = str_write_bw_log_cb,
.help = "Write log of bandwidth during run",
.category = FIO_OPT_C_LOG,
.group = FIO_OPT_G_INVALID,
{
.name = "write_lat_log",
.lname = "Write latency log",
- .type = FIO_OPT_STR,
+ .type = FIO_OPT_STR_STORE,
.off1 = td_var_offset(lat_log_file),
- .cb = str_write_lat_log_cb,
.help = "Write log of latency during run",
.category = FIO_OPT_C_LOG,
.group = FIO_OPT_G_INVALID,
{
.name = "write_iops_log",
.lname = "Write IOPS log",
- .type = FIO_OPT_STR,
+ .type = FIO_OPT_STR_STORE,
.off1 = td_var_offset(iops_log_file),
- .cb = str_write_iops_log_cb,
.help = "Write log of IOPS during run",
.category = FIO_OPT_C_LOG,
.group = FIO_OPT_G_INVALID,
.category = FIO_OPT_C_LOG,
.group = FIO_OPT_G_INVALID,
},
+ {
+ .name = "log_offset",
+ .lname = "Log offset of IO",
+ .type = FIO_OPT_BOOL,
+ .off1 = td_var_offset(log_offset),
+ .help = "Include offset of IO for each log entry",
+ .def = "0",
+ .category = FIO_OPT_C_LOG,
+ .group = FIO_OPT_G_INVALID,
+ },
+#ifdef CONFIG_ZLIB
+ {
+ .name = "log_compression",
+ .lname = "Log compression",
+ .type = FIO_OPT_INT,
+ .off1 = td_var_offset(log_gz),
+ .help = "Log in compressed chunks of this size",
+ .minval = 32 * 1024 * 1024ULL,
+ .maxval = 512 * 1024 * 1024ULL,
+ .category = FIO_OPT_C_LOG,
+ .group = FIO_OPT_G_INVALID,
+ },
+ {
+ .name = "log_store_compressed",
+ .lname = "Log store compressed",
+ .type = FIO_OPT_BOOL,
+ .off1 = td_var_offset(log_gz_store),
+ .help = "Store logs in a compressed format",
+ .category = FIO_OPT_C_LOG,
+ .group = FIO_OPT_G_INVALID,
+ },
+#endif
{
.name = "bwavgtime",
.lname = "Bandwidth average time",
{
.name = "group_reporting",
.lname = "Group reporting",
- .type = FIO_OPT_BOOL,
+ .type = FIO_OPT_STR_SET,
.off1 = td_var_offset(group_reporting),
.help = "Do reporting on a per-group basis",
- .def = "1",
.category = FIO_OPT_C_STAT,
.group = FIO_OPT_G_INVALID,
},
.category = FIO_OPT_C_IO,
.group = FIO_OPT_G_IO_BUF,
},
+ {
+ .name = "buffer_pattern",
+ .lname = "Buffer pattern",
+ .type = FIO_OPT_STR,
+ .cb = str_buffer_pattern_cb,
+ .help = "Fill pattern for IO buffers",
+ .category = FIO_OPT_C_IO,
+ .group = FIO_OPT_G_IO_BUF,
+ },
{
.name = "buffer_compress_percentage",
.lname = "Buffer compression percentage",
.type = FIO_OPT_INT,
- .off1 = td_var_offset(compress_percentage),
+ .cb = str_buffer_compress_cb,
.maxval = 100,
- .minval = 1,
+ .minval = 0,
.help = "How compressible the buffer is (approximately)",
.interval = 5,
.category = FIO_OPT_C_IO,
.category = FIO_OPT_C_IO,
.group = FIO_OPT_G_IO_BUF,
},
+ {
+ .name = "dedupe_percentage",
+ .lname = "Dedupe percentage",
+ .type = FIO_OPT_INT,
+ .cb = str_dedupe_cb,
+ .maxval = 100,
+ .minval = 0,
+ .help = "Percentage of buffers that are dedupable",
+ .interval = 1,
+ .category = FIO_OPT_C_IO,
+ .group = FIO_OPT_G_IO_BUF,
+ },
{
.name = "clat_percentiles",
.lname = "Completion latency percentiles",
lopt->name = (char *) name;
lopt->val = val;
if (o->type == FIO_OPT_STR_SET)
- lopt->has_arg = no_argument;
+ lopt->has_arg = optional_argument;
else
lopt->has_arg = required_argument;
}
sprintf(buf, "echo '%s' | %s", tmp, BC_APP);
f = popen(buf, "r");
- if (!f) {
+ if (!f)
return NULL;
- }
ret = fread(&buf[tmp - str], 1, 128 - (tmp - str), f);
if (ret <= 0) {
+ pclose(f);
return NULL;
}
return opts_copy;
}
-int fio_options_parse(struct thread_data *td, char **opts, int num_opts)
+int fio_options_parse(struct thread_data *td, char **opts, int num_opts,
+ int dump_cmdline)
{
int i, ret, unknown;
char **opts_copy;
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);
+ &o, td, dump_cmdline);
if (opts_copy[i]) {
if (newret && !o) {
if (td->eo)
newret = parse_option(opts_copy[i], opts[i],
td->io_ops->options, &o,
- td->eo);
+ td->eo, dump_cmdline);
ret |= newret;
if (!o)
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);
+ return parse_cmd_option(opt, val, td->io_ops->options, td->eo);
}
void fio_fill_default_options(struct thread_data *td)
{
+ td->o.magic = OPT_MAGIC;
fill_default_options(td, fio_options);
}
if (o->type != FIO_OPT_STR_STORE)
continue;
- ptr = td_var(data, o->off1);
+ ptr = td_var(data, o, o->off1);
if (*ptr)
*ptr = strdup(*ptr);
}
unsigned int fio_get_kb_base(void *data)
{
- struct thread_data *td = data;
+ struct thread_options *o = data;
unsigned int kb_base = 0;
- if (td)
- kb_base = td->o.kb_base;
+ /*
+ * This is a hack... For private options, *data is not holding
+ * a pointer to the thread_options, but to private data. This means
+ * we can't safely dereference it, but magic is first so mem wise
+ * it is valid. But this also means that if the job first sets
+ * kb_base and expects that to be honored by private options,
+ * it will be disappointed. We will return the global default
+ * for this.
+ */
+ if (o && o->magic == OPT_MAGIC)
+ kb_base = o->kb_base;
if (!kb_base)
kb_base = 1024;
__o++;
}
+ if (opt_index + 1 == FIO_MAX_OPTS) {
+ log_err("fio: FIO_MAX_OPTS is too small\n");
+ return 1;
+ }
+
memcpy(&fio_options[opt_index], o, sizeof(*o));
+ fio_options[opt_index + 1].name = NULL;
return 0;
}
td->eo = NULL;
}
}
+
+struct fio_option *fio_option_find(const char *name)
+{
+ return find_option(fio_options, name);
+}
+