X-Git-Url: https://git.kernel.dk/?a=blobdiff_plain;f=init.c;h=eea6e54692b177036dce001134f8ed1baeb62ca8;hb=HEAD;hp=7514d1ab0af536b288eb5b35be04fe661a2c3c99;hpb=4806b82473fea74c517e5e0c7665b0ca0542b3ec;p=fio.git diff --git a/init.c b/init.c index 7514d1ab..ff3e9a90 100644 --- a/init.c +++ b/init.c @@ -30,7 +30,9 @@ #include "idletime.h" #include "filelock.h" #include "steadystate.h" +#include "blktrace.h" +#include "oslib/asprintf.h" #include "oslib/getopt.h" #include "oslib/strcasestr.h" @@ -43,16 +45,16 @@ const char fio_version_string[] = FIO_VERSION; #define FIO_RANDSEED (0xb1899bedUL) static char **ini_file; -static int max_jobs = FIO_MAX_JOBS; -static int dump_cmdline; -static int parse_only; +static bool dump_cmdline; +static bool parse_only; +static bool merge_blktrace_only; static struct thread_data def_thread; -struct thread_data *threads = NULL; +struct thread_segment segments[REAL_MAX_SEG]; static char **job_sections; static int nr_job_sections; -int exitall_on_terminate = 0; +bool exitall_on_terminate = false; int output_format = FIO_OUTPUT_NORMAL; int eta_print = FIO_ETA_AUTO; unsigned int eta_interval_msec = 1000; @@ -62,13 +64,13 @@ FILE *f_err = NULL; char *exec_profile = NULL; int warnings_fatal = 0; int terse_version = 3; -int is_backend = 0; -int is_local_backend = 0; +bool is_backend = false; +bool is_local_backend = false; int nr_clients = 0; -int log_syslog = 0; +bool log_syslog = false; -int write_bw_log = 0; -int read_only = 0; +bool write_bw_log = false; +bool read_only = false; int status_interval = 0; char *trigger_file = NULL; @@ -222,6 +224,13 @@ static struct option l_opts[FIO_NR_OPTIONS] = { .has_arg = optional_argument, .val = 'S', }, +#ifdef WIN32 + { + .name = (char *) "server-internal", + .has_arg = required_argument, + .val = 'N', + }, +#endif { .name = (char *) "daemonize", .has_arg = required_argument, .val = 'D', @@ -286,6 +295,11 @@ static struct option l_opts[FIO_NR_OPTIONS] = { .has_arg = required_argument, .val = 'K', }, + { + .name = (char *) "merge-blktrace-only", + .has_arg = no_argument, + .val = 'A' | FIO_CLIENT_FLAG, + }, { .name = NULL, }, @@ -293,25 +307,35 @@ static struct option l_opts[FIO_NR_OPTIONS] = { void free_threads_shm(void) { - if (threads) { - void *tp = threads; + int i; + + for (i = 0; i < nr_segments; i++) { + struct thread_segment *seg = &segments[i]; + + if (seg->threads) { + void *tp = seg->threads; #ifndef CONFIG_NO_SHM - struct shmid_ds sbuf; + struct shmid_ds sbuf; - threads = NULL; - shmdt(tp); - shmctl(shm_id, IPC_RMID, &sbuf); - shm_id = -1; + seg->threads = NULL; + shmdt(tp); + shmctl(seg->shm_id, IPC_RMID, &sbuf); + seg->shm_id = -1; #else - threads = NULL; - free(tp); + seg->threads = NULL; + free(tp); #endif + } } + + nr_segments = 0; + cur_segment = 0; } static void free_shm(void) { - if (threads) { +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + if (nr_segments) { flow_exit(); fio_debug_jobp = NULL; fio_warned = NULL; @@ -327,73 +351,82 @@ static void free_shm(void) fio_filelock_exit(); file_hash_exit(); scleanup(); +#endif } -/* - * The thread area is shared between the main process and the job - * threads/processes. So setup a shared memory segment that will hold - * all the job info. We use the end of the region for keeping track of - * open files across jobs, for file sharing. - */ -static int setup_thread_area(void) +static int add_thread_segment(void) { + struct thread_segment *seg = &segments[nr_segments]; + size_t size = JOBS_PER_SEG * sizeof(struct thread_data); int i; - if (threads) - return 0; - - /* - * 1024 is too much on some machines, scale max_jobs if - * we get a failure that looks like too large a shm segment - */ - do { - size_t size = max_jobs * sizeof(struct thread_data); + if (nr_segments + 1 >= REAL_MAX_SEG) { + log_err("error: maximum number of jobs reached.\n"); + return -1; + } - size += 2 * sizeof(unsigned int); + size += 2 * sizeof(unsigned int); #ifndef CONFIG_NO_SHM - shm_id = shmget(0, size, IPC_CREAT | 0600); - if (shm_id != -1) - break; - if (errno != EINVAL && errno != ENOMEM && errno != ENOSPC) { + seg->shm_id = shmget(0, size, IPC_CREAT | 0600); + if (seg->shm_id == -1) { + if (errno != EINVAL && errno != ENOMEM && errno != ENOSPC) perror("shmget"); - break; - } + return -1; + } #else - threads = malloc(size); - if (threads) - break; + seg->threads = malloc(size); + if (!seg->threads) + return -1; #endif - max_jobs >>= 1; - } while (max_jobs); - #ifndef CONFIG_NO_SHM - if (shm_id == -1) - return 1; - - threads = shmat(shm_id, NULL, 0); - if (threads == (void *) -1) { + seg->threads = shmat(seg->shm_id, NULL, 0); + if (seg->threads == (void *) -1) { perror("shmat"); return 1; } if (shm_attach_to_open_removed()) - shmctl(shm_id, IPC_RMID, NULL); + shmctl(seg->shm_id, IPC_RMID, NULL); #endif - memset(threads, 0, max_jobs * sizeof(struct thread_data)); - for (i = 0; i < max_jobs; i++) - DRD_IGNORE_VAR(threads[i]); - fio_debug_jobp = (unsigned int *)(threads + max_jobs); + nr_segments++; + + memset(seg->threads, 0, JOBS_PER_SEG * sizeof(struct thread_data)); + for (i = 0; i < JOBS_PER_SEG; i++) + DRD_IGNORE_VAR(seg->threads[i]); + seg->nr_threads = 0; + + /* Not first segment, we're done */ + if (nr_segments != 1) { + cur_segment++; + return 0; + } + + fio_debug_jobp = (unsigned int *)(seg->threads + JOBS_PER_SEG); *fio_debug_jobp = -1; fio_warned = fio_debug_jobp + 1; *fio_warned = 0; flow_init(); - return 0; } +/* + * The thread areas are shared between the main process and the job + * threads/processes, and is split into chunks of JOBS_PER_SEG. If the current + * segment has no more room, add a new chunk. + */ +static int expand_thread_area(void) +{ + struct thread_segment *seg = &segments[cur_segment]; + + if (nr_segments && seg->nr_threads < JOBS_PER_SEG) + return 0; + + return add_thread_segment(); +} + static void dump_print_option(struct print_option *p) { const char *delim; @@ -422,19 +455,6 @@ static void dump_opt_list(struct thread_data *td) } } -static void fio_dump_options_free(struct thread_data *td) -{ - while (!flist_empty(&td->opt_list)) { - struct print_option *p; - - p = flist_first_entry(&td->opt_list, struct print_option, list); - flist_del_init(&p->list); - free(p->name); - free(p->value); - free(p); - } -} - static void copy_opt_list(struct thread_data *dst, struct thread_data *src) { struct flist_head *entry; @@ -462,21 +482,19 @@ static void copy_opt_list(struct thread_data *dst, struct thread_data *src) static struct thread_data *get_new_job(bool global, struct thread_data *parent, bool preserve_eo, const char *jobname) { + struct thread_segment *seg; struct thread_data *td; if (global) return &def_thread; - if (setup_thread_area()) { + if (expand_thread_area()) { log_err("error: failed to setup shm segment\n"); return NULL; } - if (thread_number >= max_jobs) { - log_err("error: maximum number of jobs (%d) reached.\n", - max_jobs); - return NULL; - } - td = &threads[thread_number++]; + seg = &segments[cur_segment]; + td = &seg->threads[seg->nr_threads++]; + thread_number++; *td = *parent; INIT_FLIST_HEAD(&td->opt_list); @@ -526,7 +544,8 @@ static void put_job(struct thread_data *td) if (td->o.name) free(td->o.name); - memset(&threads[td->thread_number - 1], 0, sizeof(*td)); + memset(td, 0, sizeof(*td)); + segments[cur_segment].nr_threads--; thread_number--; } @@ -556,13 +575,11 @@ static int setup_rate(struct thread_data *td) { int ret = 0; - if (td->o.rate[DDIR_READ] || td->o.rate_iops[DDIR_READ]) - ret = __setup_rate(td, DDIR_READ); - if (td->o.rate[DDIR_WRITE] || td->o.rate_iops[DDIR_WRITE]) - ret |= __setup_rate(td, DDIR_WRITE); - if (td->o.rate[DDIR_TRIM] || td->o.rate_iops[DDIR_TRIM]) - ret |= __setup_rate(td, DDIR_TRIM); - + for_each_rw_ddir(ddir) { + if (td->o.rate[ddir] || td->o.rate_iops[ddir]) { + ret |= __setup_rate(td, ddir); + } + } return ret; } @@ -601,6 +618,19 @@ static int fixup_options(struct thread_data *td) ret |= 1; } + if (td_trimwrite(td) && o->num_range > 1) { + log_err("fio: trimwrite cannot be used with multiple" + " ranges.\n"); + ret |= 1; + } + + if (td_trim(td) && o->num_range > 1 && + !td_ioengine_flagged(td, FIO_MULTI_RANGE_TRIM)) { + log_err("fio: can't use multiple ranges with IO engine %s\n", + td->io_ops->name); + ret |= 1; + } + #ifndef CONFIG_PSHARED if (!o->use_thread) { log_info("fio: this platform does not support process shared" @@ -618,17 +648,39 @@ static int fixup_options(struct thread_data *td) ret |= warnings_fatal; } + if (o->zone_mode == ZONE_MODE_NONE && o->zone_size) { + log_err("fio: --zonemode=none and --zonesize are not compatible.\n"); + ret |= 1; + } + + if (o->zone_mode == ZONE_MODE_ZBD && !o->create_serialize) { + log_err("fio: --zonemode=zbd and --create_serialize=0 are not compatible.\n"); + ret |= 1; + } + + if (o->zone_mode == ZONE_MODE_STRIDED && !o->zone_size) { + log_err("fio: --zonesize must be specified when using --zonemode=strided.\n"); + ret |= 1; + } + + if (o->zone_mode == ZONE_MODE_NOT_SPECIFIED) { + if (o->zone_size) + o->zone_mode = ZONE_MODE_STRIDED; + else + o->zone_mode = ZONE_MODE_NONE; + } + /* - * only really works with 1 file + * Strided zone mode only really works with 1 file. */ - if (o->zone_size && o->open_files > 1) - o->zone_size = 0; + if (o->zone_mode == ZONE_MODE_STRIDED && o->open_files > 1) + o->zone_mode = ZONE_MODE_NONE; /* * If zone_range isn't specified, backward compatibility dictates it * should be made equal to zone_size. */ - if (o->zone_size && !o->zone_range) + if (o->zone_mode == ZONE_MODE_STRIDED && !o->zone_range) o->zone_range = o->zone_size; /* @@ -637,31 +689,25 @@ static int fixup_options(struct thread_data *td) if (td_read(td)) o->overwrite = 1; - if (!o->min_bs[DDIR_READ]) - o->min_bs[DDIR_READ] = o->bs[DDIR_READ]; - if (!o->max_bs[DDIR_READ]) - o->max_bs[DDIR_READ] = o->bs[DDIR_READ]; - if (!o->min_bs[DDIR_WRITE]) - o->min_bs[DDIR_WRITE] = o->bs[DDIR_WRITE]; - if (!o->max_bs[DDIR_WRITE]) - o->max_bs[DDIR_WRITE] = o->bs[DDIR_WRITE]; - if (!o->min_bs[DDIR_TRIM]) - o->min_bs[DDIR_TRIM] = o->bs[DDIR_TRIM]; - if (!o->max_bs[DDIR_TRIM]) - o->max_bs[DDIR_TRIM] = o->bs[DDIR_TRIM]; - - o->rw_min_bs = min(o->min_bs[DDIR_READ], o->min_bs[DDIR_WRITE]); - o->rw_min_bs = min(o->min_bs[DDIR_TRIM], o->rw_min_bs); + for_each_rw_ddir(ddir) { + if (!o->min_bs[ddir]) + o->min_bs[ddir] = o->bs[ddir]; + if (!o->max_bs[ddir]) + o->max_bs[ddir] = o->bs[ddir]; + } + + o->rw_min_bs = -1; + for_each_rw_ddir(ddir) { + o->rw_min_bs = min(o->rw_min_bs, o->min_bs[ddir]); + } /* * For random IO, allow blockalign offset other than min_bs. */ - if (!o->ba[DDIR_READ] || !td_random(td)) - o->ba[DDIR_READ] = o->min_bs[DDIR_READ]; - if (!o->ba[DDIR_WRITE] || !td_random(td)) - o->ba[DDIR_WRITE] = o->min_bs[DDIR_WRITE]; - if (!o->ba[DDIR_TRIM] || !td_random(td)) - o->ba[DDIR_TRIM] = o->min_bs[DDIR_TRIM]; + for_each_rw_ddir(ddir) { + if (!o->ba[ddir] || !td_random(td)) + o->ba[ddir] = o->min_bs[ddir]; + } if ((o->ba[DDIR_READ] != o->min_bs[DDIR_READ] || o->ba[DDIR_WRITE] != o->min_bs[DDIR_WRITE] || @@ -720,19 +766,12 @@ static int fixup_options(struct thread_data *td) /* * There's no need to check for in-flight overlapping IOs if the job * isn't changing data or the maximum iodepth is guaranteed to be 1 + * when we are not in offload mode */ if (o->serialize_overlap && !(td->flags & TD_F_READ_IOLOG) && - (!(td_write(td) || td_trim(td)) || o->iodepth == 1)) + (!(td_write(td) || td_trim(td)) || o->iodepth == 1) && + o->io_submit_mode != IO_MODE_OFFLOAD) o->serialize_overlap = 0; - /* - * Currently can't check for overlaps in offload mode - */ - if (o->serialize_overlap && o->io_submit_mode == IO_MODE_OFFLOAD) { - log_err("fio: checking for in-flight overlaps when the " - "io_submit_mode is offload is not supported\n"); - o->serialize_overlap = 0; - ret |= warnings_fatal; - } if (o->nr_files > td->files_index) o->nr_files = td->files_index; @@ -747,14 +786,12 @@ static int fixup_options(struct thread_data *td) log_err("fio: rate and rate_iops are mutually exclusive\n"); ret |= 1; } - if ((o->rate[DDIR_READ] && (o->rate[DDIR_READ] < o->ratemin[DDIR_READ])) || - (o->rate[DDIR_WRITE] && (o->rate[DDIR_WRITE] < o->ratemin[DDIR_WRITE])) || - (o->rate[DDIR_TRIM] && (o->rate[DDIR_TRIM] < o->ratemin[DDIR_TRIM])) || - (o->rate_iops[DDIR_READ] && (o->rate_iops[DDIR_READ] < o->rate_iops_min[DDIR_READ])) || - (o->rate_iops[DDIR_WRITE] && (o->rate_iops[DDIR_WRITE] < o->rate_iops_min[DDIR_WRITE])) || - (o->rate_iops[DDIR_TRIM] && (o->rate_iops[DDIR_TRIM] < o->rate_iops_min[DDIR_TRIM]))) { - log_err("fio: minimum rate exceeds rate\n"); - ret |= 1; + for_each_rw_ddir(ddir) { + if ((o->rate[ddir] && (o->rate[ddir] < o->ratemin[ddir])) || + (o->rate_iops[ddir] && (o->rate_iops[ddir] < o->rate_iops_min[ddir]))) { + log_err("fio: minimum rate exceeds rate, ddir %d\n", +ddir); + ret |= 1; + } } if (!o->timeout && o->time_based) { @@ -835,11 +872,6 @@ static int fixup_options(struct thread_data *td) o->unit_base = N2S_BYTEPERSEC; } -#ifndef FIO_HAVE_ANY_FALLOCATE - /* Platform doesn't support any fallocate so force it to none */ - o->fallocate_mode = FIO_FALLOCATE_NONE; -#endif - #ifndef CONFIG_FDATASYNC if (o->fdatasync_blocks) { log_info("fio: this platform does not support fdatasync()" @@ -897,12 +929,6 @@ static int fixup_options(struct thread_data *td) ret |= 1; } - /* - * O_ATOMIC implies O_DIRECT - */ - if (o->oatomic) - o->odirect = 1; - /* * If randseed is set, that overrides randrepeat */ @@ -931,32 +957,73 @@ static int fixup_options(struct thread_data *td) ret |= 1; } - if (fio_option_is_set(o, clat_percentiles) && - !fio_option_is_set(o, lat_percentiles)) { - o->lat_percentiles = !o->clat_percentiles; - } else if (fio_option_is_set(o, lat_percentiles) && - !fio_option_is_set(o, clat_percentiles)) { - o->clat_percentiles = !o->lat_percentiles; - } else if (fio_option_is_set(o, lat_percentiles) && - fio_option_is_set(o, clat_percentiles) && - o->lat_percentiles && o->clat_percentiles) { - log_err("fio: lat_percentiles and clat_percentiles are " - "mutually exclusive\n"); - ret |= 1; - } - if (o->disable_lat) o->lat_percentiles = 0; if (o->disable_clat) o->clat_percentiles = 0; + if (o->disable_slat) + o->slat_percentiles = 0; + + /* Do this only for the parent job */ + if (!td->subjob_number) { + /* + * Fix these up to be nsec internally + */ + for_each_rw_ddir(ddir) + o->max_latency[ddir] *= 1000ULL; + + o->latency_target *= 1000ULL; + } /* - * Fix these up to be nsec internally + * Dedupe working set verifications */ - o->max_latency *= 1000ULL; - o->latency_target *= 1000ULL; - o->latency_window *= 1000ULL; + if (o->dedupe_percentage && o->dedupe_mode == DEDUPE_MODE_WORKING_SET) { + if (!fio_option_is_set(o, size)) { + log_err("fio: pregenerated dedupe working set " + "requires size to be set\n"); + ret |= 1; + } else if (o->nr_files != 1) { + log_err("fio: dedupe working set mode supported with " + "single file per job, but %d files " + "provided\n", o->nr_files); + ret |= 1; + } else if (o->dedupe_working_set_percentage + o->dedupe_percentage > 100) { + log_err("fio: impossible to reach expected dedupe percentage %u " + "since %u percentage of size is reserved to dedupe working set " + "(those are unique pages)\n", + o->dedupe_percentage, o->dedupe_working_set_percentage); + ret |= 1; + } + } + + for_each_td(td2) { + if (td->o.ss_check_interval != td2->o.ss_check_interval) { + log_err("fio: conflicting ss_check_interval: %llu and %llu, must be globally equal\n", + td->o.ss_check_interval, td2->o.ss_check_interval); + ret |= 1; + } + } end_for_each(); + if (td->o.ss_dur && td->o.ss_check_interval / 1000L < 1000) { + log_err("fio: ss_check_interval must be at least 1s\n"); + ret |= 1; + + } + if (td->o.ss_dur && (td->o.ss_dur % td->o.ss_check_interval != 0 || td->o.ss_dur <= td->o.ss_check_interval)) { + log_err("fio: ss_duration %lluus must be multiple of ss_check_interval %lluus\n", + td->o.ss_dur, td->o.ss_check_interval); + ret |= 1; + } + if (td->o.fdp) { + if (fio_option_is_set(&td->o, dp_type) && + (td->o.dp_type == FIO_DP_STREAMS || td->o.dp_type == FIO_DP_NONE)) { + log_err("fio: fdp=1 is not compatible with dataplacement={streams, none}\n"); + ret |= 1; + } else { + td->o.dp_type = FIO_DP_FDP; + } + } return ret; } @@ -966,19 +1033,23 @@ static void init_rand_file_service(struct thread_data *td) const unsigned int seed = td->rand_seeds[FIO_RAND_FILE_OFF]; if (td->o.file_service_type == FIO_FSERVICE_ZIPF) { - zipf_init(&td->next_file_zipf, nranges, td->zipf_theta, seed); + zipf_init(&td->next_file_zipf, nranges, td->zipf_theta, td->random_center, seed); zipf_disable_hash(&td->next_file_zipf); } else if (td->o.file_service_type == FIO_FSERVICE_PARETO) { - pareto_init(&td->next_file_zipf, nranges, td->pareto_h, seed); + pareto_init(&td->next_file_zipf, nranges, td->pareto_h, td->random_center, seed); zipf_disable_hash(&td->next_file_zipf); } else if (td->o.file_service_type == FIO_FSERVICE_GAUSS) { - gauss_init(&td->next_file_gauss, nranges, td->gauss_dev, seed); + gauss_init(&td->next_file_gauss, nranges, td->gauss_dev, td->random_center, seed); gauss_disable_hash(&td->next_file_gauss); } } -void td_fill_verify_state_seed(struct thread_data *td) +void td_fill_rand_seeds(struct thread_data *td) { + uint64_t read_seed = td->rand_seeds[FIO_RAND_BS_OFF]; + uint64_t write_seed = td->rand_seeds[FIO_RAND_BS1_OFF]; + uint64_t trim_seed = td->rand_seeds[FIO_RAND_BS2_OFF]; + int i; bool use64; if (td->o.random_generator == FIO_RAND_GEN_TAUSWORTHE64) @@ -986,17 +1057,6 @@ void td_fill_verify_state_seed(struct thread_data *td) else use64 = false; - 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, bool use64) -{ - unsigned int read_seed = td->rand_seeds[FIO_RAND_BS_OFF]; - unsigned int write_seed = td->rand_seeds[FIO_RAND_BS1_OFF]; - unsigned int trim_seed = td->rand_seeds[FIO_RAND_BS2_OFF]; - int i; - /* * trimwrite is special in that we need to generate the same * offsets to get the "write after trim" effect. If we are @@ -1013,7 +1073,8 @@ static void td_fill_rand_seeds_internal(struct thread_data *td, bool use64) init_rand_seed(&td->bsrange_state[DDIR_WRITE], write_seed, use64); init_rand_seed(&td->bsrange_state[DDIR_TRIM], trim_seed, use64); - td_fill_verify_state_seed(td); + 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], false); if (td->o.file_service_type == FIO_FSERVICE_RANDOM) @@ -1029,12 +1090,8 @@ static void td_fill_rand_seeds_internal(struct thread_data *td, bool use64) init_rand_seed(&td->poisson_state[2], td->rand_seeds[FIO_RAND_POISSON3_OFF], 0); init_rand_seed(&td->dedupe_state, td->rand_seeds[FIO_DEDUPE_OFF], false); init_rand_seed(&td->zone_state, td->rand_seeds[FIO_RAND_ZONE_OFF], false); - - if (!td_random(td)) - return; - - if (td->o.rand_repeatable) - td->rand_seeds[FIO_RAND_BLOCK_OFF] = FIO_RANDSEED * td->thread_number; + init_rand_seed(&td->prio_state, td->rand_seeds[FIO_RAND_PRIO_CMDS], false); + init_rand_seed(&td->dedupe_working_set_index_state, td->rand_seeds[FIO_RAND_DEDUPE_WORKING_SET_IX], use64); init_rand_seed(&td->random_state, td->rand_seeds[FIO_RAND_BLOCK_OFF], use64); @@ -1043,29 +1100,41 @@ static void td_fill_rand_seeds_internal(struct thread_data *td, bool use64) init_rand_seed(s, td->rand_seeds[FIO_RAND_SEQ_RAND_READ_OFF], false); } + + 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->fdp_state, td->rand_seeds[FIO_RAND_FDP_OFF], use64); } -void td_fill_rand_seeds(struct thread_data *td) +static int setup_random_seeds(struct thread_data *td) { - bool use64; - - if (td->o.allrand_repeatable) { - unsigned int i; + uint64_t seed; + unsigned int i; - for (i = 0; i < FIO_RAND_NR_OFFS; i++) - td->rand_seeds[i] = FIO_RANDSEED * td->thread_number - + i; + if (!td->o.rand_repeatable && !fio_option_is_set(&td->o, rand_seed)) { + int ret = init_random_seeds(td->rand_seeds, sizeof(td->rand_seeds)); + dprint(FD_RANDOM, "using system RNG for random seeds\n"); + if (ret) + return ret; + } else { + seed = td->o.rand_seed; + for (i = 0; i < 4; i++) + seed *= 0x9e370001UL; + + for (i = 0; i < FIO_RAND_NR_OFFS; i++) { + td->rand_seeds[i] = seed * td->thread_number + i; + seed *= 0x9e370001UL; + } } - if (td->o.random_generator == FIO_RAND_GEN_TAUSWORTHE64) - use64 = true; - else - use64 = false; + td_fill_rand_seeds(td); - td_fill_rand_seeds_internal(td, use64); + dprint(FD_RANDOM, "FIO_RAND_NR_OFFS=%d\n", FIO_RAND_NR_OFFS); + for (int i = 0; i < FIO_RAND_NR_OFFS; i++) + dprint(FD_RANDOM, "rand_seeds[%d]=%" PRIu64 "\n", i, td->rand_seeds[i]); - init_rand_seed(&td->buf_state, td->rand_seeds[FIO_RAND_BUF_OFF], use64); - frand_copy(&td->buf_state_prev, &td->buf_state); + return 0; } /* @@ -1096,15 +1165,15 @@ int ioengine_load(struct thread_data *td) * for this name and see if they match. If they do, then * the engine is unchanged. */ - dlhandle = td->io_ops_dlhandle; + dlhandle = td->io_ops->dlhandle; ops = load_ioengine(td); - if (ops == td->io_ops && dlhandle == td->io_ops_dlhandle) { - if (dlhandle) - dlclose(dlhandle); + if (!ops) + goto fail; + + if (ops == td->io_ops && dlhandle == td->io_ops->dlhandle) return 0; - } - if (dlhandle && dlhandle != td->io_ops_dlhandle) + if (dlhandle && dlhandle != td->io_ops->dlhandle) dlclose(dlhandle); /* Unload the old engine. */ @@ -1112,10 +1181,8 @@ int ioengine_load(struct thread_data *td) } td->io_ops = load_ioengine(td); - if (!td->io_ops) { - log_err("fio: failed to load engine\n"); - return 1; - } + if (!td->io_ops) + goto fail; if (td->io_ops->option_struct_size && td->io_ops->options) { /* @@ -1154,6 +1221,11 @@ int ioengine_load(struct thread_data *td) td_set_ioengine_flags(td); return 0; + +fail: + log_err("fio: failed to load engine\n"); + return 1; + } static void init_flags(struct thread_data *td) @@ -1198,36 +1270,12 @@ static void init_flags(struct thread_data *td) } } -static int setup_random_seeds(struct thread_data *td) -{ - unsigned long seed; - unsigned int i; - - if (!td->o.rand_repeatable && !fio_option_is_set(&td->o, rand_seed)) { - int ret = init_random_seeds(td->rand_seeds, sizeof(td->rand_seeds)); - if (!ret) - td_fill_rand_seeds(td); - return ret; - } - - seed = td->o.rand_seed; - for (i = 0; i < 4; i++) - seed *= 0x9e370001UL; - - for (i = 0; i < FIO_RAND_NR_OFFS; i++) { - td->rand_seeds[i] = seed * td->thread_number + i; - seed *= 0x9e370001UL; - } - - td_fill_rand_seeds(td); - return 0; -} - enum { FPRE_NONE = 0, FPRE_JOBNAME, FPRE_JOBNUM, - FPRE_FILENUM + FPRE_FILENUM, + FPRE_CLIENTUID }; static struct fpre_keyword { @@ -1238,6 +1286,7 @@ static struct fpre_keyword { { .keyword = "$jobname", .key = FPRE_JOBNAME, }, { .keyword = "$jobnum", .key = FPRE_JOBNUM, }, { .keyword = "$filenum", .key = FPRE_FILENUM, }, + { .keyword = "$clientuid", .key = FPRE_CLIENTUID, }, { .keyword = NULL, }, }; @@ -1256,8 +1305,7 @@ static char *make_filename(char *buf, size_t buf_size,struct thread_options *o, for (f = &fpre_keywords[0]; f->keyword; f++) f->strlen = strlen(f->keyword); - buf[buf_size - 1] = '\0'; - strncpy(buf, o->filename_format, buf_size - 1); + snprintf(buf, buf_size, "%s", o->filename_format); memset(copy, 0, sizeof(copy)); for (f = &fpre_keywords[0]; f->keyword; f++) { @@ -1328,6 +1376,21 @@ static char *make_filename(char *buf, size_t buf_size,struct thread_options *o, } break; } + case FPRE_CLIENTUID: { + int ret; + ret = snprintf(dst, dst_left, "%s", client_sockaddr_str); + if (ret < 0) + break; + else if (ret > dst_left) { + log_err("fio: truncated filename\n"); + dst += dst_left; + dst_left = 0; + } else { + dst += ret; + dst_left -= ret; + } + break; + } default: assert(0); break; @@ -1336,7 +1399,7 @@ static char *make_filename(char *buf, size_t buf_size,struct thread_options *o, if (post_start) strncpy(dst, buf + post_start, dst_left); - strncpy(buf, copy, buf_size - 1); + snprintf(buf, buf_size, "%s", copy); } while (1); } @@ -1360,15 +1423,14 @@ static void gen_log_name(char *name, size_t size, const char *logtype, static int check_waitees(char *waitee) { - struct thread_data *td; - int i, ret = 0; + int ret = 0; - for_each_td(td, i) { + for_each_td(td) { if (td->subjob_number) continue; ret += !strcmp(td->o.name, waitee); - } + } end_for_each(); return ret; } @@ -1401,6 +1463,34 @@ static bool wait_for_ok(const char *jobname, struct thread_options *o) return true; } +static int verify_per_group_options(struct thread_data *td, const char *jobname) +{ + for_each_td(td2) { + if (td->groupid != td2->groupid) + continue; + + if (td->o.stats && + td->o.lat_percentiles != td2->o.lat_percentiles) { + log_err("fio: lat_percentiles in job: %s differs from group\n", + jobname); + return 1; + } + } end_for_each(); + + return 0; +} + +/* + * Treat an empty log file name the same as a one not given + */ +static const char *make_log_name(const char *logname, const char *jobname) +{ + if (logname && strcmp(logname, "")) + return logname; + + return jobname; +} + /* * Adds a job to the list of things todo. Sanitizes the various options * to make sure we don't have conflicts, and initializes various @@ -1410,7 +1500,7 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num, int recursed, int client_type) { unsigned int i; - char fname[PATH_MAX]; + char fname[PATH_MAX + 1]; int numjobs, file_alloced; struct thread_options *o = &td->o; char logname[PATH_MAX + 32]; @@ -1459,6 +1549,9 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num, if (fixup_options(td)) goto err; + if (!td->o.dedupe_global && init_dedupe_working_set_seeds(td, 0)) + goto err; + /* * Belongs to fixup_options, but o->name is not necessarily set as yet */ @@ -1485,19 +1578,20 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num, td->ts.clat_percentiles = o->clat_percentiles; td->ts.lat_percentiles = o->lat_percentiles; + td->ts.slat_percentiles = o->slat_percentiles; td->ts.percentile_precision = o->percentile_precision; memcpy(td->ts.percentile_list, o->percentile_list, sizeof(o->percentile_list)); td->ts.sig_figs = o->sig_figs; - for (i = 0; i < DDIR_RWDIR_CNT; i++) { - td->ts.clat_stat[i].min_val = ULONG_MAX; - td->ts.slat_stat[i].min_val = ULONG_MAX; - td->ts.lat_stat[i].min_val = ULONG_MAX; - td->ts.bw_stat[i].min_val = ULONG_MAX; - td->ts.iops_stat[i].min_val = ULONG_MAX; - } - td->ts.sync_stat.min_val = ULONG_MAX; - td->ddir_seq_nr = o->ddir_seq_nr; + init_thread_stat_min_vals(&td->ts); + + /* + * td->>ddir_seq_nr needs to be initialized to 1, NOT o->ddir_seq_nr, + * so that get_next_offset gets a new random offset the first time it + * is called, instead of keeping an initial offset of 0 for the first + * nr-1 calls + */ + td->ddir_seq_nr = 1; if ((o->stonewall || o->new_group) && prev_group_jobs) { prev_group_jobs = 0; @@ -1511,6 +1605,10 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num, td->groupid = groupid; prev_group_jobs++; + if (td->o.group_reporting && prev_group_jobs > 1 && + verify_per_group_options(td, jobname)) + goto err; + if (setup_rate(td)) goto err; @@ -1522,10 +1620,11 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num, .hist_coarseness = o->log_hist_coarseness, .log_type = IO_LOG_TYPE_LAT, .log_offset = o->log_offset, + .log_prio = o->log_prio, .log_gz = o->log_gz, .log_gz_store = o->log_gz_store, }; - const char *pre = o->lat_log_file ? o->lat_log_file : o->name; + const char *pre = make_log_name(o->lat_log_file, o->name); const char *suf; if (p.log_gz_store) @@ -1533,17 +1632,24 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num, else suf = "log"; - gen_log_name(logname, sizeof(logname), "lat", pre, - td->thread_number, suf, o->per_job_logs); - setup_log(&td->lat_log, &p, logname); + if (!o->disable_lat) { + gen_log_name(logname, sizeof(logname), "lat", pre, + td->thread_number, suf, o->per_job_logs); + setup_log(&td->lat_log, &p, logname); + } - gen_log_name(logname, sizeof(logname), "slat", pre, - td->thread_number, suf, o->per_job_logs); - setup_log(&td->slat_log, &p, logname); + if (!o->disable_slat) { + gen_log_name(logname, sizeof(logname), "slat", pre, + td->thread_number, suf, o->per_job_logs); + setup_log(&td->slat_log, &p, logname); + } + + if (!o->disable_clat) { + gen_log_name(logname, sizeof(logname), "clat", pre, + td->thread_number, suf, o->per_job_logs); + setup_log(&td->clat_log, &p, logname); + } - gen_log_name(logname, sizeof(logname), "clat", pre, - td->thread_number, suf, o->per_job_logs); - setup_log(&td->clat_log, &p, logname); } if (o->write_hist_log) { @@ -1554,10 +1660,11 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num, .hist_coarseness = o->log_hist_coarseness, .log_type = IO_LOG_TYPE_HIST, .log_offset = o->log_offset, + .log_prio = o->log_prio, .log_gz = o->log_gz, .log_gz_store = o->log_gz_store, }; - const char *pre = o->hist_log_file ? o->hist_log_file : o->name; + const char *pre = make_log_name(o->hist_log_file, o->name); const char *suf; #ifndef CONFIG_ZLIB @@ -1585,10 +1692,11 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num, .hist_coarseness = o->log_hist_coarseness, .log_type = IO_LOG_TYPE_BW, .log_offset = o->log_offset, + .log_prio = o->log_prio, .log_gz = o->log_gz, .log_gz_store = o->log_gz_store, }; - const char *pre = o->bw_log_file ? o->bw_log_file : o->name; + const char *pre = make_log_name(o->bw_log_file, o->name); const char *suf; if (fio_option_is_set(o, bw_avg_time)) @@ -1616,10 +1724,11 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num, .hist_coarseness = o->log_hist_coarseness, .log_type = IO_LOG_TYPE_IOPS, .log_offset = o->log_offset, + .log_prio = o->log_prio, .log_gz = o->log_gz, .log_gz_store = o->log_gz_store, }; - const char *pre = o->iops_log_file ? o->iops_log_file : o->name; + const char *pre = make_log_name(o->iops_log_file, o->name); const char *suf; if (fio_option_is_set(o, iops_avg_time)) @@ -1652,6 +1761,7 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num, char *c1, *c2, *c3, *c4; char *c5 = NULL, *c6 = NULL; int i2p = is_power_of_2(o->kb_base); + struct buf_output out; c1 = num2str(o->min_bs[DDIR_READ], o->sig_figs, 1, i2p, N2S_BYTE); c2 = num2str(o->max_bs[DDIR_READ], o->sig_figs, 1, i2p, N2S_BYTE); @@ -1663,19 +1773,22 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num, c6 = num2str(o->max_bs[DDIR_TRIM], o->sig_figs, 1, i2p, N2S_BYTE); } - log_info("%s: (g=%d): rw=%s, ", td->o.name, + buf_output_init(&out); + __log_buf(&out, "%s: (g=%d): rw=%s, ", td->o.name, td->groupid, ddir_str(o->td_ddir)); if (o->bs_is_seq_rand) - log_info("bs=(R) %s-%s, (W) %s-%s, bs_is_seq_rand, ", + __log_buf(&out, "bs=(R) %s-%s, (W) %s-%s, bs_is_seq_rand, ", c1, c2, c3, c4); else - log_info("bs=(R) %s-%s, (W) %s-%s, (T) %s-%s, ", + __log_buf(&out, "bs=(R) %s-%s, (W) %s-%s, (T) %s-%s, ", c1, c2, c3, c4, c5, c6); - log_info("ioengine=%s, iodepth=%u\n", + __log_buf(&out, "ioengine=%s, iodepth=%u\n", td->io_ops->name, o->iodepth); + log_info_buf(out.buf, out.buflen); + buf_output_free(&out); free(c1); free(c2); @@ -1691,6 +1804,14 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num, if (td_steadystate_init(td)) goto err; + if (o->merge_blktrace_file && !merge_blktrace_iologs(td)) + goto err; + + if (merge_blktrace_only) { + put_job(td); + return 0; + } + /* * recurse add identical jobs, clear numjobs and stonewall options * as they don't apply to sub-jobs @@ -1712,11 +1833,8 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num, if (file_alloced) { if (td_new->files) { struct fio_file *f; - for_each_file(td_new, f, i) { - if (f->file_name) - sfree(f->file_name); - sfree(f); - } + for_each_file(td_new, f, i) + fio_file_free(f); free(td_new->files); td_new->files = NULL; } @@ -1815,6 +1933,7 @@ static int __parse_jobs_ini(struct thread_data *td, int nested, char *name, char ***popts, int *aopts, int *nopts) { bool global = false; + bool stdin_occupied = false; char *string; FILE *f; char *p; @@ -1831,9 +1950,10 @@ static int __parse_jobs_ini(struct thread_data *td, if (is_buf) f = NULL; else { - if (!strcmp(file, "-")) + if (!strcmp(file, "-")) { f = stdin; - else + stdin_occupied = true; + } else f = fopen(file, "r"); if (!f) { @@ -1846,14 +1966,13 @@ static int __parse_jobs_ini(struct thread_data *td, } } - string = malloc(4096); + string = malloc(OPT_LEN_MAX); /* * it's really 256 + small bit, 280 should suffice */ if (!nested) { - name = malloc(280); - memset(name, 0, 280); + name = calloc(1, 280); } opts = NULL; @@ -1879,7 +1998,7 @@ static int __parse_jobs_ini(struct thread_data *td, if (is_buf) p = strsep(&file, "\n"); else - p = fgets(string, 4096, f); + p = fgets(string, OPT_LEN_MAX, f); if (!p) break; } @@ -1948,7 +2067,7 @@ static int __parse_jobs_ini(struct thread_data *td, if (is_buf) p = strsep(&file, "\n"); else - p = fgets(string, 4096, f); + p = fgets(string, OPT_LEN_MAX, f); if (!p) break; dprint(FD_PARSE, "%s", p); @@ -1988,19 +2107,12 @@ static int __parse_jobs_ini(struct thread_data *td, */ if (access(filename, F_OK) && (ts = strrchr(file, '/'))) { - int len = ts - file + - strlen(filename) + 2; - - if (!(full_fn = calloc(1, len))) { + if (asprintf(&full_fn, "%.*s%s", + (int)(ts - file + 1), file, + filename) < 0) { ret = ENOMEM; break; } - - strncpy(full_fn, - file, (ts - file) + 1); - strncpy(full_fn + (ts - file) + 1, - filename, strlen(filename)); - full_fn[len - 1] = 0; filename = full_fn; } @@ -2042,6 +2154,20 @@ static int __parse_jobs_ini(struct thread_data *td, } ret = fio_options_parse(td, opts, num_opts); + + if (!ret && td->o.read_iolog_file != NULL) { + char *fname = get_name_by_idx(td->o.read_iolog_file, + td->subjob_number); + if (!strcmp(fname, "-")) { + if (stdin_occupied) { + log_err("fio: only one user (read_iolog_file/job " + "file) of stdin is permitted at once but " + "more than one was found.\n"); + ret = 1; + } + stdin_occupied = true; + } + } if (!ret) { if (dump_cmdline) dump_opt_list(td); @@ -2066,6 +2192,10 @@ static int __parse_jobs_ini(struct thread_data *td, i++; } + free(job_sections); + job_sections = NULL; + nr_job_sections = 0; + free(opts); out: free(string); @@ -2140,12 +2270,13 @@ static void usage(const char *name) printf(" --debug=options\tEnable debug logging. May be one/more of:\n"); show_debug_categories(); printf(" --parse-only\t\tParse options only, don't start any IO\n"); + printf(" --merge-blktrace-only\tMerge blktraces only, don't start any IO\n"); printf(" --output\t\tWrite output to file\n"); printf(" --bandwidth-log\tGenerate aggregate bandwidth logs\n"); printf(" --minimal\t\tMinimal (terse) output\n"); 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"); + " (default 3, or 2 or 4 or 5)\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"); @@ -2438,7 +2569,7 @@ int parse_cmd_line(int argc, char *argv[], int client_type) char *ostr = cmd_optstr; char *pid_file = NULL; void *cur_client = NULL; - int backend = 0; + bool backend = false; /* * Reset optind handling, since we may call this multiple times @@ -2464,7 +2595,7 @@ int parse_cmd_line(int argc, char *argv[], int client_type) exit_val = 1; break; case 'b': - write_bw_log = 1; + write_bw_log = true; break; case 'o': { FILE *tmp; @@ -2513,13 +2644,13 @@ int parse_cmd_line(int argc, char *argv[], int client_type) case 'i': did_arg = true; if (!cur_client) { - fio_show_ioengine_help(optarg); + exit_val = fio_show_ioengine_help(optarg); do_exit++; } break; case 's': did_arg = true; - dump_cmdline = 1; + dump_cmdline = true; break; case 'r': read_only = 1; @@ -2585,7 +2716,7 @@ int parse_cmd_line(int argc, char *argv[], int client_type) break; case 'P': did_arg = true; - parse_only = 1; + parse_only = true; break; case 'x': { size_t new_size; @@ -2686,18 +2817,22 @@ int parse_cmd_line(int argc, char *argv[], int client_type) break; ret = fio_cmd_ioengine_option_parse(td, opt, val); + + if (ret) { + if (td) { + put_job(td); + td = NULL; + } + do_exit++; + exit_val = 1; + } break; } case 'w': warnings_fatal = 1; break; case 'j': - max_jobs = atoi(optarg); - if (!max_jobs || max_jobs > REAL_MAX_JOBS) { - log_err("fio: invalid max jobs: %d\n", max_jobs); - do_exit++; - exit_val = 1; - } + /* we don't track/need this anymore, ignore it */ break; case 'S': did_arg = true; @@ -2710,14 +2845,20 @@ int parse_cmd_line(int argc, char *argv[], int client_type) } if (optarg) fio_server_set_arg(optarg); - is_backend = 1; - backend = 1; + is_backend = true; + backend = true; #else log_err("fio: client/server requires SHM support\n"); do_exit++; exit_val = 1; #endif break; +#ifdef WIN32 + case 'N': + did_arg = true; + fio_server_internal_set(optarg); + break; +#endif case 'D': if (pid_file) free(pid_file); @@ -2856,10 +2997,16 @@ int parse_cmd_line(int argc, char *argv[], int client_type) } trigger_timeout /= 1000000; break; + + case 'A': + did_arg = true; + merge_blktrace_only = true; + break; case '?': log_err("%s: unrecognized option '%s'\n", argv[0], argv[optind - 1]); show_closest_option(argv[optind - 1]); + fio_fallthrough; default: do_exit++; exit_val = 1;