X-Git-Url: https://git.kernel.dk/?p=fio.git;a=blobdiff_plain;f=init.c;h=213844647752f4212f88e7dd837149defbf833b5;hp=5edd53e06cca9e03b063fc5aea5f94e9e65575ad;hb=3bc20ebdf0802db981f9d8b2cb3255c6f4cc9265;hpb=7627557b4795971e0a7565f51415697c71d27c6b diff --git a/init.c b/init.c index 5edd53e0..21384464 100644 --- a/init.c +++ b/init.c @@ -25,11 +25,13 @@ #include "server.h" #include "idletime.h" #include "filelock.h" +#include "steadystate.h" -#include "lib/getopt.h" -#include "lib/strcasestr.h" +#include "oslib/getopt.h" +#include "oslib/strcasestr.h" #include "crc/test.h" +#include "lib/pow2.h" const char fio_version_string[] = FIO_VERSION; @@ -38,7 +40,6 @@ const char fio_version_string[] = FIO_VERSION; static char **ini_file; static int max_jobs = FIO_MAX_JOBS; static int dump_cmdline; -static long long def_timeout; static int parse_only; static struct thread_data def_thread; @@ -48,7 +49,6 @@ static int nr_job_sections; int exitall_on_terminate = 0; int output_format = FIO_OUTPUT_NORMAL; -int append_terse_output = 0; int eta_print = FIO_ETA_AUTO; int eta_new_line = 0; FILE *f_out = NULL; @@ -69,6 +69,8 @@ long long trigger_timeout = 0; char *trigger_cmd = NULL; char *trigger_remote_cmd = NULL; +char *aux_path = NULL; + static int prev_group_jobs; unsigned long fio_debug = 0; @@ -90,11 +92,6 @@ static struct option l_opts[FIO_NR_OPTIONS] = { .has_arg = required_argument, .val = 'o' | FIO_CLIENT_FLAG, }, - { - .name = (char *) "timeout", - .has_arg = required_argument, - .val = 't' | FIO_CLIENT_FLAG, - }, { .name = (char *) "latency-log", .has_arg = required_argument, @@ -102,7 +99,7 @@ static struct option l_opts[FIO_NR_OPTIONS] = { }, { .name = (char *) "bandwidth-log", - .has_arg = required_argument, + .has_arg = no_argument, .val = 'b' | FIO_CLIENT_FLAG, }, { @@ -112,7 +109,7 @@ static struct option l_opts[FIO_NR_OPTIONS] = { }, { .name = (char *) "output-format", - .has_arg = optional_argument, + .has_arg = required_argument, .val = 'F' | FIO_CLIENT_FLAG, }, { @@ -266,6 +263,11 @@ static struct option l_opts[FIO_NR_OPTIONS] = { .has_arg = required_argument, .val = 'J', }, + { + .name = (char *) "aux-path", + .has_arg = required_argument, + .val = 'K', + }, { .name = NULL, }, @@ -292,7 +294,6 @@ void free_threads_shm(void) static void free_shm(void) { if (threads) { - file_hash_exit(); flow_exit(); fio_debug_jobp = NULL; free_threads_shm(); @@ -303,8 +304,9 @@ static void free_shm(void) free(trigger_remote_cmd); trigger_file = trigger_cmd = trigger_remote_cmd = NULL; - options_free(fio_options, &def_thread); + options_free(fio_options, &def_thread.o); fio_filelock_exit(); + file_hash_exit(); scleanup(); } @@ -316,8 +318,6 @@ static void free_shm(void) */ static int setup_thread_area(void) { - void *hash; - if (threads) return 0; @@ -328,7 +328,6 @@ static int setup_thread_area(void) do { size_t size = max_jobs * sizeof(struct thread_data); - size += file_hash_size; size += sizeof(unsigned int); #ifndef CONFIG_NO_SHM @@ -357,25 +356,80 @@ static int setup_thread_area(void) perror("shmat"); return 1; } +#ifdef FIO_HAVE_SHM_ATTACH_REMOVED + shmctl(shm_id, IPC_RMID, NULL); +#endif #endif memset(threads, 0, max_jobs * sizeof(struct thread_data)); - hash = (void *) threads + max_jobs * sizeof(struct thread_data); - fio_debug_jobp = (void *) hash + file_hash_size; + fio_debug_jobp = (void *) threads + max_jobs * sizeof(struct thread_data); *fio_debug_jobp = -1; - file_hash_init(hash); flow_init(); return 0; } -static void set_cmd_options(struct thread_data *td) +static void dump_print_option(struct print_option *p) { - struct thread_options *o = &td->o; + const char *delim; + + if (!strcmp("description", p->name)) + delim = "\""; + else + delim = ""; + + log_info("--%s%s", p->name, p->value ? "" : " "); + if (p->value) + log_info("=%s%s%s ", delim, p->value, delim); +} + +static void dump_opt_list(struct thread_data *td) +{ + struct flist_head *entry; + struct print_option *p; + + if (flist_empty(&td->opt_list)) + return; + + flist_for_each(entry, &td->opt_list) { + p = flist_entry(entry, struct print_option, list); + dump_print_option(p); + } +} - if (!o->timeout) - o->timeout = def_timeout; +static void fio_dump_options_free(struct thread_data *td) +{ + while (!flist_empty(&td->opt_list)) { + struct print_option *p; + + p = flist_first_entry(&td->opt_list, struct print_option, list); + flist_del_init(&p->list); + free(p->name); + free(p->value); + free(p); + } +} + +static void copy_opt_list(struct thread_data *dst, struct thread_data *src) +{ + struct flist_head *entry; + + if (flist_empty(&src->opt_list)) + return; + + flist_for_each(entry, &src->opt_list) { + struct print_option *srcp, *dstp; + + srcp = flist_entry(entry, struct print_option, list); + dstp = malloc(sizeof(*dstp)); + dstp->name = strdup(srcp->name); + if (srcp->value) + dstp->value = strdup(srcp->value); + else + dstp->value = NULL; + flist_add_tail(&dstp->list, &dst->opt_list); + } } /* @@ -386,10 +440,8 @@ static struct thread_data *get_new_job(int global, struct thread_data *parent, { struct thread_data *td; - if (global) { - set_cmd_options(&def_thread); + if (global) return &def_thread; - } if (setup_thread_area()) { log_err("error: failed to setup shm segment\n"); return NULL; @@ -403,6 +455,10 @@ static struct thread_data *get_new_job(int global, struct thread_data *parent, td = &threads[thread_number++]; *td = *parent; + INIT_FLIST_HEAD(&td->opt_list); + if (parent != &def_thread) + copy_opt_list(td, parent); + td->io_ops = NULL; if (!preserve_eo) td->eo = NULL; @@ -420,10 +476,9 @@ static struct thread_data *get_new_job(int global, struct thread_data *parent, if (jobname) td->o.name = strdup(jobname); - if (!parent->o.group_reporting) + if (!parent->o.group_reporting || parent == &def_thread) stat_number++; - set_cmd_options(td); return td; } @@ -439,6 +494,7 @@ static void put_job(struct thread_data *td) log_info("fio: %s\n", td->verror); fio_options_free(td); + fio_dump_options_free(td); if (td->io_ops) free_ioengine(td); @@ -458,14 +514,16 @@ static int __setup_rate(struct thread_data *td, enum fio_ddir ddir) if (td->o.rate[ddir]) td->rate_bps[ddir] = td->o.rate[ddir]; else - td->rate_bps[ddir] = td->o.rate_iops[ddir] * bs; + td->rate_bps[ddir] = (uint64_t) td->o.rate_iops[ddir] * bs; if (!td->rate_bps[ddir]) { log_err("rate lower than supported\n"); return -1; } - td->rate_pending_usleep[ddir] = 0; + td->rate_next_io_time[ddir] = 0; + td->rate_io_issue_bytes[ddir] = 0; + td->last_usec = 0; return 0; } @@ -509,6 +567,17 @@ static unsigned long long get_rand_start_delay(struct thread_data *td) return delayrange; } +/* + * <3 Johannes + */ +static unsigned int gcd(unsigned int m, unsigned int n) +{ + if (!n) + return m; + + return gcd(n, m % n); +} + /* * Lazy way of fixing up options that depend on each other. We could also * define option callback handlers, but this is easier. @@ -601,7 +670,7 @@ static int fixup_options(struct thread_data *td) "verify limited\n"); ret = warnings_fatal; } - if (o->bs_unaligned && (o->odirect || td->io_ops->flags & FIO_RAWIO)) + if (o->bs_unaligned && (o->odirect || td_ioengine_flagged(td, FIO_RAWIO))) log_err("fio: bs_unaligned may not work with raw io\n"); /* @@ -622,6 +691,13 @@ static int fixup_options(struct thread_data *td) if (o->iodepth_batch > o->iodepth || !o->iodepth_batch) o->iodepth_batch = o->iodepth; + /* + * If max batch complete number isn't set or set incorrectly, + * default to the same as iodepth_batch_complete_min + */ + if (o->iodepth_batch_complete_min > o->iodepth_batch_complete_max) + o->iodepth_batch_complete_max = o->iodepth_batch_complete_min; + if (o->nr_files > td->files_index) o->nr_files = td->files_index; @@ -635,12 +711,12 @@ static int fixup_options(struct thread_data *td) log_err("fio: rate and rate_iops are mutually exclusive\n"); ret = 1; } - if ((o->rate[DDIR_READ] < o->ratemin[DDIR_READ]) || - (o->rate[DDIR_WRITE] < o->ratemin[DDIR_WRITE]) || - (o->rate[DDIR_TRIM] < o->ratemin[DDIR_TRIM]) || - (o->rate_iops[DDIR_READ] < o->rate_iops_min[DDIR_READ]) || - (o->rate_iops[DDIR_WRITE] < o->rate_iops_min[DDIR_WRITE]) || - (o->rate_iops[DDIR_TRIM] < o->rate_iops_min[DDIR_TRIM])) { + if ((o->rate[DDIR_READ] && (o->rate[DDIR_READ] < o->ratemin[DDIR_READ])) || + (o->rate[DDIR_WRITE] && (o->rate[DDIR_WRITE] < o->ratemin[DDIR_WRITE])) || + (o->rate[DDIR_TRIM] && (o->rate[DDIR_TRIM] < o->ratemin[DDIR_TRIM])) || + (o->rate_iops[DDIR_READ] && (o->rate_iops[DDIR_READ] < o->rate_iops_min[DDIR_READ])) || + (o->rate_iops[DDIR_WRITE] && (o->rate_iops[DDIR_WRITE] < o->rate_iops_min[DDIR_WRITE])) || + (o->rate_iops[DDIR_TRIM] && (o->rate_iops[DDIR_TRIM] < o->rate_iops_min[DDIR_TRIM]))) { log_err("fio: minimum rate exceeds rate\n"); ret = 1; } @@ -677,11 +753,20 @@ static int fixup_options(struct thread_data *td) o->verify_interval = o->min_bs[DDIR_WRITE]; else if (td_read(td) && o->verify_interval > o->min_bs[DDIR_READ]) o->verify_interval = o->min_bs[DDIR_READ]; + + /* + * Verify interval must be a factor or both min and max + * write size + */ + if (o->verify_interval % o->min_bs[DDIR_WRITE] || + o->verify_interval % o->max_bs[DDIR_WRITE]) + o->verify_interval = gcd(o->min_bs[DDIR_WRITE], + o->max_bs[DDIR_WRITE]); } if (o->pre_read) { o->invalidate_cache = 0; - if (td->io_ops->flags & FIO_PIPEIO) { + if (td_ioengine_flagged(td, FIO_PIPEIO)) { log_info("fio: cannot pre-read files with an IO engine" " that isn't seekable. Pre-read disabled.\n"); ret = warnings_fatal; @@ -689,7 +774,7 @@ static int fixup_options(struct thread_data *td) } if (!o->unit_base) { - if (td->io_ops->flags & FIO_BIT_BASED) + if (td_ioengine_flagged(td, FIO_BIT_BASED)) o->unit_base = 1; else o->unit_base = 8; @@ -712,7 +797,7 @@ static int fixup_options(struct thread_data *td) * Windows doesn't support O_DIRECT or O_SYNC with the _open interface, * so fail if we're passed those flags */ - if ((td->io_ops->flags & FIO_SYNCIO) && (td->o.odirect || td->o.sync_io)) { + if (td_ioengine_flagged(td, FIO_SYNCIO) && (td->o.odirect || td->o.sync_io)) { log_err("fio: Windows does not support direct or non-buffered io with" " the synchronous ioengines. Use the 'windowsaio' ioengine" " with 'direct=1' and 'iodepth=1' instead.\n"); @@ -722,11 +807,16 @@ static int fixup_options(struct thread_data *td) /* * For fully compressible data, just zero them at init time. - * It's faster than repeatedly filling it. + * It's faster than repeatedly filling it. For non-zero + * compression, we should have refill_buffers set. Set it, unless + * the job file already changed it. */ - if (td->o.compress_percentage == 100) { - td->o.zero_buffers = 1; - td->o.compress_percentage = 0; + if (o->compress_percentage) { + if (o->compress_percentage == 100) { + o->zero_buffers = 1; + o->compress_percentage = 0; + } else if (!fio_option_is_set(o, refill_buffers)) + o->refill_buffers = 1; } /* @@ -740,7 +830,8 @@ static int fixup_options(struct thread_data *td) * If size is set but less than the min block size, complain */ if (o->size && o->size < td_min_bs(td)) { - log_err("fio: size too small, must be larger than the IO size: %llu\n", (unsigned long long) o->size); + log_err("fio: size too small, must not be less than minimum block size: %llu < %u\n", + (unsigned long long) o->size, td_min_bs(td)); ret = 1; } @@ -756,7 +847,7 @@ static int fixup_options(struct thread_data *td) if (fio_option_is_set(&td->o, rand_seed)) td->o.rand_repeatable = 0; - if ((td->io_ops->flags & FIO_NOEXTEND) && td->o.file_append) { + if (td_ioengine_flagged(td, FIO_NOEXTEND) && td->o.file_append) { log_err("fio: can't append/extent with IO engine %s\n", td->io_ops->name); ret = 1; } @@ -772,7 +863,7 @@ static int fixup_options(struct thread_data *td) td->loops = 1; if (td->o.block_error_hist && td->o.nr_files != 1) { - log_err("fio: block error histogram only available with " + log_err("fio: block error histogram only available " "with a single file per job, but %d files " "provided\n", td->o.nr_files); ret = 1; @@ -781,27 +872,6 @@ static int fixup_options(struct thread_data *td) return ret; } -/* - * This function leaks the buffer - */ -char *fio_uint_to_kmg(unsigned int val) -{ - char *buf = malloc(32); - char post[] = { 0, 'K', 'M', 'G', 'P', 'E', 0 }; - char *p = post; - - do { - if (val & 1023) - break; - - val >>= 10; - p++; - } while (*p); - - snprintf(buf, 32, "%u%c", val, *p); - return buf; -} - /* External engines are specified by "external:name.o") */ static const char *get_engine_name(const char *str) { @@ -816,33 +886,55 @@ static const char *get_engine_name(const char *str) return p; } -static int exists_and_not_file(const char *filename) +static void init_rand_file_service(struct thread_data *td) { - struct stat sb; + unsigned long nranges = td->o.nr_files << FIO_FSERVICE_SHIFT; + const unsigned int seed = td->rand_seeds[FIO_RAND_FILE_OFF]; + + if (td->o.file_service_type == FIO_FSERVICE_ZIPF) { + zipf_init(&td->next_file_zipf, nranges, td->zipf_theta, seed); + zipf_disable_hash(&td->next_file_zipf); + } else if (td->o.file_service_type == FIO_FSERVICE_PARETO) { + pareto_init(&td->next_file_zipf, nranges, td->pareto_h, seed); + zipf_disable_hash(&td->next_file_zipf); + } else if (td->o.file_service_type == FIO_FSERVICE_GAUSS) { + gauss_init(&td->next_file_gauss, nranges, td->gauss_dev, seed); + gauss_disable_hash(&td->next_file_gauss); + } +} - if (lstat(filename, &sb) == -1) - return 0; +void td_fill_verify_state_seed(struct thread_data *td) +{ + bool use64; - /* \\.\ is the device namespace in Windows, where every file - * is a device node */ - if (S_ISREG(sb.st_mode) && strncmp(filename, "\\\\.\\", 4) != 0) - return 0; + if (td->o.random_generator == FIO_RAND_GEN_TAUSWORTHE64) + use64 = 1; + else + use64 = 0; - return 1; + init_rand_seed(&td->verify_state, td->rand_seeds[FIO_RAND_VER_OFF], + use64); } -static void td_fill_rand_seeds_internal(struct thread_data *td, int use64) +static void td_fill_rand_seeds_internal(struct thread_data *td, bool use64) { + int i; + init_rand_seed(&td->bsrange_state, td->rand_seeds[FIO_RAND_BS_OFF], use64); - init_rand_seed(&td->verify_state, td->rand_seeds[FIO_RAND_VER_OFF], use64); - init_rand_seed(&td->rwmix_state, td->rand_seeds[FIO_RAND_MIX_OFF], use64); + td_fill_verify_state_seed(td); + init_rand_seed(&td->rwmix_state, td->rand_seeds[FIO_RAND_MIX_OFF], false); if (td->o.file_service_type == FIO_FSERVICE_RANDOM) init_rand_seed(&td->next_file_state, td->rand_seeds[FIO_RAND_FILE_OFF], use64); + else if (td->o.file_service_type & __FIO_FSERVICE_NONUNIFORM) + init_rand_file_service(td); init_rand_seed(&td->file_size_state, td->rand_seeds[FIO_RAND_FILE_SIZE_OFF], use64); init_rand_seed(&td->trim_state, td->rand_seeds[FIO_RAND_TRIM_OFF], use64); init_rand_seed(&td->delay_state, td->rand_seeds[FIO_RAND_START_DELAY], use64); + init_rand_seed(&td->poisson_state, td->rand_seeds[FIO_RAND_POISSON_OFF], 0); + init_rand_seed(&td->dedupe_state, td->rand_seeds[FIO_DEDUPE_OFF], false); + init_rand_seed(&td->zone_state, td->rand_seeds[FIO_RAND_ZONE_OFF], false); if (!td_random(td)) return; @@ -851,14 +943,17 @@ static void td_fill_rand_seeds_internal(struct thread_data *td, int use64) td->rand_seeds[FIO_RAND_BLOCK_OFF] = FIO_RANDSEED * td->thread_number; init_rand_seed(&td->random_state, td->rand_seeds[FIO_RAND_BLOCK_OFF], use64); - init_rand_seed(&td->seq_rand_state[DDIR_READ], td->rand_seeds[FIO_RAND_SEQ_RAND_READ_OFF], use64); - init_rand_seed(&td->seq_rand_state[DDIR_WRITE], td->rand_seeds[FIO_RAND_SEQ_RAND_WRITE_OFF], use64); - init_rand_seed(&td->seq_rand_state[DDIR_TRIM], td->rand_seeds[FIO_RAND_SEQ_RAND_TRIM_OFF], use64); + + for (i = 0; i < DDIR_RWDIR_CNT; i++) { + struct frand_state *s = &td->seq_rand_state[i]; + + init_rand_seed(s, td->rand_seeds[FIO_RAND_SEQ_RAND_READ_OFF], false); + } } void td_fill_rand_seeds(struct thread_data *td) { - int use64; + bool use64; if (td->o.allrand_repeatable) { unsigned int i; @@ -877,8 +972,6 @@ void td_fill_rand_seeds(struct thread_data *td) init_rand_seed(&td->buf_state, td->rand_seeds[FIO_RAND_BUF_OFF], use64); frand_copy(&td->buf_state_prev, &td->buf_state); - - init_rand_seed(&td->dedupe_state, td->rand_seeds[FIO_DEDUPE_OFF], use64); } /* @@ -930,7 +1023,7 @@ int ioengine_load(struct thread_data *td) */ if (origeo) { memcpy(td->eo, origeo, td->io_ops->option_struct_size); - options_mem_dupe(td->eo, td->io_ops->options); + options_mem_dupe(td->io_ops->options, td->eo); } else { memset(td->eo, 0, td->io_ops->option_struct_size); fill_default_options(td->eo, td->io_ops->options); @@ -938,6 +1031,10 @@ int ioengine_load(struct thread_data *td) *(struct thread_data **)td->eo = td; } + if (td->o.odirect) + td->io_ops->flags |= FIO_RAWIO; + + td_set_ioengine_flags(td); return 0; } @@ -985,7 +1082,7 @@ static int setup_random_seeds(struct thread_data *td) seed *= 0x9e370001UL; for (i = 0; i < FIO_RAND_NR_OFFS; i++) { - td->rand_seeds[i] = seed; + td->rand_seeds[i] = seed * td->thread_number + i; seed *= 0x9e370001UL; } @@ -1113,7 +1210,7 @@ static char *make_filename(char *buf, size_t buf_size,struct thread_options *o, return buf; } -int parse_dryrun(void) +bool parse_dryrun(void) { return dump_cmdline || parse_only; } @@ -1128,6 +1225,49 @@ static void gen_log_name(char *name, size_t size, const char *logtype, snprintf(name, size, "%s_%s.%s", logname, logtype, suf); } +static int check_waitees(char *waitee) +{ + struct thread_data *td; + int i, ret = 0; + + for_each_td(td, i) { + if (td->subjob_number) + continue; + + ret += !strcmp(td->o.name, waitee); + } + + return ret; +} + +static bool wait_for_ok(const char *jobname, struct thread_options *o) +{ + int nw; + + if (!o->wait_for) + return true; + + if (!strcmp(jobname, o->wait_for)) { + log_err("%s: a job cannot wait for itself (wait_for=%s).\n", + jobname, o->wait_for); + return false; + } + + if (!(nw = check_waitees(o->wait_for))) { + log_err("%s: waitee job %s unknown.\n", jobname, o->wait_for); + return false; + } + + if (nw > 1) { + log_err("%s: multiple waitees %s found,\n" + "please avoid duplicates when using wait_for option.\n", + jobname, o->wait_for); + return false; + } + + return true; +} + /* * Adds a job to the list of things todo. Sanitizes the various options * to make sure we don't have conflicts, and initializes various @@ -1166,14 +1306,11 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num, if (ioengine_load(td)) goto err; - if (o->odirect) - td->io_ops->flags |= FIO_RAWIO; - file_alloced = 0; if (!o->filename && !td->files_index && !o->read_iolog_file) { file_alloced = 1; - if (o->nr_files == 1 && exists_and_not_file(jobname)) + if (o->nr_files == 1 && exists_and_not_regfile(jobname)) add_file(td, jobname, job_add_num, 0); else { for (i = 0; i < o->nr_files; i++) @@ -1184,6 +1321,12 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num, if (fixup_options(td)) goto err; + /* + * Belongs to fixup_options, but o->name is not necessarily set as yet + */ + if (!wait_for_ok(jobname, o)) + goto err; + flow_init_job(td); /* @@ -1193,7 +1336,7 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num, if (td->eo) *(struct thread_data **)td->eo = NULL; - if (td->io_ops->flags & FIO_DISKLESSIO) { + if (td_ioengine_flagged(td, FIO_DISKLESSIO)) { struct fio_file *f; for_each_file(td, f, i) @@ -1217,6 +1360,10 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num, if ((o->stonewall || o->new_group) && prev_group_jobs) { prev_group_jobs = 0; groupid++; + if (groupid == INT_MAX) { + log_err("fio: too many groups defined\n"); + goto err; + } } td->groupid = groupid; @@ -1230,15 +1377,18 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num, if (setup_rate(td)) goto err; - if (o->lat_log_file) { + if (o->write_lat_log) { struct log_params p = { .td = td, .avg_msec = o->log_avg_msec, + .hist_msec = o->log_hist_msec, + .hist_coarseness = o->log_hist_coarseness, .log_type = IO_LOG_TYPE_LAT, .log_offset = o->log_offset, .log_gz = o->log_gz, .log_gz_store = o->log_gz_store, }; + const char *pre = o->lat_log_file ? o->lat_log_file : o->name; const char *suf; if (p.log_gz_store) @@ -1246,55 +1396,109 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num, else suf = "log"; - gen_log_name(logname, sizeof(logname), "lat", o->lat_log_file, + gen_log_name(logname, sizeof(logname), "lat", pre, td->thread_number, suf, o->per_job_logs); setup_log(&td->lat_log, &p, logname); - gen_log_name(logname, sizeof(logname), "slat", o->lat_log_file, + gen_log_name(logname, sizeof(logname), "slat", pre, td->thread_number, suf, o->per_job_logs); setup_log(&td->slat_log, &p, logname); - gen_log_name(logname, sizeof(logname), "clat", o->lat_log_file, + gen_log_name(logname, sizeof(logname), "clat", pre, td->thread_number, suf, o->per_job_logs); setup_log(&td->clat_log, &p, logname); } - if (o->bw_log_file) { + + if (o->write_hist_log) { + struct log_params p = { + .td = td, + .avg_msec = o->log_avg_msec, + .hist_msec = o->log_hist_msec, + .hist_coarseness = o->log_hist_coarseness, + .log_type = IO_LOG_TYPE_HIST, + .log_offset = o->log_offset, + .log_gz = o->log_gz, + .log_gz_store = o->log_gz_store, + }; + const char *pre = o->hist_log_file ? o->hist_log_file : o->name; + const char *suf; + +#ifndef CONFIG_ZLIB + if (td->client_type) { + log_err("fio: --write_hist_log requires zlib in client/server mode\n"); + goto err; + } +#endif + + if (p.log_gz_store) + suf = "log.fz"; + else + suf = "log"; + + gen_log_name(logname, sizeof(logname), "clat_hist", pre, + td->thread_number, suf, o->per_job_logs); + setup_log(&td->clat_hist_log, &p, logname); + } + + if (o->write_bw_log) { struct log_params p = { .td = td, .avg_msec = o->log_avg_msec, + .hist_msec = o->log_hist_msec, + .hist_coarseness = o->log_hist_coarseness, .log_type = IO_LOG_TYPE_BW, .log_offset = o->log_offset, .log_gz = o->log_gz, .log_gz_store = o->log_gz_store, }; + const char *pre = o->bw_log_file ? o->bw_log_file : o->name; const char *suf; + if (fio_option_is_set(o, bw_avg_time)) + p.avg_msec = min(o->log_avg_msec, o->bw_avg_time); + else + o->bw_avg_time = p.avg_msec; + + p.hist_msec = o->log_hist_msec; + p.hist_coarseness = o->log_hist_coarseness; + if (p.log_gz_store) suf = "log.fz"; else suf = "log"; - gen_log_name(logname, sizeof(logname), "bw", o->bw_log_file, + gen_log_name(logname, sizeof(logname), "bw", pre, td->thread_number, suf, o->per_job_logs); setup_log(&td->bw_log, &p, logname); } - if (o->iops_log_file) { + if (o->write_iops_log) { struct log_params p = { .td = td, .avg_msec = o->log_avg_msec, + .hist_msec = o->log_hist_msec, + .hist_coarseness = o->log_hist_coarseness, .log_type = IO_LOG_TYPE_IOPS, .log_offset = o->log_offset, .log_gz = o->log_gz, .log_gz_store = o->log_gz_store, }; + const char *pre = o->iops_log_file ? o->iops_log_file : o->name; const char *suf; + if (fio_option_is_set(o, iops_avg_time)) + p.avg_msec = min(o->log_avg_msec, o->iops_avg_time); + else + o->iops_avg_time = p.avg_msec; + + p.hist_msec = o->log_hist_msec; + p.hist_coarseness = o->log_hist_coarseness; + if (p.log_gz_store) suf = "log.fz"; else suf = "log"; - gen_log_name(logname, sizeof(logname), "iops", o->iops_log_file, + gen_log_name(logname, sizeof(logname), "iops", pre, td->thread_number, suf, o->per_job_logs); setup_log(&td->iops_log, &p, logname); } @@ -1302,23 +1506,24 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num, if (!o->name) o->name = strdup(jobname); - if (output_format == FIO_OUTPUT_NORMAL) { + if (output_format & FIO_OUTPUT_NORMAL) { if (!job_add_num) { if (is_backend && !recursed) fio_server_send_add_job(td); - if (!(td->io_ops->flags & FIO_NOIO)) { + if (!td_ioengine_flagged(td, FIO_NOIO)) { char *c1, *c2, *c3, *c4; char *c5 = NULL, *c6 = NULL; + int i2p = is_power_of_2(o->kb_base); - c1 = fio_uint_to_kmg(o->min_bs[DDIR_READ]); - c2 = fio_uint_to_kmg(o->max_bs[DDIR_READ]); - c3 = fio_uint_to_kmg(o->min_bs[DDIR_WRITE]); - c4 = fio_uint_to_kmg(o->max_bs[DDIR_WRITE]); + c1 = num2str(o->min_bs[DDIR_READ], 4, 1, i2p, N2S_BYTE); + c2 = num2str(o->max_bs[DDIR_READ], 4, 1, i2p, N2S_BYTE); + c3 = num2str(o->min_bs[DDIR_WRITE], 4, 1, i2p, N2S_BYTE); + c4 = num2str(o->max_bs[DDIR_WRITE], 4, 1, i2p, N2S_BYTE); if (!o->bs_is_seq_rand) { - c5 = fio_uint_to_kmg(o->min_bs[DDIR_TRIM]); - c6 = fio_uint_to_kmg(o->max_bs[DDIR_TRIM]); + c5 = num2str(o->min_bs[DDIR_TRIM], 4, 1, i2p, N2S_BYTE); + c6 = num2str(o->max_bs[DDIR_TRIM], 4, 1, i2p, N2S_BYTE); } log_info("%s: (g=%d): rw=%s, ", td->o.name, @@ -1326,10 +1531,10 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num, ddir_str(o->td_ddir)); if (o->bs_is_seq_rand) - log_info("bs(seq/rand)=%s-%s/%s-%s, ", + log_info("bs=%s-%s,%s-%s, bs_is_seq_rand, ", c1, c2, c3, c4); else - log_info("bs=%s-%s/%s-%s/%s-%s, ", + log_info("bs=%s-%s,%s-%s,%s-%s, ", c1, c2, c3, c4, c5, c6); log_info("ioengine=%s, iodepth=%u\n", @@ -1346,6 +1551,9 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num, log_info("...\n"); } + if (td_steadystate_init(td)) + goto err; + /* * recurse add identical jobs, clear numjobs and stonewall options * as they don't apply to sub-jobs @@ -1361,6 +1569,8 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num, td_new->o.stonewall = 0; td_new->o.new_group = 0; td_new->subjob_number = numjobs; + td_new->o.ss_dur = o->ss_dur * 1000000l; + td_new->o.ss_limit = o->ss_limit; if (file_alloced) { if (td_new->files) { @@ -1418,9 +1628,9 @@ void add_job_opts(const char **o, int client_type) td = get_new_job(0, td_parent, 0, jobname); } if (in_global) - fio_options_parse(td_parent, (char **) &o[i], 1, 0); + fio_options_parse(td_parent, (char **) &o[i], 1); else - fio_options_parse(td, (char **) &o[i], 1, 0); + fio_options_parse(td, (char **) &o[i], 1); i++; } @@ -1463,7 +1673,7 @@ static int is_empty_or_comment(char *line) /* * This is our [ini] type file parser. */ -int __parse_jobs_ini(struct thread_data *td, +static int __parse_jobs_ini(struct thread_data *td, char *file, int is_buf, int stonewall_flag, int type, int nested, char *name, char ***popts, int *aopts, int *nopts) { @@ -1631,15 +1841,48 @@ int __parse_jobs_ini(struct thread_data *td, strip_blank_end(p); if (!strncmp(p, "include", strlen("include"))) { - char *filename = p + strlen("include") + 1; + char *filename = p + strlen("include") + 1, + *ts, *full_fn = NULL; - if ((ret = __parse_jobs_ini(td, filename, - is_buf, stonewall_flag, type, 1, - name, &opts, &alloc_opts, &num_opts))) { - log_err("Error %d while parsing include file %s\n", + /* + * Allow for the include filename + * specification to be relative. + */ + if (access(filename, F_OK) && + (ts = strrchr(file, '/'))) { + int len = ts - file + + strlen(filename) + 2; + + if (!(full_fn = calloc(1, len))) { + ret = ENOMEM; + break; + } + + strncpy(full_fn, + file, (ts - file) + 1); + strncpy(full_fn + (ts - file) + 1, + filename, strlen(filename)); + full_fn[len - 1] = 0; + filename = full_fn; + } + + ret = __parse_jobs_ini(td, filename, is_buf, + stonewall_flag, type, 1, + name, &opts, + &alloc_opts, &num_opts); + + if (ret) { + log_err("Error %d while parsing " + "include file %s\n", ret, filename); - break; } + + if (full_fn) + free(full_fn); + + if (ret) + break; + continue; } @@ -1660,10 +1903,13 @@ int __parse_jobs_ini(struct thread_data *td, goto out; } - ret = fio_options_parse(td, opts, num_opts, dump_cmdline); - if (!ret) + ret = fio_options_parse(td, opts, num_opts); + if (!ret) { + if (dump_cmdline) + dump_opt_list(td); + ret = add_job(td, name, 0, 0, type); - else { + } else { log_err("fio: job %s dropped\n", name); put_job(td); } @@ -1701,6 +1947,7 @@ int parse_jobs_ini(char *file, int is_buf, int stonewall_flag, int type) static int fill_def_thread(void) { memset(&def_thread, 0, sizeof(def_thread)); + INIT_FLIST_HEAD(&def_thread.opt_list); fio_getaffinity(getpid(), &def_thread.o.cpumask); def_thread.o.error_dump = 1; @@ -1714,6 +1961,7 @@ static int fill_def_thread(void) static void show_debug_categories(void) { +#ifdef FIO_INC_DEBUG struct debug_level *dl = &debug_levels[0]; int curlen, first = 1; @@ -1739,8 +1987,14 @@ static void show_debug_categories(void) first = 0; } printf("\n"); +#endif } +/* + * Following options aren't printed by usage(). + * --append-terse - Equivalent to --output-format=terse, see f6a7df53. + * --latency-log - Deprecated option. + */ static void usage(const char *name) { printf("%s\n", fio_version_string); @@ -1749,15 +2003,15 @@ static void usage(const char *name) show_debug_categories(); printf(" --parse-only\t\tParse options only, don't start any IO\n"); printf(" --output\t\tWrite output to file\n"); - printf(" --runtime\t\tRuntime in seconds\n"); - printf(" --bandwidth-log\tGenerate per-job bandwidth logs\n"); + printf(" --bandwidth-log\tGenerate aggregate bandwidth logs\n"); printf(" --minimal\t\tMinimal (terse) output\n"); - printf(" --output-format=x\tOutput format (terse,json,normal)\n"); - printf(" --terse-version=x\tSet terse version output format to 'x'\n"); + printf(" --output-format=type\tOutput format (terse,json,json+,normal)\n"); + printf(" --terse-version=type\tSet terse version output format" + " (default 3, or 2 or 4)\n"); printf(" --version\t\tPrint version info and exit\n"); printf(" --help\t\tPrint this page\n"); printf(" --cpuclock-test\tPerform test/validation of CPU clock\n"); - printf(" --crctest\t\tTest speed of checksum functions\n"); + printf(" --crctest=type\tTest speed of checksum functions\n"); printf(" --cmdhelp=cmd\t\tPrint command help, \"all\" for all of" " them\n"); printf(" --enghelp=engine\tPrint ioengine help, or list" @@ -1773,14 +2027,15 @@ static void usage(const char *name) printf(" 't' period passed\n"); printf(" --readonly\t\tTurn on safety read-only checks, preventing" " writes\n"); - printf(" --section=name\tOnly run specified section in job file\n"); + printf(" --section=name\tOnly run specified section in job file," + " multiple sections can be specified\n"); printf(" --alloc-size=kb\tSet smalloc pool to this size in kb" - " (def 1024)\n"); + " (def 16384)\n"); printf(" --warnings-fatal\tFio parser warnings are fatal\n"); printf(" --max-jobs=nr\t\tMaximum number of threads/processes to support\n"); printf(" --server=args\t\tStart a backend fio server\n"); printf(" --daemonize=pidfile\tBackground fio server, write pid to file\n"); - printf(" --client=hostname\tTalk to remote backend fio server at hostname\n"); + printf(" --client=hostname\tTalk to remote backend(s) fio server at hostname\n"); printf(" --remote-config=file\tTell fio server to load this local job file\n"); printf(" --idle-prof=option\tReport cpu idleness on a system or percpu basis\n" "\t\t\t(option=system,percpu) or run unit work\n" @@ -1792,6 +2047,7 @@ static void usage(const char *name) printf(" --trigger-timeout=t\tExecute trigger af this time\n"); printf(" --trigger=cmd\t\tSet this command as local trigger\n"); printf(" --trigger-remote=cmd\tSet this command as remote trigger\n"); + printf(" --aux-path=path\tUse this path for fio state generated files\n"); printf("\nFio was written by Jens Axboe "); printf("\n Jens Axboe "); printf("\n Jens Axboe \n"); @@ -1863,6 +2119,14 @@ struct debug_level debug_levels[] = { .help = "Log compression logging", .shift = FD_COMPRESS, }, + { .name = "steadystate", + .help = "Steady state detection logging", + .shift = FD_STEADYSTATE, + }, + { .name = "helperthread", + .help = "Helper thread logging", + .shift = FD_HELPERTHREAD, + }, { .name = NULL, }, }; @@ -1873,6 +2137,9 @@ static int set_debug(const char *string) char *opt; int i; + if (!string) + return 0; + if (!strcmp(string, "?") || !strcmp(string, "help")) { log_info("fio: dumping debug options:"); for (i = 0; debug_levels[i].name; i++) { @@ -1989,16 +2256,47 @@ static void show_closest_option(const char *name) i++; } - if (best_option != -1) + if (best_option != -1 && string_distance_ok(name, best_distance)) log_err("Did you mean %s?\n", l_opts[best_option].name); } +static int parse_output_format(const char *optarg) +{ + char *p, *orig, *opt; + int ret = 0; + + p = orig = strdup(optarg); + + output_format = 0; + + while ((opt = strsep(&p, ",")) != NULL) { + if (!strcmp(opt, "minimal") || + !strcmp(opt, "terse") || + !strcmp(opt, "csv")) + output_format |= FIO_OUTPUT_TERSE; + else if (!strcmp(opt, "json")) + output_format |= FIO_OUTPUT_JSON; + else if (!strcmp(opt, "json+")) + output_format |= (FIO_OUTPUT_JSON | FIO_OUTPUT_JSON_PLUS); + else if (!strcmp(opt, "normal")) + output_format |= FIO_OUTPUT_NORMAL; + else { + log_err("fio: invalid output format %s\n", opt); + ret = 1; + break; + } + } + + free(orig); + return ret; +} + int parse_cmd_line(int argc, char *argv[], int client_type) { struct thread_data *td = NULL; int c, ini_idx = 0, lidx, ret = 0, do_exit = 0, exit_val = 0; char *ostr = cmd_optstr; - void *pid_file = NULL; + char *pid_file = NULL; void *cur_client = NULL; int backend = 0; @@ -2017,13 +2315,8 @@ int parse_cmd_line(int argc, char *argv[], int client_type) switch (c) { case 'a': smalloc_pool_size = atoi(optarg); - break; - case 't': - if (check_str_time(optarg, &def_timeout, 1)) { - log_err("fio: failed parsing time %s\n", optarg); - do_exit++; - exit_val = 1; - } + smalloc_pool_size <<= 10; + sinit(); break; case 'l': log_err("fio: --latency-log is deprecated. Use per-job latency log options.\n"); @@ -2033,38 +2326,35 @@ int parse_cmd_line(int argc, char *argv[], int client_type) case 'b': write_bw_log = 1; break; - case 'o': + case 'o': { + FILE *tmp; + if (f_out && f_out != stdout) fclose(f_out); - f_out = fopen(optarg, "w+"); - if (!f_out) { - perror("fopen output"); - exit(1); + tmp = fopen(optarg, "w+"); + if (!tmp) { + log_err("fio: output file open error: %s\n", strerror(errno)); + exit_val = 1; + do_exit++; + break; } - f_err = f_out; + f_err = f_out = tmp; break; + } case 'm': output_format = FIO_OUTPUT_TERSE; break; case 'F': - if (!optarg) { - log_err("fio: missing --output-format argument\n"); + if (parse_output_format(optarg)) { + log_err("fio: failed parsing output-format\n"); exit_val = 1; do_exit++; break; } - if (!strcmp(optarg, "minimal") || - !strcmp(optarg, "terse") || - !strcmp(optarg, "csv")) - output_format = FIO_OUTPUT_TERSE; - else if (!strcmp(optarg, "json")) - output_format = FIO_OUTPUT_JSON; - else - output_format = FIO_OUTPUT_NORMAL; break; case 'f': - append_terse_output = 1; + output_format |= FIO_OUTPUT_TERSE; break; case 'h': did_arg = 1; @@ -2333,14 +2623,14 @@ int parse_cmd_line(int argc, char *argv[], int client_type) !strncmp(argv[optind], "-", 1)) break; - if (fio_client_add_ini_file(cur_client, argv[optind], 0)) + if (fio_client_add_ini_file(cur_client, argv[optind], false)) break; optind++; } break; case 'R': did_arg = 1; - if (fio_client_add_ini_file(cur_client, optarg, 1)) { + if (fio_client_add_ini_file(cur_client, optarg, true)) { do_exit++; exit_val = 1; } @@ -2382,6 +2672,11 @@ int parse_cmd_line(int argc, char *argv[], int client_type) free(trigger_remote_cmd); trigger_remote_cmd = strdup(optarg); break; + case 'K': + if (aux_path) + free(aux_path); + aux_path = strdup(optarg); + break; case 'B': if (check_str_time(optarg, &trigger_timeout, 1)) { log_err("fio: failed parsing time %s\n", optarg); @@ -2418,7 +2713,7 @@ int parse_cmd_line(int argc, char *argv[], int client_type) if (!ret) { ret = add_job(td, td->o.name ?: "fio", 0, 0, client_type); if (ret) - did_arg = 1; + exit(1); } } @@ -2430,9 +2725,6 @@ int parse_cmd_line(int argc, char *argv[], int client_type) } out_free: - if (pid_file) - free(pid_file); - return ini_idx; } @@ -2501,7 +2793,7 @@ int parse_options(int argc, char *argv[]) if (did_arg) return 0; - log_err("No jobs(s) defined\n\n"); + log_err("No job(s) defined\n\n"); if (!did_arg) { usage(argv[0]); @@ -2511,7 +2803,7 @@ int parse_options(int argc, char *argv[]) return 0; } - if (output_format == FIO_OUTPUT_NORMAL) + if (output_format & FIO_OUTPUT_NORMAL) log_info("%s\n", fio_version_string); return 0; @@ -2521,3 +2813,8 @@ void options_default_fill(struct thread_options *o) { memcpy(o, &def_thread.o, sizeof(*o)); } + +struct thread_data *get_global_options(void) +{ + return &def_thread; +}