xref: /spdk/examples/bdev/bdevperf/bdevperf.c (revision a1f4f11b41e4fa9fe9242fa8467a3eee92c20a03)
129784f35SKrzysztof Karas /*   SPDX-License-Identifier: BSD-3-Clause
229784f35SKrzysztof Karas  *   Copyright (C) 2016 Intel Corporation.
39ef9d4bdSShuhei Matsumoto  *   Copyright (c) 2022-2023 NVIDIA CORPORATION & AFFILIATES.
429784f35SKrzysztof Karas  *   All rights reserved.
529784f35SKrzysztof Karas  */
629784f35SKrzysztof Karas 
729784f35SKrzysztof Karas #include "spdk/stdinc.h"
829784f35SKrzysztof Karas 
929784f35SKrzysztof Karas #include "spdk/bdev.h"
1029784f35SKrzysztof Karas #include "spdk/accel.h"
1129784f35SKrzysztof Karas #include "spdk/endian.h"
1229784f35SKrzysztof Karas #include "spdk/env.h"
1329784f35SKrzysztof Karas #include "spdk/event.h"
1429784f35SKrzysztof Karas #include "spdk/log.h"
1529784f35SKrzysztof Karas #include "spdk/util.h"
1629784f35SKrzysztof Karas #include "spdk/thread.h"
1729784f35SKrzysztof Karas #include "spdk/string.h"
1829784f35SKrzysztof Karas #include "spdk/rpc.h"
1929784f35SKrzysztof Karas #include "spdk/bit_array.h"
2029784f35SKrzysztof Karas #include "spdk/conf.h"
2129784f35SKrzysztof Karas #include "spdk/zipf.h"
22723dd06eSRichael Zhuang #include "spdk/histogram_data.h"
2329784f35SKrzysztof Karas 
2429784f35SKrzysztof Karas #define BDEVPERF_CONFIG_MAX_FILENAME 1024
2529784f35SKrzysztof Karas #define BDEVPERF_CONFIG_UNDEFINED -1
2629784f35SKrzysztof Karas #define BDEVPERF_CONFIG_ERROR -2
2729784f35SKrzysztof Karas 
2829784f35SKrzysztof Karas struct bdevperf_task {
2929784f35SKrzysztof Karas 	struct iovec			iov;
3029784f35SKrzysztof Karas 	struct bdevperf_job		*job;
3129784f35SKrzysztof Karas 	struct spdk_bdev_io		*bdev_io;
3229784f35SKrzysztof Karas 	void				*buf;
3329784f35SKrzysztof Karas 	void				*md_buf;
3429784f35SKrzysztof Karas 	uint64_t			offset_blocks;
3529784f35SKrzysztof Karas 	struct bdevperf_task		*task_to_abort;
3629784f35SKrzysztof Karas 	enum spdk_bdev_io_type		io_type;
3729784f35SKrzysztof Karas 	TAILQ_ENTRY(bdevperf_task)	link;
3829784f35SKrzysztof Karas 	struct spdk_bdev_io_wait_entry	bdev_io_wait;
3929784f35SKrzysztof Karas };
4029784f35SKrzysztof Karas 
4129784f35SKrzysztof Karas static const char *g_workload_type = NULL;
4229784f35SKrzysztof Karas static int g_io_size = 0;
4329784f35SKrzysztof Karas /* initialize to invalid value so we can detect if user overrides it. */
4429784f35SKrzysztof Karas static int g_rw_percentage = -1;
4529784f35SKrzysztof Karas static bool g_verify = false;
4629784f35SKrzysztof Karas static bool g_reset = false;
4729784f35SKrzysztof Karas static bool g_continue_on_failure = false;
4829784f35SKrzysztof Karas static bool g_abort = false;
4929784f35SKrzysztof Karas static bool g_error_to_exit = false;
5029784f35SKrzysztof Karas static int g_queue_depth = 0;
5129784f35SKrzysztof Karas static uint64_t g_time_in_usec;
5229784f35SKrzysztof Karas static int g_show_performance_real_time = 0;
53045c781dSRichael Zhuang static uint64_t g_show_performance_period_in_usec = SPDK_SEC_TO_USEC;
5429784f35SKrzysztof Karas static uint64_t g_show_performance_period_num = 0;
5529784f35SKrzysztof Karas static uint64_t g_show_performance_ema_period = 0;
5629784f35SKrzysztof Karas static int g_run_rc = 0;
5729784f35SKrzysztof Karas static bool g_shutdown = false;
5829784f35SKrzysztof Karas static uint64_t g_start_tsc;
5929784f35SKrzysztof Karas static uint64_t g_shutdown_tsc;
6029784f35SKrzysztof Karas static bool g_zcopy = false;
6129784f35SKrzysztof Karas static struct spdk_thread *g_main_thread;
6229784f35SKrzysztof Karas static int g_time_in_sec = 0;
6329784f35SKrzysztof Karas static bool g_mix_specified = false;
6429784f35SKrzysztof Karas static const char *g_job_bdev_name;
6529784f35SKrzysztof Karas static bool g_wait_for_tests = false;
6629784f35SKrzysztof Karas static struct spdk_jsonrpc_request *g_request = NULL;
6729784f35SKrzysztof Karas static bool g_multithread_mode = false;
6829784f35SKrzysztof Karas static int g_timeout_in_sec;
6929784f35SKrzysztof Karas static struct spdk_conf *g_bdevperf_conf = NULL;
7029784f35SKrzysztof Karas static const char *g_bdevperf_conf_file = NULL;
7129784f35SKrzysztof Karas static double g_zipf_theta;
72d254a3b9SSlawomir Ptak static bool g_random_map = false;
7329784f35SKrzysztof Karas 
7429784f35SKrzysztof Karas static struct spdk_cpuset g_all_cpuset;
7529784f35SKrzysztof Karas static struct spdk_poller *g_perf_timer = NULL;
7629784f35SKrzysztof Karas 
7729784f35SKrzysztof Karas static void bdevperf_submit_single(struct bdevperf_job *job, struct bdevperf_task *task);
7829784f35SKrzysztof Karas static void rpc_perform_tests_cb(void);
7929784f35SKrzysztof Karas 
80723dd06eSRichael Zhuang static uint32_t g_bdev_count = 0;
81723dd06eSRichael Zhuang static uint32_t g_latency_display_level;
82723dd06eSRichael Zhuang 
838e373044SShuhei Matsumoto static bool g_one_thread_per_lcore = false;
848e373044SShuhei Matsumoto 
85723dd06eSRichael Zhuang static const double g_latency_cutoffs[] = {
86723dd06eSRichael Zhuang 	0.01,
87723dd06eSRichael Zhuang 	0.10,
88723dd06eSRichael Zhuang 	0.25,
89723dd06eSRichael Zhuang 	0.50,
90723dd06eSRichael Zhuang 	0.75,
91723dd06eSRichael Zhuang 	0.90,
92723dd06eSRichael Zhuang 	0.95,
93723dd06eSRichael Zhuang 	0.98,
94723dd06eSRichael Zhuang 	0.99,
95723dd06eSRichael Zhuang 	0.995,
96723dd06eSRichael Zhuang 	0.999,
97723dd06eSRichael Zhuang 	0.9999,
98723dd06eSRichael Zhuang 	0.99999,
99723dd06eSRichael Zhuang 	0.999999,
100723dd06eSRichael Zhuang 	0.9999999,
101723dd06eSRichael Zhuang 	-1,
102723dd06eSRichael Zhuang };
103723dd06eSRichael Zhuang 
104723dd06eSRichael Zhuang struct latency_info {
105723dd06eSRichael Zhuang 	uint64_t	min;
106723dd06eSRichael Zhuang 	uint64_t	max;
107723dd06eSRichael Zhuang 	uint64_t	total;
108723dd06eSRichael Zhuang };
109723dd06eSRichael Zhuang 
11029784f35SKrzysztof Karas struct bdevperf_job {
11129784f35SKrzysztof Karas 	char				*name;
11229784f35SKrzysztof Karas 	struct spdk_bdev		*bdev;
11329784f35SKrzysztof Karas 	struct spdk_bdev_desc		*bdev_desc;
11429784f35SKrzysztof Karas 	struct spdk_io_channel		*ch;
11529784f35SKrzysztof Karas 	TAILQ_ENTRY(bdevperf_job)	link;
11629784f35SKrzysztof Karas 	struct spdk_thread		*thread;
11729784f35SKrzysztof Karas 
11829784f35SKrzysztof Karas 	const char			*workload_type;
11929784f35SKrzysztof Karas 	int				io_size;
12029784f35SKrzysztof Karas 	int				rw_percentage;
12129784f35SKrzysztof Karas 	bool				is_random;
12229784f35SKrzysztof Karas 	bool				verify;
12329784f35SKrzysztof Karas 	bool				reset;
12429784f35SKrzysztof Karas 	bool				continue_on_failure;
12529784f35SKrzysztof Karas 	bool				unmap;
12629784f35SKrzysztof Karas 	bool				write_zeroes;
12729784f35SKrzysztof Karas 	bool				flush;
12829784f35SKrzysztof Karas 	bool				abort;
12929784f35SKrzysztof Karas 	int				queue_depth;
13029784f35SKrzysztof Karas 	unsigned int			seed;
13129784f35SKrzysztof Karas 
13229784f35SKrzysztof Karas 	uint64_t			io_completed;
13329784f35SKrzysztof Karas 	uint64_t			io_failed;
13429784f35SKrzysztof Karas 	uint64_t			io_timeout;
13529784f35SKrzysztof Karas 	uint64_t			prev_io_completed;
13629784f35SKrzysztof Karas 	double				ema_io_per_second;
13729784f35SKrzysztof Karas 	int				current_queue_depth;
13829784f35SKrzysztof Karas 	uint64_t			size_in_ios;
13929784f35SKrzysztof Karas 	uint64_t			ios_base;
14029784f35SKrzysztof Karas 	uint64_t			offset_in_ios;
14129784f35SKrzysztof Karas 	uint64_t			io_size_blocks;
14229784f35SKrzysztof Karas 	uint64_t			buf_size;
14329784f35SKrzysztof Karas 	uint32_t			dif_check_flags;
14429784f35SKrzysztof Karas 	bool				is_draining;
14529784f35SKrzysztof Karas 	struct spdk_poller		*run_timer;
14629784f35SKrzysztof Karas 	struct spdk_poller		*reset_timer;
14729784f35SKrzysztof Karas 	struct spdk_bit_array		*outstanding;
14829784f35SKrzysztof Karas 	struct spdk_zipf		*zipf;
14929784f35SKrzysztof Karas 	TAILQ_HEAD(, bdevperf_task)	task_list;
15029784f35SKrzysztof Karas 	uint64_t			run_time_in_usec;
151723dd06eSRichael Zhuang 
152723dd06eSRichael Zhuang 	/* keep channel's histogram data before being destroyed */
153723dd06eSRichael Zhuang 	struct spdk_histogram_data	*histogram;
154d254a3b9SSlawomir Ptak 	struct spdk_bit_array		*random_map;
15529784f35SKrzysztof Karas };
15629784f35SKrzysztof Karas 
15729784f35SKrzysztof Karas struct spdk_bdevperf {
15829784f35SKrzysztof Karas 	TAILQ_HEAD(, bdevperf_job)	jobs;
15929784f35SKrzysztof Karas 	uint32_t			running_jobs;
16029784f35SKrzysztof Karas };
16129784f35SKrzysztof Karas 
16229784f35SKrzysztof Karas static struct spdk_bdevperf g_bdevperf = {
16329784f35SKrzysztof Karas 	.jobs = TAILQ_HEAD_INITIALIZER(g_bdevperf.jobs),
16429784f35SKrzysztof Karas 	.running_jobs = 0,
16529784f35SKrzysztof Karas };
16629784f35SKrzysztof Karas 
16729784f35SKrzysztof Karas enum job_config_rw {
16829784f35SKrzysztof Karas 	JOB_CONFIG_RW_READ = 0,
16929784f35SKrzysztof Karas 	JOB_CONFIG_RW_WRITE,
17029784f35SKrzysztof Karas 	JOB_CONFIG_RW_RANDREAD,
17129784f35SKrzysztof Karas 	JOB_CONFIG_RW_RANDWRITE,
17229784f35SKrzysztof Karas 	JOB_CONFIG_RW_RW,
17329784f35SKrzysztof Karas 	JOB_CONFIG_RW_RANDRW,
17429784f35SKrzysztof Karas 	JOB_CONFIG_RW_VERIFY,
17529784f35SKrzysztof Karas 	JOB_CONFIG_RW_RESET,
17629784f35SKrzysztof Karas 	JOB_CONFIG_RW_UNMAP,
17729784f35SKrzysztof Karas 	JOB_CONFIG_RW_FLUSH,
17829784f35SKrzysztof Karas 	JOB_CONFIG_RW_WRITE_ZEROES,
17929784f35SKrzysztof Karas };
18029784f35SKrzysztof Karas 
18129784f35SKrzysztof Karas /* Storing values from a section of job config file */
18229784f35SKrzysztof Karas struct job_config {
18329784f35SKrzysztof Karas 	const char			*name;
18429784f35SKrzysztof Karas 	const char			*filename;
18529784f35SKrzysztof Karas 	struct spdk_cpuset		cpumask;
18629784f35SKrzysztof Karas 	int				bs;
18729784f35SKrzysztof Karas 	int				iodepth;
18829784f35SKrzysztof Karas 	int				rwmixread;
1898e373044SShuhei Matsumoto 	uint32_t			lcore;
19029784f35SKrzysztof Karas 	int64_t				offset;
19129784f35SKrzysztof Karas 	uint64_t			length;
19229784f35SKrzysztof Karas 	enum job_config_rw		rw;
19329784f35SKrzysztof Karas 	TAILQ_ENTRY(job_config)	link;
19429784f35SKrzysztof Karas };
19529784f35SKrzysztof Karas 
19629784f35SKrzysztof Karas TAILQ_HEAD(, job_config) job_config_list
19729784f35SKrzysztof Karas 	= TAILQ_HEAD_INITIALIZER(job_config_list);
19829784f35SKrzysztof Karas 
19929784f35SKrzysztof Karas static bool g_performance_dump_active = false;
20029784f35SKrzysztof Karas 
20129784f35SKrzysztof Karas struct bdevperf_aggregate_stats {
20229784f35SKrzysztof Karas 	struct bdevperf_job		*current_job;
20329784f35SKrzysztof Karas 	uint64_t			io_time_in_usec;
20429784f35SKrzysztof Karas 	uint64_t			ema_period;
20529784f35SKrzysztof Karas 	double				total_io_per_second;
20629784f35SKrzysztof Karas 	double				total_mb_per_second;
20729784f35SKrzysztof Karas 	double				total_failed_per_second;
20829784f35SKrzysztof Karas 	double				total_timeout_per_second;
209723dd06eSRichael Zhuang 	double				min_latency;
210723dd06eSRichael Zhuang 	double				max_latency;
211723dd06eSRichael Zhuang 	uint64_t			total_io_completed;
212723dd06eSRichael Zhuang 	uint64_t			total_tsc;
21329784f35SKrzysztof Karas };
21429784f35SKrzysztof Karas 
215723dd06eSRichael Zhuang static struct bdevperf_aggregate_stats g_stats = {.min_latency = (double)UINT64_MAX};
21629784f35SKrzysztof Karas 
2178e373044SShuhei Matsumoto struct lcore_thread {
2188e373044SShuhei Matsumoto 	struct spdk_thread		*thread;
2198e373044SShuhei Matsumoto 	uint32_t			lcore;
2208e373044SShuhei Matsumoto 	TAILQ_ENTRY(lcore_thread)	link;
2218e373044SShuhei Matsumoto };
2228e373044SShuhei Matsumoto 
2238e373044SShuhei Matsumoto TAILQ_HEAD(, lcore_thread) g_lcore_thread_list
2248e373044SShuhei Matsumoto 	= TAILQ_HEAD_INITIALIZER(g_lcore_thread_list);
2258e373044SShuhei Matsumoto 
22629784f35SKrzysztof Karas /*
22729784f35SKrzysztof Karas  * Cumulative Moving Average (CMA): average of all data up to current
22829784f35SKrzysztof Karas  * Exponential Moving Average (EMA): weighted mean of the previous n data and more weight is given to recent
22929784f35SKrzysztof Karas  * Simple Moving Average (SMA): unweighted mean of the previous n data
23029784f35SKrzysztof Karas  *
23129784f35SKrzysztof Karas  * Bdevperf supports CMA and EMA.
23229784f35SKrzysztof Karas  */
23329784f35SKrzysztof Karas static double
23429784f35SKrzysztof Karas get_cma_io_per_second(struct bdevperf_job *job, uint64_t io_time_in_usec)
23529784f35SKrzysztof Karas {
236045c781dSRichael Zhuang 	return (double)job->io_completed * SPDK_SEC_TO_USEC / io_time_in_usec;
23729784f35SKrzysztof Karas }
23829784f35SKrzysztof Karas 
23929784f35SKrzysztof Karas static double
24029784f35SKrzysztof Karas get_ema_io_per_second(struct bdevperf_job *job, uint64_t ema_period)
24129784f35SKrzysztof Karas {
24229784f35SKrzysztof Karas 	double io_completed, io_per_second;
24329784f35SKrzysztof Karas 
24429784f35SKrzysztof Karas 	io_completed = job->io_completed;
245045c781dSRichael Zhuang 	io_per_second = (double)(io_completed - job->prev_io_completed) * SPDK_SEC_TO_USEC
24629784f35SKrzysztof Karas 			/ g_show_performance_period_in_usec;
24729784f35SKrzysztof Karas 	job->prev_io_completed = io_completed;
24829784f35SKrzysztof Karas 
24929784f35SKrzysztof Karas 	job->ema_io_per_second += (io_per_second - job->ema_io_per_second) * 2
25029784f35SKrzysztof Karas 				  / (ema_period + 1);
25129784f35SKrzysztof Karas 	return job->ema_io_per_second;
25229784f35SKrzysztof Karas }
25329784f35SKrzysztof Karas 
25429784f35SKrzysztof Karas static void
255723dd06eSRichael Zhuang get_avg_latency(void *ctx, uint64_t start, uint64_t end, uint64_t count,
256723dd06eSRichael Zhuang 		uint64_t total, uint64_t so_far)
257723dd06eSRichael Zhuang {
258723dd06eSRichael Zhuang 	struct latency_info *latency_info = ctx;
259723dd06eSRichael Zhuang 
260723dd06eSRichael Zhuang 	if (count == 0) {
261723dd06eSRichael Zhuang 		return;
262723dd06eSRichael Zhuang 	}
263723dd06eSRichael Zhuang 
264723dd06eSRichael Zhuang 	latency_info->total += (start + end) / 2 * count;
265723dd06eSRichael Zhuang 
266723dd06eSRichael Zhuang 	if (so_far == count) {
267723dd06eSRichael Zhuang 		latency_info->min = start;
268723dd06eSRichael Zhuang 	}
269723dd06eSRichael Zhuang 
270723dd06eSRichael Zhuang 	if (so_far == total) {
271723dd06eSRichael Zhuang 		latency_info->max = end;
272723dd06eSRichael Zhuang 	}
273723dd06eSRichael Zhuang }
274723dd06eSRichael Zhuang 
275723dd06eSRichael Zhuang static void
27629784f35SKrzysztof Karas performance_dump_job(struct bdevperf_aggregate_stats *stats, struct bdevperf_job *job)
27729784f35SKrzysztof Karas {
27829784f35SKrzysztof Karas 	double io_per_second, mb_per_second, failed_per_second, timeout_per_second;
279723dd06eSRichael Zhuang 	double average_latency = 0.0, min_latency, max_latency;
28029784f35SKrzysztof Karas 	uint64_t time_in_usec;
281723dd06eSRichael Zhuang 	uint64_t tsc_rate;
282723dd06eSRichael Zhuang 	uint64_t total_io;
283723dd06eSRichael Zhuang 	struct latency_info latency_info = {};
28429784f35SKrzysztof Karas 
28572da547aSShuhei Matsumoto 	printf("\r Job: %s (Core Mask 0x%s)\n", job->name,
28629784f35SKrzysztof Karas 	       spdk_cpuset_fmt(spdk_thread_get_cpumask(job->thread)));
28729784f35SKrzysztof Karas 
28829784f35SKrzysztof Karas 	if (job->io_failed > 0 && !job->reset && !job->continue_on_failure) {
28929784f35SKrzysztof Karas 		printf("\r Job: %s ended in about %.2f seconds with error\n",
29072da547aSShuhei Matsumoto 		       job->name, (double)job->run_time_in_usec / SPDK_SEC_TO_USEC);
29129784f35SKrzysztof Karas 	}
29229784f35SKrzysztof Karas 	if (job->verify) {
29329784f35SKrzysztof Karas 		printf("\t Verification LBA range: start 0x%" PRIx64 " length 0x%" PRIx64 "\n",
29429784f35SKrzysztof Karas 		       job->ios_base, job->size_in_ios);
29529784f35SKrzysztof Karas 	}
29629784f35SKrzysztof Karas 
29729784f35SKrzysztof Karas 	if (g_performance_dump_active == true) {
29829784f35SKrzysztof Karas 		/* Use job's actual run time as Job has ended */
29929784f35SKrzysztof Karas 		if (job->io_failed > 0 && !job->continue_on_failure) {
30029784f35SKrzysztof Karas 			time_in_usec = job->run_time_in_usec;
30129784f35SKrzysztof Karas 		} else {
30229784f35SKrzysztof Karas 			time_in_usec = stats->io_time_in_usec;
30329784f35SKrzysztof Karas 		}
30429784f35SKrzysztof Karas 	} else {
30529784f35SKrzysztof Karas 		time_in_usec = job->run_time_in_usec;
30629784f35SKrzysztof Karas 	}
30729784f35SKrzysztof Karas 
30829784f35SKrzysztof Karas 	if (stats->ema_period == 0) {
30929784f35SKrzysztof Karas 		io_per_second = get_cma_io_per_second(job, time_in_usec);
31029784f35SKrzysztof Karas 	} else {
31129784f35SKrzysztof Karas 		io_per_second = get_ema_io_per_second(job, stats->ema_period);
31229784f35SKrzysztof Karas 	}
313723dd06eSRichael Zhuang 
314723dd06eSRichael Zhuang 	tsc_rate = spdk_get_ticks_hz();
31529784f35SKrzysztof Karas 	mb_per_second = io_per_second * job->io_size / (1024 * 1024);
31629784f35SKrzysztof Karas 
317723dd06eSRichael Zhuang 	spdk_histogram_data_iterate(job->histogram, get_avg_latency, &latency_info);
318723dd06eSRichael Zhuang 
319723dd06eSRichael Zhuang 	total_io = job->io_completed + job->io_failed;
320723dd06eSRichael Zhuang 	if (total_io != 0) {
321045c781dSRichael Zhuang 		average_latency = (double)latency_info.total / total_io * SPDK_SEC_TO_USEC / tsc_rate;
322723dd06eSRichael Zhuang 	}
323045c781dSRichael Zhuang 	min_latency = (double)latency_info.min * SPDK_SEC_TO_USEC / tsc_rate;
324045c781dSRichael Zhuang 	max_latency = (double)latency_info.max * SPDK_SEC_TO_USEC / tsc_rate;
325723dd06eSRichael Zhuang 
326045c781dSRichael Zhuang 	failed_per_second = (double)job->io_failed * SPDK_SEC_TO_USEC / time_in_usec;
327045c781dSRichael Zhuang 	timeout_per_second = (double)job->io_timeout * SPDK_SEC_TO_USEC / time_in_usec;
32829784f35SKrzysztof Karas 
32929784f35SKrzysztof Karas 	printf("\t %-20s: %10.2f %10.2f %10.2f",
330045c781dSRichael Zhuang 	       job->name, (float)time_in_usec / SPDK_SEC_TO_USEC, io_per_second, mb_per_second);
331723dd06eSRichael Zhuang 	printf(" %10.2f %8.2f",
33229784f35SKrzysztof Karas 	       failed_per_second, timeout_per_second);
333723dd06eSRichael Zhuang 	printf(" %10.2f %10.2f %10.2f\n",
334723dd06eSRichael Zhuang 	       average_latency, min_latency, max_latency);
33529784f35SKrzysztof Karas 
33629784f35SKrzysztof Karas 	stats->total_io_per_second += io_per_second;
33729784f35SKrzysztof Karas 	stats->total_mb_per_second += mb_per_second;
33829784f35SKrzysztof Karas 	stats->total_failed_per_second += failed_per_second;
33929784f35SKrzysztof Karas 	stats->total_timeout_per_second += timeout_per_second;
340723dd06eSRichael Zhuang 	stats->total_io_completed += job->io_completed + job->io_failed;
341723dd06eSRichael Zhuang 	stats->total_tsc += latency_info.total;
342723dd06eSRichael Zhuang 	if (min_latency < stats->min_latency) {
343723dd06eSRichael Zhuang 		stats->min_latency = min_latency;
344723dd06eSRichael Zhuang 	}
345723dd06eSRichael Zhuang 	if (max_latency > stats->max_latency) {
346723dd06eSRichael Zhuang 		stats->max_latency = max_latency;
347723dd06eSRichael Zhuang 	}
34829784f35SKrzysztof Karas }
34929784f35SKrzysztof Karas 
35029784f35SKrzysztof Karas static void
35129784f35SKrzysztof Karas generate_data(void *buf, int buf_len, int block_size, void *md_buf, int md_size,
35229784f35SKrzysztof Karas 	      int num_blocks)
35329784f35SKrzysztof Karas {
35429784f35SKrzysztof Karas 	int offset_blocks = 0, md_offset, data_block_size, inner_offset;
35529784f35SKrzysztof Karas 
35629784f35SKrzysztof Karas 	if (buf_len < num_blocks * block_size) {
35729784f35SKrzysztof Karas 		return;
35829784f35SKrzysztof Karas 	}
35929784f35SKrzysztof Karas 
36029784f35SKrzysztof Karas 	if (md_buf == NULL) {
36129784f35SKrzysztof Karas 		data_block_size = block_size - md_size;
36229784f35SKrzysztof Karas 		md_buf = (char *)buf + data_block_size;
36329784f35SKrzysztof Karas 		md_offset = block_size;
36429784f35SKrzysztof Karas 	} else {
36529784f35SKrzysztof Karas 		data_block_size = block_size;
36629784f35SKrzysztof Karas 		md_offset = md_size;
36729784f35SKrzysztof Karas 	}
36829784f35SKrzysztof Karas 
36929784f35SKrzysztof Karas 	while (offset_blocks < num_blocks) {
37029784f35SKrzysztof Karas 		inner_offset = 0;
37129784f35SKrzysztof Karas 		while (inner_offset < data_block_size) {
37229784f35SKrzysztof Karas 			*(uint32_t *)buf = offset_blocks + inner_offset;
37329784f35SKrzysztof Karas 			inner_offset += sizeof(uint32_t);
37429784f35SKrzysztof Karas 			buf += sizeof(uint32_t);
37529784f35SKrzysztof Karas 		}
37629784f35SKrzysztof Karas 		memset(md_buf, offset_blocks, md_size);
37729784f35SKrzysztof Karas 		md_buf += md_offset;
37829784f35SKrzysztof Karas 		offset_blocks++;
37929784f35SKrzysztof Karas 	}
38029784f35SKrzysztof Karas }
38129784f35SKrzysztof Karas 
38229784f35SKrzysztof Karas static bool
38329784f35SKrzysztof Karas copy_data(void *wr_buf, int wr_buf_len, void *rd_buf, int rd_buf_len, int block_size,
38429784f35SKrzysztof Karas 	  void *wr_md_buf, void *rd_md_buf, int md_size, int num_blocks)
38529784f35SKrzysztof Karas {
38629784f35SKrzysztof Karas 	if (wr_buf_len < num_blocks * block_size || rd_buf_len < num_blocks * block_size) {
38729784f35SKrzysztof Karas 		return false;
38829784f35SKrzysztof Karas 	}
38929784f35SKrzysztof Karas 
39029784f35SKrzysztof Karas 	assert((wr_md_buf != NULL) == (rd_md_buf != NULL));
39129784f35SKrzysztof Karas 
39229784f35SKrzysztof Karas 	memcpy(wr_buf, rd_buf, block_size * num_blocks);
39329784f35SKrzysztof Karas 
39429784f35SKrzysztof Karas 	if (wr_md_buf != NULL) {
39529784f35SKrzysztof Karas 		memcpy(wr_md_buf, rd_md_buf, md_size * num_blocks);
39629784f35SKrzysztof Karas 	}
39729784f35SKrzysztof Karas 
39829784f35SKrzysztof Karas 	return true;
39929784f35SKrzysztof Karas }
40029784f35SKrzysztof Karas 
40129784f35SKrzysztof Karas static bool
40229784f35SKrzysztof Karas verify_data(void *wr_buf, int wr_buf_len, void *rd_buf, int rd_buf_len, int block_size,
40329784f35SKrzysztof Karas 	    void *wr_md_buf, void *rd_md_buf, int md_size, int num_blocks, bool md_check)
40429784f35SKrzysztof Karas {
40529784f35SKrzysztof Karas 	int offset_blocks = 0, md_offset, data_block_size;
40629784f35SKrzysztof Karas 
40729784f35SKrzysztof Karas 	if (wr_buf_len < num_blocks * block_size || rd_buf_len < num_blocks * block_size) {
40829784f35SKrzysztof Karas 		return false;
40929784f35SKrzysztof Karas 	}
41029784f35SKrzysztof Karas 
41129784f35SKrzysztof Karas 	assert((wr_md_buf != NULL) == (rd_md_buf != NULL));
41229784f35SKrzysztof Karas 
41329784f35SKrzysztof Karas 	if (wr_md_buf == NULL) {
41429784f35SKrzysztof Karas 		data_block_size = block_size - md_size;
41529784f35SKrzysztof Karas 		wr_md_buf = (char *)wr_buf + data_block_size;
41629784f35SKrzysztof Karas 		rd_md_buf = (char *)rd_buf + data_block_size;
41729784f35SKrzysztof Karas 		md_offset = block_size;
41829784f35SKrzysztof Karas 	} else {
41929784f35SKrzysztof Karas 		data_block_size = block_size;
42029784f35SKrzysztof Karas 		md_offset = md_size;
42129784f35SKrzysztof Karas 	}
42229784f35SKrzysztof Karas 
42329784f35SKrzysztof Karas 	while (offset_blocks < num_blocks) {
42429784f35SKrzysztof Karas 		if (memcmp(wr_buf, rd_buf, data_block_size) != 0) {
42529784f35SKrzysztof Karas 			return false;
42629784f35SKrzysztof Karas 		}
42729784f35SKrzysztof Karas 
42829784f35SKrzysztof Karas 		wr_buf += block_size;
42929784f35SKrzysztof Karas 		rd_buf += block_size;
43029784f35SKrzysztof Karas 
43129784f35SKrzysztof Karas 		if (md_check) {
43229784f35SKrzysztof Karas 			if (memcmp(wr_md_buf, rd_md_buf, md_size) != 0) {
43329784f35SKrzysztof Karas 				return false;
43429784f35SKrzysztof Karas 			}
43529784f35SKrzysztof Karas 
43629784f35SKrzysztof Karas 			wr_md_buf += md_offset;
43729784f35SKrzysztof Karas 			rd_md_buf += md_offset;
43829784f35SKrzysztof Karas 		}
43929784f35SKrzysztof Karas 
44029784f35SKrzysztof Karas 		offset_blocks++;
44129784f35SKrzysztof Karas 	}
44229784f35SKrzysztof Karas 
44329784f35SKrzysztof Karas 	return true;
44429784f35SKrzysztof Karas }
44529784f35SKrzysztof Karas 
44629784f35SKrzysztof Karas static void
44729784f35SKrzysztof Karas free_job_config(void)
44829784f35SKrzysztof Karas {
44929784f35SKrzysztof Karas 	struct job_config *config, *tmp;
45029784f35SKrzysztof Karas 
45129784f35SKrzysztof Karas 	spdk_conf_free(g_bdevperf_conf);
45229784f35SKrzysztof Karas 	g_bdevperf_conf = NULL;
45329784f35SKrzysztof Karas 
45429784f35SKrzysztof Karas 	TAILQ_FOREACH_SAFE(config, &job_config_list, link, tmp) {
45529784f35SKrzysztof Karas 		TAILQ_REMOVE(&job_config_list, config, link);
45629784f35SKrzysztof Karas 		free(config);
45729784f35SKrzysztof Karas 	}
45829784f35SKrzysztof Karas }
45929784f35SKrzysztof Karas 
46029784f35SKrzysztof Karas static void
461dcb296a3SJim Harris bdevperf_job_free(struct bdevperf_job *job)
462dcb296a3SJim Harris {
463723dd06eSRichael Zhuang 	spdk_histogram_data_free(job->histogram);
464dcb296a3SJim Harris 	spdk_bit_array_free(&job->outstanding);
465d254a3b9SSlawomir Ptak 	spdk_bit_array_free(&job->random_map);
466dcb296a3SJim Harris 	spdk_zipf_free(&job->zipf);
467dcb296a3SJim Harris 	free(job->name);
468dcb296a3SJim Harris 	free(job);
469dcb296a3SJim Harris }
470dcb296a3SJim Harris 
471dcb296a3SJim Harris static void
472081b190bSJim Harris job_thread_exit(void *ctx)
473081b190bSJim Harris {
474081b190bSJim Harris 	spdk_thread_exit(spdk_get_thread());
475081b190bSJim Harris }
476081b190bSJim Harris 
477081b190bSJim Harris static void
478723dd06eSRichael Zhuang check_cutoff(void *ctx, uint64_t start, uint64_t end, uint64_t count,
479723dd06eSRichael Zhuang 	     uint64_t total, uint64_t so_far)
480723dd06eSRichael Zhuang {
481723dd06eSRichael Zhuang 	double so_far_pct;
482723dd06eSRichael Zhuang 	double **cutoff = ctx;
483723dd06eSRichael Zhuang 	uint64_t tsc_rate;
484723dd06eSRichael Zhuang 
485723dd06eSRichael Zhuang 	if (count == 0) {
486723dd06eSRichael Zhuang 		return;
487723dd06eSRichael Zhuang 	}
488723dd06eSRichael Zhuang 
489723dd06eSRichael Zhuang 	tsc_rate = spdk_get_ticks_hz();
490723dd06eSRichael Zhuang 	so_far_pct = (double)so_far / total;
491723dd06eSRichael Zhuang 	while (so_far_pct >= **cutoff && **cutoff > 0) {
492045c781dSRichael Zhuang 		printf("%9.5f%% : %9.3fus\n", **cutoff * 100, (double)end * SPDK_SEC_TO_USEC / tsc_rate);
493723dd06eSRichael Zhuang 		(*cutoff)++;
494723dd06eSRichael Zhuang 	}
495723dd06eSRichael Zhuang }
496723dd06eSRichael Zhuang 
497723dd06eSRichael Zhuang static void
498723dd06eSRichael Zhuang print_bucket(void *ctx, uint64_t start, uint64_t end, uint64_t count,
499723dd06eSRichael Zhuang 	     uint64_t total, uint64_t so_far)
500723dd06eSRichael Zhuang {
501723dd06eSRichael Zhuang 	double so_far_pct;
502723dd06eSRichael Zhuang 	uint64_t tsc_rate;
503723dd06eSRichael Zhuang 
504723dd06eSRichael Zhuang 	if (count == 0) {
505723dd06eSRichael Zhuang 		return;
506723dd06eSRichael Zhuang 	}
507723dd06eSRichael Zhuang 
508723dd06eSRichael Zhuang 	tsc_rate = spdk_get_ticks_hz();
509723dd06eSRichael Zhuang 	so_far_pct = (double)so_far * 100 / total;
510723dd06eSRichael Zhuang 	printf("%9.3f - %9.3f: %9.4f%%  (%9ju)\n",
511045c781dSRichael Zhuang 	       (double)start * SPDK_SEC_TO_USEC / tsc_rate,
512045c781dSRichael Zhuang 	       (double)end * SPDK_SEC_TO_USEC / tsc_rate,
513723dd06eSRichael Zhuang 	       so_far_pct, count);
514723dd06eSRichael Zhuang }
515723dd06eSRichael Zhuang 
516723dd06eSRichael Zhuang static void
51729784f35SKrzysztof Karas bdevperf_test_done(void *ctx)
51829784f35SKrzysztof Karas {
51929784f35SKrzysztof Karas 	struct bdevperf_job *job, *jtmp;
52029784f35SKrzysztof Karas 	struct bdevperf_task *task, *ttmp;
5218e373044SShuhei Matsumoto 	struct lcore_thread *lthread, *lttmp;
522723dd06eSRichael Zhuang 	double average_latency = 0.0;
52329784f35SKrzysztof Karas 	uint64_t time_in_usec;
524723dd06eSRichael Zhuang 	int rc;
52529784f35SKrzysztof Karas 
52629784f35SKrzysztof Karas 	if (g_time_in_usec) {
52729784f35SKrzysztof Karas 		g_stats.io_time_in_usec = g_time_in_usec;
52829784f35SKrzysztof Karas 
52929784f35SKrzysztof Karas 		if (!g_run_rc && g_performance_dump_active) {
53029784f35SKrzysztof Karas 			spdk_thread_send_msg(spdk_get_thread(), bdevperf_test_done, NULL);
53129784f35SKrzysztof Karas 			return;
53229784f35SKrzysztof Karas 		}
53329784f35SKrzysztof Karas 	}
53429784f35SKrzysztof Karas 
53529784f35SKrzysztof Karas 	if (g_show_performance_real_time) {
53629784f35SKrzysztof Karas 		spdk_poller_unregister(&g_perf_timer);
53729784f35SKrzysztof Karas 	}
53829784f35SKrzysztof Karas 
53929784f35SKrzysztof Karas 	if (g_shutdown) {
54029784f35SKrzysztof Karas 		g_shutdown_tsc = spdk_get_ticks() - g_start_tsc;
541045c781dSRichael Zhuang 		time_in_usec = g_shutdown_tsc * SPDK_SEC_TO_USEC / spdk_get_ticks_hz();
54229784f35SKrzysztof Karas 		g_time_in_usec = (g_time_in_usec > time_in_usec) ? time_in_usec : g_time_in_usec;
54329784f35SKrzysztof Karas 		printf("Received shutdown signal, test time was about %.6f seconds\n",
544045c781dSRichael Zhuang 		       (double)g_time_in_usec / SPDK_SEC_TO_USEC);
54529784f35SKrzysztof Karas 	}
54629784f35SKrzysztof Karas 
547723dd06eSRichael Zhuang 	printf("\n%*s\n", 107, "Latency(us)");
548723dd06eSRichael Zhuang 	printf("\r %-*s: %10s %10s %10s %10s %8s %10s %10s %10s\n",
549723dd06eSRichael Zhuang 	       28, "Device Information", "runtime(s)", "IOPS", "MiB/s", "Fail/s", "TO/s", "Average", "min", "max");
55029784f35SKrzysztof Karas 
55129784f35SKrzysztof Karas 	TAILQ_FOREACH_SAFE(job, &g_bdevperf.jobs, link, jtmp) {
55229784f35SKrzysztof Karas 		performance_dump_job(&g_stats, job);
553723dd06eSRichael Zhuang 	}
554723dd06eSRichael Zhuang 
555723dd06eSRichael Zhuang 	printf("\r =================================================================================="
556723dd06eSRichael Zhuang 	       "=================================\n");
557723dd06eSRichael Zhuang 	printf("\r %-28s: %10s %10.2f %10.2f",
558723dd06eSRichael Zhuang 	       "Total", "", g_stats.total_io_per_second, g_stats.total_mb_per_second);
559723dd06eSRichael Zhuang 	printf(" %10.2f %8.2f",
560723dd06eSRichael Zhuang 	       g_stats.total_failed_per_second, g_stats.total_timeout_per_second);
561723dd06eSRichael Zhuang 
562723dd06eSRichael Zhuang 	if (g_stats.total_io_completed != 0) {
563045c781dSRichael Zhuang 		average_latency = ((double)g_stats.total_tsc / g_stats.total_io_completed) * SPDK_SEC_TO_USEC /
564723dd06eSRichael Zhuang 				  spdk_get_ticks_hz();
565723dd06eSRichael Zhuang 	}
566723dd06eSRichael Zhuang 	printf(" %10.2f %10.2f %10.2f\n", average_latency, g_stats.min_latency, g_stats.max_latency);
567723dd06eSRichael Zhuang 
568723dd06eSRichael Zhuang 	fflush(stdout);
569723dd06eSRichael Zhuang 
570723dd06eSRichael Zhuang 	if (g_latency_display_level == 0 || g_stats.total_io_completed == 0) {
571723dd06eSRichael Zhuang 		goto clean;
572723dd06eSRichael Zhuang 	}
573723dd06eSRichael Zhuang 
574723dd06eSRichael Zhuang 	printf("\n Latency summary\n");
575723dd06eSRichael Zhuang 	TAILQ_FOREACH_SAFE(job, &g_bdevperf.jobs, link, jtmp) {
576723dd06eSRichael Zhuang 		printf("\r =============================================\n");
57772da547aSShuhei Matsumoto 		printf("\r Job: %s (Core Mask 0x%s)\n", job->name,
578723dd06eSRichael Zhuang 		       spdk_cpuset_fmt(spdk_thread_get_cpumask(job->thread)));
579723dd06eSRichael Zhuang 
580723dd06eSRichael Zhuang 		const double *cutoff = g_latency_cutoffs;
581723dd06eSRichael Zhuang 
582723dd06eSRichael Zhuang 		spdk_histogram_data_iterate(job->histogram, check_cutoff, &cutoff);
583723dd06eSRichael Zhuang 
584723dd06eSRichael Zhuang 		printf("\n");
585723dd06eSRichael Zhuang 	}
586723dd06eSRichael Zhuang 
587723dd06eSRichael Zhuang 	if (g_latency_display_level == 1) {
588723dd06eSRichael Zhuang 		goto clean;
589723dd06eSRichael Zhuang 	}
590723dd06eSRichael Zhuang 
591723dd06eSRichael Zhuang 	printf("\r Latency histogram\n");
592723dd06eSRichael Zhuang 	TAILQ_FOREACH_SAFE(job, &g_bdevperf.jobs, link, jtmp) {
593723dd06eSRichael Zhuang 		printf("\r =============================================\n");
59472da547aSShuhei Matsumoto 		printf("\r Job: %s (Core Mask 0x%s)\n", job->name,
595723dd06eSRichael Zhuang 		       spdk_cpuset_fmt(spdk_thread_get_cpumask(job->thread)));
596723dd06eSRichael Zhuang 
597723dd06eSRichael Zhuang 		spdk_histogram_data_iterate(job->histogram, print_bucket, NULL);
598723dd06eSRichael Zhuang 		printf("\n");
599723dd06eSRichael Zhuang 	}
600723dd06eSRichael Zhuang 
601723dd06eSRichael Zhuang clean:
602723dd06eSRichael Zhuang 	TAILQ_FOREACH_SAFE(job, &g_bdevperf.jobs, link, jtmp) {
603723dd06eSRichael Zhuang 		TAILQ_REMOVE(&g_bdevperf.jobs, job, link);
60429784f35SKrzysztof Karas 
6058e373044SShuhei Matsumoto 		if (!g_one_thread_per_lcore) {
606081b190bSJim Harris 			spdk_thread_send_msg(job->thread, job_thread_exit, NULL);
6078e373044SShuhei Matsumoto 		}
608081b190bSJim Harris 
60929784f35SKrzysztof Karas 		TAILQ_FOREACH_SAFE(task, &job->task_list, link, ttmp) {
61029784f35SKrzysztof Karas 			TAILQ_REMOVE(&job->task_list, task, link);
61129784f35SKrzysztof Karas 			spdk_free(task->buf);
61229784f35SKrzysztof Karas 			spdk_free(task->md_buf);
61329784f35SKrzysztof Karas 			free(task);
61429784f35SKrzysztof Karas 		}
61529784f35SKrzysztof Karas 
616dcb296a3SJim Harris 		bdevperf_job_free(job);
61729784f35SKrzysztof Karas 	}
61829784f35SKrzysztof Karas 
6198e373044SShuhei Matsumoto 	if (g_one_thread_per_lcore) {
6208e373044SShuhei Matsumoto 		TAILQ_FOREACH_SAFE(lthread, &g_lcore_thread_list, link, lttmp) {
6218e373044SShuhei Matsumoto 			TAILQ_REMOVE(&g_lcore_thread_list, lthread, link);
6228e373044SShuhei Matsumoto 			spdk_thread_send_msg(lthread->thread, job_thread_exit, NULL);
6238e373044SShuhei Matsumoto 			free(lthread);
6248e373044SShuhei Matsumoto 		}
6258e373044SShuhei Matsumoto 	}
6268e373044SShuhei Matsumoto 
62729784f35SKrzysztof Karas 	rc = g_run_rc;
62829784f35SKrzysztof Karas 	if (g_request && !g_shutdown) {
62929784f35SKrzysztof Karas 		rpc_perform_tests_cb();
63029784f35SKrzysztof Karas 		if (rc != 0) {
63129784f35SKrzysztof Karas 			spdk_app_stop(rc);
63229784f35SKrzysztof Karas 		}
63329784f35SKrzysztof Karas 	} else {
63429784f35SKrzysztof Karas 		spdk_app_stop(rc);
63529784f35SKrzysztof Karas 	}
63629784f35SKrzysztof Karas }
63729784f35SKrzysztof Karas 
63829784f35SKrzysztof Karas static void
63929784f35SKrzysztof Karas bdevperf_job_end(void *ctx)
64029784f35SKrzysztof Karas {
64129784f35SKrzysztof Karas 	assert(g_main_thread == spdk_get_thread());
64229784f35SKrzysztof Karas 
64329784f35SKrzysztof Karas 	if (--g_bdevperf.running_jobs == 0) {
64429784f35SKrzysztof Karas 		bdevperf_test_done(NULL);
64529784f35SKrzysztof Karas 	}
64629784f35SKrzysztof Karas }
64729784f35SKrzysztof Karas 
64829784f35SKrzysztof Karas static void
649723dd06eSRichael Zhuang bdevperf_channel_get_histogram_cb(void *cb_arg, int status, struct spdk_histogram_data *histogram)
650723dd06eSRichael Zhuang {
651723dd06eSRichael Zhuang 	struct spdk_histogram_data *job_hist = cb_arg;
65260fd5c55SGangCao 
65360fd5c55SGangCao 	if (status == 0) {
654723dd06eSRichael Zhuang 		spdk_histogram_data_merge(job_hist, histogram);
655723dd06eSRichael Zhuang 	}
65660fd5c55SGangCao }
657723dd06eSRichael Zhuang 
658723dd06eSRichael Zhuang static void
659893aaaccSAlexey Marchuk bdevperf_job_empty(struct bdevperf_job *job)
660893aaaccSAlexey Marchuk {
661893aaaccSAlexey Marchuk 	uint64_t end_tsc = 0;
662893aaaccSAlexey Marchuk 
663893aaaccSAlexey Marchuk 	end_tsc = spdk_get_ticks() - g_start_tsc;
664893aaaccSAlexey Marchuk 	job->run_time_in_usec = end_tsc * SPDK_SEC_TO_USEC / spdk_get_ticks_hz();
665893aaaccSAlexey Marchuk 	/* keep histogram info before channel is destroyed */
666893aaaccSAlexey Marchuk 	spdk_bdev_channel_get_histogram(job->ch, bdevperf_channel_get_histogram_cb,
667893aaaccSAlexey Marchuk 					job->histogram);
668893aaaccSAlexey Marchuk 	spdk_put_io_channel(job->ch);
669893aaaccSAlexey Marchuk 	spdk_bdev_close(job->bdev_desc);
670893aaaccSAlexey Marchuk 	spdk_thread_send_msg(g_main_thread, bdevperf_job_end, NULL);
671893aaaccSAlexey Marchuk }
672893aaaccSAlexey Marchuk 
673893aaaccSAlexey Marchuk static void
67429784f35SKrzysztof Karas bdevperf_end_task(struct bdevperf_task *task)
67529784f35SKrzysztof Karas {
67629784f35SKrzysztof Karas 	struct bdevperf_job     *job = task->job;
67729784f35SKrzysztof Karas 
67829784f35SKrzysztof Karas 	TAILQ_INSERT_TAIL(&job->task_list, task, link);
67929784f35SKrzysztof Karas 	if (job->is_draining) {
68029784f35SKrzysztof Karas 		if (job->current_queue_depth == 0) {
681893aaaccSAlexey Marchuk 			bdevperf_job_empty(job);
68229784f35SKrzysztof Karas 		}
68329784f35SKrzysztof Karas 	}
68429784f35SKrzysztof Karas }
68529784f35SKrzysztof Karas 
68629784f35SKrzysztof Karas static void
68729784f35SKrzysztof Karas bdevperf_queue_io_wait_with_cb(struct bdevperf_task *task, spdk_bdev_io_wait_cb cb_fn)
68829784f35SKrzysztof Karas {
68929784f35SKrzysztof Karas 	struct bdevperf_job	*job = task->job;
69029784f35SKrzysztof Karas 
69129784f35SKrzysztof Karas 	task->bdev_io_wait.bdev = job->bdev;
69229784f35SKrzysztof Karas 	task->bdev_io_wait.cb_fn = cb_fn;
69329784f35SKrzysztof Karas 	task->bdev_io_wait.cb_arg = task;
69429784f35SKrzysztof Karas 	spdk_bdev_queue_io_wait(job->bdev, job->ch, &task->bdev_io_wait);
69529784f35SKrzysztof Karas }
69629784f35SKrzysztof Karas 
69729784f35SKrzysztof Karas static int
69829784f35SKrzysztof Karas bdevperf_job_drain(void *ctx)
69929784f35SKrzysztof Karas {
70029784f35SKrzysztof Karas 	struct bdevperf_job *job = ctx;
70129784f35SKrzysztof Karas 
70229784f35SKrzysztof Karas 	spdk_poller_unregister(&job->run_timer);
70329784f35SKrzysztof Karas 	if (job->reset) {
70429784f35SKrzysztof Karas 		spdk_poller_unregister(&job->reset_timer);
70529784f35SKrzysztof Karas 	}
70629784f35SKrzysztof Karas 
70729784f35SKrzysztof Karas 	job->is_draining = true;
70829784f35SKrzysztof Karas 
70929784f35SKrzysztof Karas 	return -1;
71029784f35SKrzysztof Karas }
71129784f35SKrzysztof Karas 
712893aaaccSAlexey Marchuk static int
713893aaaccSAlexey Marchuk bdevperf_job_drain_timer(void *ctx)
714893aaaccSAlexey Marchuk {
715893aaaccSAlexey Marchuk 	struct bdevperf_job *job = ctx;
716893aaaccSAlexey Marchuk 
717893aaaccSAlexey Marchuk 	bdevperf_job_drain(ctx);
718893aaaccSAlexey Marchuk 	if (job->current_queue_depth == 0) {
719893aaaccSAlexey Marchuk 		bdevperf_job_empty(job);
720893aaaccSAlexey Marchuk 	}
721893aaaccSAlexey Marchuk 
722893aaaccSAlexey Marchuk 	return SPDK_POLLER_BUSY;
723893aaaccSAlexey Marchuk }
724893aaaccSAlexey Marchuk 
72529784f35SKrzysztof Karas static void
72629784f35SKrzysztof Karas bdevperf_abort_complete(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg)
72729784f35SKrzysztof Karas {
72829784f35SKrzysztof Karas 	struct bdevperf_task	*task = cb_arg;
72929784f35SKrzysztof Karas 	struct bdevperf_job	*job = task->job;
73029784f35SKrzysztof Karas 
73129784f35SKrzysztof Karas 	job->current_queue_depth--;
73229784f35SKrzysztof Karas 
73329784f35SKrzysztof Karas 	if (success) {
73429784f35SKrzysztof Karas 		job->io_completed++;
73529784f35SKrzysztof Karas 	} else {
73629784f35SKrzysztof Karas 		job->io_failed++;
73729784f35SKrzysztof Karas 		if (!job->continue_on_failure) {
73829784f35SKrzysztof Karas 			bdevperf_job_drain(job);
73929784f35SKrzysztof Karas 			g_run_rc = -1;
74029784f35SKrzysztof Karas 		}
74129784f35SKrzysztof Karas 	}
74229784f35SKrzysztof Karas 
74329784f35SKrzysztof Karas 	spdk_bdev_free_io(bdev_io);
74429784f35SKrzysztof Karas 	bdevperf_end_task(task);
74529784f35SKrzysztof Karas }
74629784f35SKrzysztof Karas 
74729784f35SKrzysztof Karas static int
74829784f35SKrzysztof Karas bdevperf_verify_dif(struct bdevperf_task *task, struct iovec *iovs, int iovcnt)
74929784f35SKrzysztof Karas {
75029784f35SKrzysztof Karas 	struct bdevperf_job	*job = task->job;
75129784f35SKrzysztof Karas 	struct spdk_bdev	*bdev = job->bdev;
75229784f35SKrzysztof Karas 	struct spdk_dif_ctx	dif_ctx;
75329784f35SKrzysztof Karas 	struct spdk_dif_error	err_blk = {};
75429784f35SKrzysztof Karas 	int			rc;
755a711d629SSlawomir Ptak 	struct spdk_dif_ctx_init_ext_opts dif_opts;
75629784f35SKrzysztof Karas 
7575681a8a6SKonrad Sztyber 	dif_opts.size = SPDK_SIZEOF(&dif_opts, dif_pi_format);
758a711d629SSlawomir Ptak 	dif_opts.dif_pi_format = SPDK_DIF_PI_FORMAT_16;
75929784f35SKrzysztof Karas 	rc = spdk_dif_ctx_init(&dif_ctx,
76029784f35SKrzysztof Karas 			       spdk_bdev_get_block_size(bdev),
76129784f35SKrzysztof Karas 			       spdk_bdev_get_md_size(bdev),
76229784f35SKrzysztof Karas 			       spdk_bdev_is_md_interleaved(bdev),
76329784f35SKrzysztof Karas 			       spdk_bdev_is_dif_head_of_md(bdev),
76429784f35SKrzysztof Karas 			       spdk_bdev_get_dif_type(bdev),
76529784f35SKrzysztof Karas 			       job->dif_check_flags,
766a711d629SSlawomir Ptak 			       task->offset_blocks, 0, 0, 0, 0, &dif_opts);
76729784f35SKrzysztof Karas 	if (rc != 0) {
76829784f35SKrzysztof Karas 		fprintf(stderr, "Initialization of DIF context failed\n");
76929784f35SKrzysztof Karas 		return rc;
77029784f35SKrzysztof Karas 	}
77129784f35SKrzysztof Karas 
77229784f35SKrzysztof Karas 	if (spdk_bdev_is_md_interleaved(bdev)) {
77329784f35SKrzysztof Karas 		rc = spdk_dif_verify(iovs, iovcnt, job->io_size_blocks, &dif_ctx, &err_blk);
77429784f35SKrzysztof Karas 	} else {
77529784f35SKrzysztof Karas 		struct iovec md_iov = {
77629784f35SKrzysztof Karas 			.iov_base	= task->md_buf,
77729784f35SKrzysztof Karas 			.iov_len	= spdk_bdev_get_md_size(bdev) * job->io_size_blocks,
77829784f35SKrzysztof Karas 		};
77929784f35SKrzysztof Karas 
78029784f35SKrzysztof Karas 		rc = spdk_dix_verify(iovs, iovcnt, &md_iov, job->io_size_blocks, &dif_ctx, &err_blk);
78129784f35SKrzysztof Karas 	}
78229784f35SKrzysztof Karas 
78329784f35SKrzysztof Karas 	if (rc != 0) {
78429784f35SKrzysztof Karas 		fprintf(stderr, "DIF/DIX error detected. type=%d, offset=%" PRIu32 "\n",
78529784f35SKrzysztof Karas 			err_blk.err_type, err_blk.err_offset);
78629784f35SKrzysztof Karas 	}
78729784f35SKrzysztof Karas 
78829784f35SKrzysztof Karas 	return rc;
78929784f35SKrzysztof Karas }
79029784f35SKrzysztof Karas 
79129784f35SKrzysztof Karas static void
79229784f35SKrzysztof Karas bdevperf_complete(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg)
79329784f35SKrzysztof Karas {
79429784f35SKrzysztof Karas 	struct bdevperf_job	*job;
79529784f35SKrzysztof Karas 	struct bdevperf_task	*task = cb_arg;
79629784f35SKrzysztof Karas 	struct iovec		*iovs;
79729784f35SKrzysztof Karas 	int			iovcnt;
79829784f35SKrzysztof Karas 	bool			md_check;
79929784f35SKrzysztof Karas 	uint64_t		offset_in_ios;
80029784f35SKrzysztof Karas 	int			rc;
80129784f35SKrzysztof Karas 
80229784f35SKrzysztof Karas 	job = task->job;
80329784f35SKrzysztof Karas 	md_check = spdk_bdev_get_dif_type(job->bdev) == SPDK_DIF_DISABLE;
80429784f35SKrzysztof Karas 
80529784f35SKrzysztof Karas 	if (g_error_to_exit == true) {
80629784f35SKrzysztof Karas 		bdevperf_job_drain(job);
80729784f35SKrzysztof Karas 	} else if (!success) {
80829784f35SKrzysztof Karas 		if (!job->reset && !job->continue_on_failure) {
80929784f35SKrzysztof Karas 			bdevperf_job_drain(job);
81029784f35SKrzysztof Karas 			g_run_rc = -1;
81129784f35SKrzysztof Karas 			g_error_to_exit = true;
81229784f35SKrzysztof Karas 			printf("task offset: %" PRIu64 " on job bdev=%s fails\n",
81329784f35SKrzysztof Karas 			       task->offset_blocks, job->name);
81429784f35SKrzysztof Karas 		}
81529784f35SKrzysztof Karas 	} else if (job->verify || job->reset) {
81629784f35SKrzysztof Karas 		spdk_bdev_io_get_iovec(bdev_io, &iovs, &iovcnt);
81729784f35SKrzysztof Karas 		assert(iovcnt == 1);
81829784f35SKrzysztof Karas 		assert(iovs != NULL);
81929784f35SKrzysztof Karas 		if (!verify_data(task->buf, job->buf_size, iovs[0].iov_base, iovs[0].iov_len,
82029784f35SKrzysztof Karas 				 spdk_bdev_get_block_size(job->bdev),
82129784f35SKrzysztof Karas 				 task->md_buf, spdk_bdev_io_get_md_buf(bdev_io),
82229784f35SKrzysztof Karas 				 spdk_bdev_get_md_size(job->bdev),
82329784f35SKrzysztof Karas 				 job->io_size_blocks, md_check)) {
82429784f35SKrzysztof Karas 			printf("Buffer mismatch! Target: %s Disk Offset: %" PRIu64 "\n", job->name, task->offset_blocks);
82529784f35SKrzysztof Karas 			printf("   First dword expected 0x%x got 0x%x\n", *(int *)task->buf, *(int *)iovs[0].iov_base);
82629784f35SKrzysztof Karas 			bdevperf_job_drain(job);
82729784f35SKrzysztof Karas 			g_run_rc = -1;
82829784f35SKrzysztof Karas 		}
82929784f35SKrzysztof Karas 	} else if (job->dif_check_flags != 0) {
83029784f35SKrzysztof Karas 		if (task->io_type == SPDK_BDEV_IO_TYPE_READ && spdk_bdev_get_md_size(job->bdev) != 0) {
83129784f35SKrzysztof Karas 			spdk_bdev_io_get_iovec(bdev_io, &iovs, &iovcnt);
83229784f35SKrzysztof Karas 			assert(iovcnt == 1);
83329784f35SKrzysztof Karas 			assert(iovs != NULL);
83429784f35SKrzysztof Karas 			rc = bdevperf_verify_dif(task, iovs, iovcnt);
83529784f35SKrzysztof Karas 			if (rc != 0) {
83629784f35SKrzysztof Karas 				printf("DIF error detected. task offset: %" PRIu64 " on job bdev=%s\n",
83729784f35SKrzysztof Karas 				       task->offset_blocks, job->name);
83829784f35SKrzysztof Karas 
83929784f35SKrzysztof Karas 				success = false;
84029784f35SKrzysztof Karas 				if (!job->reset && !job->continue_on_failure) {
84129784f35SKrzysztof Karas 					bdevperf_job_drain(job);
84229784f35SKrzysztof Karas 					g_run_rc = -1;
84329784f35SKrzysztof Karas 					g_error_to_exit = true;
84429784f35SKrzysztof Karas 				}
84529784f35SKrzysztof Karas 			}
84629784f35SKrzysztof Karas 		}
84729784f35SKrzysztof Karas 	}
84829784f35SKrzysztof Karas 
84929784f35SKrzysztof Karas 	job->current_queue_depth--;
85029784f35SKrzysztof Karas 
85129784f35SKrzysztof Karas 	if (success) {
85229784f35SKrzysztof Karas 		job->io_completed++;
85329784f35SKrzysztof Karas 	} else {
85429784f35SKrzysztof Karas 		job->io_failed++;
85529784f35SKrzysztof Karas 	}
85629784f35SKrzysztof Karas 
85729784f35SKrzysztof Karas 	if (job->verify) {
85829784f35SKrzysztof Karas 		assert(task->offset_blocks / job->io_size_blocks >= job->ios_base);
85929784f35SKrzysztof Karas 		offset_in_ios = task->offset_blocks / job->io_size_blocks - job->ios_base;
86029784f35SKrzysztof Karas 
86129784f35SKrzysztof Karas 		assert(spdk_bit_array_get(job->outstanding, offset_in_ios) == true);
86229784f35SKrzysztof Karas 		spdk_bit_array_clear(job->outstanding, offset_in_ios);
86329784f35SKrzysztof Karas 	}
86429784f35SKrzysztof Karas 
86529784f35SKrzysztof Karas 	spdk_bdev_free_io(bdev_io);
86629784f35SKrzysztof Karas 
86729784f35SKrzysztof Karas 	/*
86829784f35SKrzysztof Karas 	 * is_draining indicates when time has expired for the test run
86929784f35SKrzysztof Karas 	 * and we are just waiting for the previously submitted I/O
87029784f35SKrzysztof Karas 	 * to complete.  In this case, do not submit a new I/O to replace
87129784f35SKrzysztof Karas 	 * the one just completed.
87229784f35SKrzysztof Karas 	 */
87329784f35SKrzysztof Karas 	if (!job->is_draining) {
87429784f35SKrzysztof Karas 		bdevperf_submit_single(job, task);
87529784f35SKrzysztof Karas 	} else {
87629784f35SKrzysztof Karas 		bdevperf_end_task(task);
87729784f35SKrzysztof Karas 	}
87829784f35SKrzysztof Karas }
87929784f35SKrzysztof Karas 
88029784f35SKrzysztof Karas static void
88129784f35SKrzysztof Karas bdevperf_verify_submit_read(void *cb_arg)
88229784f35SKrzysztof Karas {
88329784f35SKrzysztof Karas 	struct bdevperf_job	*job;
88429784f35SKrzysztof Karas 	struct bdevperf_task	*task = cb_arg;
88529784f35SKrzysztof Karas 	int			rc;
88629784f35SKrzysztof Karas 
88729784f35SKrzysztof Karas 	job = task->job;
88829784f35SKrzysztof Karas 
88929784f35SKrzysztof Karas 	/* Read the data back in */
89029784f35SKrzysztof Karas 	rc = spdk_bdev_read_blocks_with_md(job->bdev_desc, job->ch, NULL, NULL,
89129784f35SKrzysztof Karas 					   task->offset_blocks, job->io_size_blocks,
89229784f35SKrzysztof Karas 					   bdevperf_complete, task);
89329784f35SKrzysztof Karas 
89429784f35SKrzysztof Karas 	if (rc == -ENOMEM) {
89529784f35SKrzysztof Karas 		bdevperf_queue_io_wait_with_cb(task, bdevperf_verify_submit_read);
89629784f35SKrzysztof Karas 	} else if (rc != 0) {
89729784f35SKrzysztof Karas 		printf("Failed to submit read: %d\n", rc);
89829784f35SKrzysztof Karas 		bdevperf_job_drain(job);
89929784f35SKrzysztof Karas 		g_run_rc = rc;
90029784f35SKrzysztof Karas 	}
90129784f35SKrzysztof Karas }
90229784f35SKrzysztof Karas 
90329784f35SKrzysztof Karas static void
90429784f35SKrzysztof Karas bdevperf_verify_write_complete(struct spdk_bdev_io *bdev_io, bool success,
90529784f35SKrzysztof Karas 			       void *cb_arg)
90629784f35SKrzysztof Karas {
90729784f35SKrzysztof Karas 	if (success) {
90829784f35SKrzysztof Karas 		spdk_bdev_free_io(bdev_io);
90929784f35SKrzysztof Karas 		bdevperf_verify_submit_read(cb_arg);
91029784f35SKrzysztof Karas 	} else {
91129784f35SKrzysztof Karas 		bdevperf_complete(bdev_io, success, cb_arg);
91229784f35SKrzysztof Karas 	}
91329784f35SKrzysztof Karas }
91429784f35SKrzysztof Karas 
91529784f35SKrzysztof Karas static void
91629784f35SKrzysztof Karas bdevperf_zcopy_populate_complete(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg)
91729784f35SKrzysztof Karas {
91829784f35SKrzysztof Karas 	if (!success) {
91929784f35SKrzysztof Karas 		bdevperf_complete(bdev_io, success, cb_arg);
92029784f35SKrzysztof Karas 		return;
92129784f35SKrzysztof Karas 	}
92229784f35SKrzysztof Karas 
92329784f35SKrzysztof Karas 	spdk_bdev_zcopy_end(bdev_io, false, bdevperf_complete, cb_arg);
92429784f35SKrzysztof Karas }
92529784f35SKrzysztof Karas 
92629784f35SKrzysztof Karas static int
92729784f35SKrzysztof Karas bdevperf_generate_dif(struct bdevperf_task *task)
92829784f35SKrzysztof Karas {
92929784f35SKrzysztof Karas 	struct bdevperf_job	*job = task->job;
93029784f35SKrzysztof Karas 	struct spdk_bdev	*bdev = job->bdev;
93129784f35SKrzysztof Karas 	struct spdk_dif_ctx	dif_ctx;
93229784f35SKrzysztof Karas 	int			rc;
933a711d629SSlawomir Ptak 	struct spdk_dif_ctx_init_ext_opts dif_opts;
93429784f35SKrzysztof Karas 
9355681a8a6SKonrad Sztyber 	dif_opts.size = SPDK_SIZEOF(&dif_opts, dif_pi_format);
936a711d629SSlawomir Ptak 	dif_opts.dif_pi_format = SPDK_DIF_PI_FORMAT_16;
93729784f35SKrzysztof Karas 	rc = spdk_dif_ctx_init(&dif_ctx,
93829784f35SKrzysztof Karas 			       spdk_bdev_get_block_size(bdev),
93929784f35SKrzysztof Karas 			       spdk_bdev_get_md_size(bdev),
94029784f35SKrzysztof Karas 			       spdk_bdev_is_md_interleaved(bdev),
94129784f35SKrzysztof Karas 			       spdk_bdev_is_dif_head_of_md(bdev),
94229784f35SKrzysztof Karas 			       spdk_bdev_get_dif_type(bdev),
94329784f35SKrzysztof Karas 			       job->dif_check_flags,
944a711d629SSlawomir Ptak 			       task->offset_blocks, 0, 0, 0, 0, &dif_opts);
94529784f35SKrzysztof Karas 	if (rc != 0) {
94629784f35SKrzysztof Karas 		fprintf(stderr, "Initialization of DIF context failed\n");
94729784f35SKrzysztof Karas 		return rc;
94829784f35SKrzysztof Karas 	}
94929784f35SKrzysztof Karas 
95029784f35SKrzysztof Karas 	if (spdk_bdev_is_md_interleaved(bdev)) {
95129784f35SKrzysztof Karas 		rc = spdk_dif_generate(&task->iov, 1, job->io_size_blocks, &dif_ctx);
95229784f35SKrzysztof Karas 	} else {
95329784f35SKrzysztof Karas 		struct iovec md_iov = {
95429784f35SKrzysztof Karas 			.iov_base	= task->md_buf,
95529784f35SKrzysztof Karas 			.iov_len	= spdk_bdev_get_md_size(bdev) * job->io_size_blocks,
95629784f35SKrzysztof Karas 		};
95729784f35SKrzysztof Karas 
95829784f35SKrzysztof Karas 		rc = spdk_dix_generate(&task->iov, 1, &md_iov, job->io_size_blocks, &dif_ctx);
95929784f35SKrzysztof Karas 	}
96029784f35SKrzysztof Karas 
96129784f35SKrzysztof Karas 	if (rc != 0) {
96229784f35SKrzysztof Karas 		fprintf(stderr, "Generation of DIF/DIX failed\n");
96329784f35SKrzysztof Karas 	}
96429784f35SKrzysztof Karas 
96529784f35SKrzysztof Karas 	return rc;
96629784f35SKrzysztof Karas }
96729784f35SKrzysztof Karas 
96829784f35SKrzysztof Karas static void
96929784f35SKrzysztof Karas bdevperf_submit_task(void *arg)
97029784f35SKrzysztof Karas {
97129784f35SKrzysztof Karas 	struct bdevperf_task	*task = arg;
97229784f35SKrzysztof Karas 	struct bdevperf_job	*job = task->job;
97329784f35SKrzysztof Karas 	struct spdk_bdev_desc	*desc;
97429784f35SKrzysztof Karas 	struct spdk_io_channel	*ch;
97529784f35SKrzysztof Karas 	spdk_bdev_io_completion_cb cb_fn;
97629784f35SKrzysztof Karas 	uint64_t		offset_in_ios;
97729784f35SKrzysztof Karas 	int			rc = 0;
97829784f35SKrzysztof Karas 
97929784f35SKrzysztof Karas 	desc = job->bdev_desc;
98029784f35SKrzysztof Karas 	ch = job->ch;
98129784f35SKrzysztof Karas 
98229784f35SKrzysztof Karas 	switch (task->io_type) {
98329784f35SKrzysztof Karas 	case SPDK_BDEV_IO_TYPE_WRITE:
98429784f35SKrzysztof Karas 		if (spdk_bdev_get_md_size(job->bdev) != 0 && job->dif_check_flags != 0) {
98529784f35SKrzysztof Karas 			rc = bdevperf_generate_dif(task);
98629784f35SKrzysztof Karas 		}
98729784f35SKrzysztof Karas 		if (rc == 0) {
98829784f35SKrzysztof Karas 			cb_fn = (job->verify || job->reset) ? bdevperf_verify_write_complete : bdevperf_complete;
98929784f35SKrzysztof Karas 
99029784f35SKrzysztof Karas 			if (g_zcopy) {
99129784f35SKrzysztof Karas 				spdk_bdev_zcopy_end(task->bdev_io, true, cb_fn, task);
99229784f35SKrzysztof Karas 				return;
99329784f35SKrzysztof Karas 			} else {
99429784f35SKrzysztof Karas 				rc = spdk_bdev_writev_blocks_with_md(desc, ch, &task->iov, 1,
99529784f35SKrzysztof Karas 								     task->md_buf,
99629784f35SKrzysztof Karas 								     task->offset_blocks,
99729784f35SKrzysztof Karas 								     job->io_size_blocks,
99829784f35SKrzysztof Karas 								     cb_fn, task);
99929784f35SKrzysztof Karas 			}
100029784f35SKrzysztof Karas 		}
100129784f35SKrzysztof Karas 		break;
100229784f35SKrzysztof Karas 	case SPDK_BDEV_IO_TYPE_FLUSH:
100329784f35SKrzysztof Karas 		rc = spdk_bdev_flush_blocks(desc, ch, task->offset_blocks,
100429784f35SKrzysztof Karas 					    job->io_size_blocks, bdevperf_complete, task);
100529784f35SKrzysztof Karas 		break;
100629784f35SKrzysztof Karas 	case SPDK_BDEV_IO_TYPE_UNMAP:
100729784f35SKrzysztof Karas 		rc = spdk_bdev_unmap_blocks(desc, ch, task->offset_blocks,
100829784f35SKrzysztof Karas 					    job->io_size_blocks, bdevperf_complete, task);
100929784f35SKrzysztof Karas 		break;
101029784f35SKrzysztof Karas 	case SPDK_BDEV_IO_TYPE_WRITE_ZEROES:
101129784f35SKrzysztof Karas 		rc = spdk_bdev_write_zeroes_blocks(desc, ch, task->offset_blocks,
101229784f35SKrzysztof Karas 						   job->io_size_blocks, bdevperf_complete, task);
101329784f35SKrzysztof Karas 		break;
101429784f35SKrzysztof Karas 	case SPDK_BDEV_IO_TYPE_READ:
101529784f35SKrzysztof Karas 		if (g_zcopy) {
101629784f35SKrzysztof Karas 			rc = spdk_bdev_zcopy_start(desc, ch, NULL, 0, task->offset_blocks, job->io_size_blocks,
101729784f35SKrzysztof Karas 						   true, bdevperf_zcopy_populate_complete, task);
101829784f35SKrzysztof Karas 		} else {
101929784f35SKrzysztof Karas 			rc = spdk_bdev_read_blocks_with_md(desc, ch, task->buf, task->md_buf,
102029784f35SKrzysztof Karas 							   task->offset_blocks,
102129784f35SKrzysztof Karas 							   job->io_size_blocks,
102229784f35SKrzysztof Karas 							   bdevperf_complete, task);
102329784f35SKrzysztof Karas 		}
102429784f35SKrzysztof Karas 		break;
102529784f35SKrzysztof Karas 	case SPDK_BDEV_IO_TYPE_ABORT:
102629784f35SKrzysztof Karas 		rc = spdk_bdev_abort(desc, ch, task->task_to_abort, bdevperf_abort_complete, task);
102729784f35SKrzysztof Karas 		break;
102829784f35SKrzysztof Karas 	default:
102929784f35SKrzysztof Karas 		assert(false);
103029784f35SKrzysztof Karas 		rc = -EINVAL;
103129784f35SKrzysztof Karas 		break;
103229784f35SKrzysztof Karas 	}
103329784f35SKrzysztof Karas 
103429784f35SKrzysztof Karas 	if (rc == -ENOMEM) {
103529784f35SKrzysztof Karas 		bdevperf_queue_io_wait_with_cb(task, bdevperf_submit_task);
103629784f35SKrzysztof Karas 		return;
103729784f35SKrzysztof Karas 	} else if (rc != 0) {
103829784f35SKrzysztof Karas 		printf("Failed to submit bdev_io: %d\n", rc);
103929784f35SKrzysztof Karas 		if (job->verify) {
104029784f35SKrzysztof Karas 			assert(task->offset_blocks / job->io_size_blocks >= job->ios_base);
104129784f35SKrzysztof Karas 			offset_in_ios = task->offset_blocks / job->io_size_blocks - job->ios_base;
104229784f35SKrzysztof Karas 
104329784f35SKrzysztof Karas 			assert(spdk_bit_array_get(job->outstanding, offset_in_ios) == true);
104429784f35SKrzysztof Karas 			spdk_bit_array_clear(job->outstanding, offset_in_ios);
104529784f35SKrzysztof Karas 		}
104629784f35SKrzysztof Karas 		bdevperf_job_drain(job);
104729784f35SKrzysztof Karas 		g_run_rc = rc;
104829784f35SKrzysztof Karas 		return;
104929784f35SKrzysztof Karas 	}
105029784f35SKrzysztof Karas 
105129784f35SKrzysztof Karas 	job->current_queue_depth++;
105229784f35SKrzysztof Karas }
105329784f35SKrzysztof Karas 
105429784f35SKrzysztof Karas static void
105529784f35SKrzysztof Karas bdevperf_zcopy_get_buf_complete(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg)
105629784f35SKrzysztof Karas {
105729784f35SKrzysztof Karas 	struct bdevperf_task	*task = cb_arg;
105829784f35SKrzysztof Karas 	struct bdevperf_job	*job = task->job;
105929784f35SKrzysztof Karas 	struct iovec		*iovs;
106029784f35SKrzysztof Karas 	int			iovcnt;
106129784f35SKrzysztof Karas 
106229784f35SKrzysztof Karas 	if (!success) {
106329784f35SKrzysztof Karas 		bdevperf_job_drain(job);
106429784f35SKrzysztof Karas 		g_run_rc = -1;
106529784f35SKrzysztof Karas 		return;
106629784f35SKrzysztof Karas 	}
106729784f35SKrzysztof Karas 
106829784f35SKrzysztof Karas 	task->bdev_io = bdev_io;
106929784f35SKrzysztof Karas 	task->io_type = SPDK_BDEV_IO_TYPE_WRITE;
107029784f35SKrzysztof Karas 
107129784f35SKrzysztof Karas 	if (job->verify || job->reset) {
107229784f35SKrzysztof Karas 		/* When job->verify or job->reset is enabled, task->buf is used for
107329784f35SKrzysztof Karas 		 *  verification of read after write.  For write I/O, when zcopy APIs
107429784f35SKrzysztof Karas 		 *  are used, task->buf cannot be used, and data must be written to
107529784f35SKrzysztof Karas 		 *  the data buffer allocated underneath bdev layer instead.
107629784f35SKrzysztof Karas 		 *  Hence we copy task->buf to the allocated data buffer here.
107729784f35SKrzysztof Karas 		 */
107829784f35SKrzysztof Karas 		spdk_bdev_io_get_iovec(bdev_io, &iovs, &iovcnt);
107929784f35SKrzysztof Karas 		assert(iovcnt == 1);
108029784f35SKrzysztof Karas 		assert(iovs != NULL);
108129784f35SKrzysztof Karas 
108229784f35SKrzysztof Karas 		copy_data(iovs[0].iov_base, iovs[0].iov_len, task->buf, job->buf_size,
108329784f35SKrzysztof Karas 			  spdk_bdev_get_block_size(job->bdev),
108429784f35SKrzysztof Karas 			  spdk_bdev_io_get_md_buf(bdev_io), task->md_buf,
108529784f35SKrzysztof Karas 			  spdk_bdev_get_md_size(job->bdev), job->io_size_blocks);
108629784f35SKrzysztof Karas 	}
108729784f35SKrzysztof Karas 
108829784f35SKrzysztof Karas 	bdevperf_submit_task(task);
108929784f35SKrzysztof Karas }
109029784f35SKrzysztof Karas 
109129784f35SKrzysztof Karas static void
109229784f35SKrzysztof Karas bdevperf_prep_zcopy_write_task(void *arg)
109329784f35SKrzysztof Karas {
109429784f35SKrzysztof Karas 	struct bdevperf_task	*task = arg;
109529784f35SKrzysztof Karas 	struct bdevperf_job	*job = task->job;
109629784f35SKrzysztof Karas 	int			rc;
109729784f35SKrzysztof Karas 
109829784f35SKrzysztof Karas 	rc = spdk_bdev_zcopy_start(job->bdev_desc, job->ch, NULL, 0,
109929784f35SKrzysztof Karas 				   task->offset_blocks, job->io_size_blocks,
110029784f35SKrzysztof Karas 				   false, bdevperf_zcopy_get_buf_complete, task);
110129784f35SKrzysztof Karas 	if (rc != 0) {
110229784f35SKrzysztof Karas 		assert(rc == -ENOMEM);
110329784f35SKrzysztof Karas 		bdevperf_queue_io_wait_with_cb(task, bdevperf_prep_zcopy_write_task);
110429784f35SKrzysztof Karas 		return;
110529784f35SKrzysztof Karas 	}
110629784f35SKrzysztof Karas 
110729784f35SKrzysztof Karas 	job->current_queue_depth++;
110829784f35SKrzysztof Karas }
110929784f35SKrzysztof Karas 
111029784f35SKrzysztof Karas static struct bdevperf_task *
111129784f35SKrzysztof Karas bdevperf_job_get_task(struct bdevperf_job *job)
111229784f35SKrzysztof Karas {
111329784f35SKrzysztof Karas 	struct bdevperf_task *task;
111429784f35SKrzysztof Karas 
111529784f35SKrzysztof Karas 	task = TAILQ_FIRST(&job->task_list);
111629784f35SKrzysztof Karas 	if (!task) {
111729784f35SKrzysztof Karas 		printf("Task allocation failed\n");
111829784f35SKrzysztof Karas 		abort();
111929784f35SKrzysztof Karas 	}
112029784f35SKrzysztof Karas 
112129784f35SKrzysztof Karas 	TAILQ_REMOVE(&job->task_list, task, link);
112229784f35SKrzysztof Karas 	return task;
112329784f35SKrzysztof Karas }
112429784f35SKrzysztof Karas 
112529784f35SKrzysztof Karas static void
112629784f35SKrzysztof Karas bdevperf_submit_single(struct bdevperf_job *job, struct bdevperf_task *task)
112729784f35SKrzysztof Karas {
112829784f35SKrzysztof Karas 	uint64_t offset_in_ios;
11290d11cf93SJim Harris 	uint64_t rand_value;
1130d254a3b9SSlawomir Ptak 	uint32_t first_clear;
113129784f35SKrzysztof Karas 
113229784f35SKrzysztof Karas 	if (job->zipf) {
113329784f35SKrzysztof Karas 		offset_in_ios = spdk_zipf_generate(job->zipf);
113429784f35SKrzysztof Karas 	} else if (job->is_random) {
11350d11cf93SJim Harris 		/* RAND_MAX is only INT32_MAX, so use 2 calls to rand_r to
11360d11cf93SJim Harris 		 * get a large enough value to ensure we are issuing I/O
11370d11cf93SJim Harris 		 * uniformly across the whole bdev.
11380d11cf93SJim Harris 		 */
11390d11cf93SJim Harris 		rand_value = (uint64_t)rand_r(&job->seed) * RAND_MAX + rand_r(&job->seed);
11400d11cf93SJim Harris 		offset_in_ios = rand_value % job->size_in_ios;
1141d254a3b9SSlawomir Ptak 
1142d254a3b9SSlawomir Ptak 		if (g_random_map) {
1143d254a3b9SSlawomir Ptak 			/* Make sure, that the offset does not exceed the maximum size
1144d254a3b9SSlawomir Ptak 			 * of the bit array (verified during job creation)
1145d254a3b9SSlawomir Ptak 			 */
1146d254a3b9SSlawomir Ptak 			assert(offset_in_ios < UINT32_MAX);
1147d254a3b9SSlawomir Ptak 
1148d254a3b9SSlawomir Ptak 			first_clear = spdk_bit_array_find_first_clear(job->random_map, (uint32_t)offset_in_ios);
1149d254a3b9SSlawomir Ptak 
1150d254a3b9SSlawomir Ptak 			if (first_clear == UINT32_MAX) {
1151d254a3b9SSlawomir Ptak 				first_clear = spdk_bit_array_find_first_clear(job->random_map, 0);
1152d254a3b9SSlawomir Ptak 
1153d254a3b9SSlawomir Ptak 				if (first_clear == UINT32_MAX) {
1154d254a3b9SSlawomir Ptak 					/* If there are no more clear bits in the array, we start over
1155d254a3b9SSlawomir Ptak 					 * and select the previously selected random value.
1156d254a3b9SSlawomir Ptak 					 */
1157d254a3b9SSlawomir Ptak 					spdk_bit_array_clear_mask(job->random_map);
1158d254a3b9SSlawomir Ptak 					first_clear = (uint32_t)offset_in_ios;
1159d254a3b9SSlawomir Ptak 				}
1160d254a3b9SSlawomir Ptak 			}
1161d254a3b9SSlawomir Ptak 
1162d254a3b9SSlawomir Ptak 			spdk_bit_array_set(job->random_map, first_clear);
1163d254a3b9SSlawomir Ptak 
1164d254a3b9SSlawomir Ptak 			offset_in_ios = first_clear;
1165d254a3b9SSlawomir Ptak 		}
116629784f35SKrzysztof Karas 	} else {
116729784f35SKrzysztof Karas 		offset_in_ios = job->offset_in_ios++;
116829784f35SKrzysztof Karas 		if (job->offset_in_ios == job->size_in_ios) {
116929784f35SKrzysztof Karas 			job->offset_in_ios = 0;
117029784f35SKrzysztof Karas 		}
117129784f35SKrzysztof Karas 
117229784f35SKrzysztof Karas 		/* Increment of offset_in_ios if there's already an outstanding IO
117329784f35SKrzysztof Karas 		 * to that location. We only need this with job->verify as random
117429784f35SKrzysztof Karas 		 * offsets are not supported with job->verify at this time.
117529784f35SKrzysztof Karas 		 */
117629784f35SKrzysztof Karas 		if (job->verify) {
117729784f35SKrzysztof Karas 			assert(spdk_bit_array_find_first_clear(job->outstanding, 0) != UINT32_MAX);
117829784f35SKrzysztof Karas 
117929784f35SKrzysztof Karas 			while (spdk_bit_array_get(job->outstanding, offset_in_ios)) {
118029784f35SKrzysztof Karas 				offset_in_ios = job->offset_in_ios++;
118129784f35SKrzysztof Karas 				if (job->offset_in_ios == job->size_in_ios) {
118229784f35SKrzysztof Karas 					job->offset_in_ios = 0;
118329784f35SKrzysztof Karas 				}
118429784f35SKrzysztof Karas 			}
118529784f35SKrzysztof Karas 			spdk_bit_array_set(job->outstanding, offset_in_ios);
118629784f35SKrzysztof Karas 		}
118729784f35SKrzysztof Karas 	}
118829784f35SKrzysztof Karas 
118929784f35SKrzysztof Karas 	/* For multi-thread to same job, offset_in_ios is relative
119029784f35SKrzysztof Karas 	 * to the LBA range assigned for that job. job->offset_blocks
119129784f35SKrzysztof Karas 	 * is absolute (entire bdev LBA range).
119229784f35SKrzysztof Karas 	 */
119329784f35SKrzysztof Karas 	task->offset_blocks = (offset_in_ios + job->ios_base) * job->io_size_blocks;
119429784f35SKrzysztof Karas 
119529784f35SKrzysztof Karas 	if (job->verify || job->reset) {
119629784f35SKrzysztof Karas 		generate_data(task->buf, job->buf_size,
119729784f35SKrzysztof Karas 			      spdk_bdev_get_block_size(job->bdev),
119829784f35SKrzysztof Karas 			      task->md_buf, spdk_bdev_get_md_size(job->bdev),
119929784f35SKrzysztof Karas 			      job->io_size_blocks);
120029784f35SKrzysztof Karas 		if (g_zcopy) {
120129784f35SKrzysztof Karas 			bdevperf_prep_zcopy_write_task(task);
120229784f35SKrzysztof Karas 			return;
120329784f35SKrzysztof Karas 		} else {
120429784f35SKrzysztof Karas 			task->iov.iov_base = task->buf;
120529784f35SKrzysztof Karas 			task->iov.iov_len = job->buf_size;
120629784f35SKrzysztof Karas 			task->io_type = SPDK_BDEV_IO_TYPE_WRITE;
120729784f35SKrzysztof Karas 		}
120829784f35SKrzysztof Karas 	} else if (job->flush) {
120929784f35SKrzysztof Karas 		task->io_type = SPDK_BDEV_IO_TYPE_FLUSH;
121029784f35SKrzysztof Karas 	} else if (job->unmap) {
121129784f35SKrzysztof Karas 		task->io_type = SPDK_BDEV_IO_TYPE_UNMAP;
121229784f35SKrzysztof Karas 	} else if (job->write_zeroes) {
121329784f35SKrzysztof Karas 		task->io_type = SPDK_BDEV_IO_TYPE_WRITE_ZEROES;
121429784f35SKrzysztof Karas 	} else if ((job->rw_percentage == 100) ||
121529784f35SKrzysztof Karas 		   (job->rw_percentage != 0 && ((rand_r(&job->seed) % 100) < job->rw_percentage))) {
121629784f35SKrzysztof Karas 		task->io_type = SPDK_BDEV_IO_TYPE_READ;
121729784f35SKrzysztof Karas 	} else {
121829784f35SKrzysztof Karas 		if (g_zcopy) {
121929784f35SKrzysztof Karas 			bdevperf_prep_zcopy_write_task(task);
122029784f35SKrzysztof Karas 			return;
122129784f35SKrzysztof Karas 		} else {
122229784f35SKrzysztof Karas 			task->iov.iov_base = task->buf;
122329784f35SKrzysztof Karas 			task->iov.iov_len = job->buf_size;
122429784f35SKrzysztof Karas 			task->io_type = SPDK_BDEV_IO_TYPE_WRITE;
122529784f35SKrzysztof Karas 		}
122629784f35SKrzysztof Karas 	}
122729784f35SKrzysztof Karas 
122829784f35SKrzysztof Karas 	bdevperf_submit_task(task);
122929784f35SKrzysztof Karas }
123029784f35SKrzysztof Karas 
123129784f35SKrzysztof Karas static int reset_job(void *arg);
123229784f35SKrzysztof Karas 
123329784f35SKrzysztof Karas static void
123429784f35SKrzysztof Karas reset_cb(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg)
123529784f35SKrzysztof Karas {
123629784f35SKrzysztof Karas 	struct bdevperf_task	*task = cb_arg;
123729784f35SKrzysztof Karas 	struct bdevperf_job	*job = task->job;
123829784f35SKrzysztof Karas 
123929784f35SKrzysztof Karas 	if (!success) {
124029784f35SKrzysztof Karas 		printf("Reset blockdev=%s failed\n", spdk_bdev_get_name(job->bdev));
124129784f35SKrzysztof Karas 		bdevperf_job_drain(job);
124229784f35SKrzysztof Karas 		g_run_rc = -1;
124329784f35SKrzysztof Karas 	}
124429784f35SKrzysztof Karas 
124529784f35SKrzysztof Karas 	TAILQ_INSERT_TAIL(&job->task_list, task, link);
124629784f35SKrzysztof Karas 	spdk_bdev_free_io(bdev_io);
124729784f35SKrzysztof Karas 
124829784f35SKrzysztof Karas 	job->reset_timer = SPDK_POLLER_REGISTER(reset_job, job,
1249045c781dSRichael Zhuang 						10 * SPDK_SEC_TO_USEC);
125029784f35SKrzysztof Karas }
125129784f35SKrzysztof Karas 
125229784f35SKrzysztof Karas static int
125329784f35SKrzysztof Karas reset_job(void *arg)
125429784f35SKrzysztof Karas {
125529784f35SKrzysztof Karas 	struct bdevperf_job *job = arg;
125629784f35SKrzysztof Karas 	struct bdevperf_task *task;
125729784f35SKrzysztof Karas 	int rc;
125829784f35SKrzysztof Karas 
125929784f35SKrzysztof Karas 	spdk_poller_unregister(&job->reset_timer);
126029784f35SKrzysztof Karas 
126129784f35SKrzysztof Karas 	/* Do reset. */
126229784f35SKrzysztof Karas 	task = bdevperf_job_get_task(job);
126329784f35SKrzysztof Karas 	rc = spdk_bdev_reset(job->bdev_desc, job->ch,
126429784f35SKrzysztof Karas 			     reset_cb, task);
126529784f35SKrzysztof Karas 	if (rc) {
126629784f35SKrzysztof Karas 		printf("Reset failed: %d\n", rc);
126729784f35SKrzysztof Karas 		bdevperf_job_drain(job);
126829784f35SKrzysztof Karas 		g_run_rc = -1;
126929784f35SKrzysztof Karas 	}
127029784f35SKrzysztof Karas 
127129784f35SKrzysztof Karas 	return -1;
127229784f35SKrzysztof Karas }
127329784f35SKrzysztof Karas 
127429784f35SKrzysztof Karas static void
127529784f35SKrzysztof Karas bdevperf_timeout_cb(void *cb_arg, struct spdk_bdev_io *bdev_io)
127629784f35SKrzysztof Karas {
127729784f35SKrzysztof Karas 	struct bdevperf_job *job = cb_arg;
127829784f35SKrzysztof Karas 	struct bdevperf_task *task;
127929784f35SKrzysztof Karas 
128029784f35SKrzysztof Karas 	job->io_timeout++;
128129784f35SKrzysztof Karas 
128229784f35SKrzysztof Karas 	if (job->is_draining || !job->abort ||
128329784f35SKrzysztof Karas 	    !spdk_bdev_io_type_supported(job->bdev, SPDK_BDEV_IO_TYPE_ABORT)) {
128429784f35SKrzysztof Karas 		return;
128529784f35SKrzysztof Karas 	}
128629784f35SKrzysztof Karas 
128729784f35SKrzysztof Karas 	task = bdevperf_job_get_task(job);
128829784f35SKrzysztof Karas 	if (task == NULL) {
128929784f35SKrzysztof Karas 		return;
129029784f35SKrzysztof Karas 	}
129129784f35SKrzysztof Karas 
129229784f35SKrzysztof Karas 	task->task_to_abort = spdk_bdev_io_get_cb_arg(bdev_io);
129329784f35SKrzysztof Karas 	task->io_type = SPDK_BDEV_IO_TYPE_ABORT;
129429784f35SKrzysztof Karas 
129529784f35SKrzysztof Karas 	bdevperf_submit_task(task);
129629784f35SKrzysztof Karas }
129729784f35SKrzysztof Karas 
129829784f35SKrzysztof Karas static void
129929784f35SKrzysztof Karas bdevperf_job_run(void *ctx)
130029784f35SKrzysztof Karas {
130129784f35SKrzysztof Karas 	struct bdevperf_job *job = ctx;
130229784f35SKrzysztof Karas 	struct bdevperf_task *task;
130329784f35SKrzysztof Karas 	int i;
130429784f35SKrzysztof Karas 
130529784f35SKrzysztof Karas 	/* Submit initial I/O for this job. Each time one
130629784f35SKrzysztof Karas 	 * completes, another will be submitted. */
130729784f35SKrzysztof Karas 
130829784f35SKrzysztof Karas 	/* Start a timer to stop this I/O chain when the run is over */
1309893aaaccSAlexey Marchuk 	job->run_timer = SPDK_POLLER_REGISTER(bdevperf_job_drain_timer, job, g_time_in_usec);
131029784f35SKrzysztof Karas 	if (job->reset) {
131129784f35SKrzysztof Karas 		job->reset_timer = SPDK_POLLER_REGISTER(reset_job, job,
1312045c781dSRichael Zhuang 							10 * SPDK_SEC_TO_USEC);
131329784f35SKrzysztof Karas 	}
131429784f35SKrzysztof Karas 
131529784f35SKrzysztof Karas 	spdk_bdev_set_timeout(job->bdev_desc, g_timeout_in_sec, bdevperf_timeout_cb, job);
131629784f35SKrzysztof Karas 
131729784f35SKrzysztof Karas 	for (i = 0; i < job->queue_depth; i++) {
131829784f35SKrzysztof Karas 		task = bdevperf_job_get_task(job);
131929784f35SKrzysztof Karas 		bdevperf_submit_single(job, task);
132029784f35SKrzysztof Karas 	}
132129784f35SKrzysztof Karas }
132229784f35SKrzysztof Karas 
132329784f35SKrzysztof Karas static void
132429784f35SKrzysztof Karas _performance_dump_done(void *ctx)
132529784f35SKrzysztof Karas {
132629784f35SKrzysztof Karas 	struct bdevperf_aggregate_stats *stats = ctx;
1327723dd06eSRichael Zhuang 	double average_latency;
132829784f35SKrzysztof Karas 
1329723dd06eSRichael Zhuang 	printf("\r =================================================================================="
1330723dd06eSRichael Zhuang 	       "=================================\n");
133129784f35SKrzysztof Karas 	printf("\r %-28s: %10s %10.2f %10.2f",
133229784f35SKrzysztof Karas 	       "Total", "", stats->total_io_per_second, stats->total_mb_per_second);
1333723dd06eSRichael Zhuang 	printf(" %10.2f %8.2f",
133429784f35SKrzysztof Karas 	       stats->total_failed_per_second, stats->total_timeout_per_second);
1335723dd06eSRichael Zhuang 
1336045c781dSRichael Zhuang 	average_latency = ((double)stats->total_tsc / stats->total_io_completed) * SPDK_SEC_TO_USEC /
1337723dd06eSRichael Zhuang 			  spdk_get_ticks_hz();
1338723dd06eSRichael Zhuang 	printf(" %10.2f %10.2f %10.2f\n", average_latency, stats->min_latency, stats->max_latency);
1339723dd06eSRichael Zhuang 	printf("\n");
1340723dd06eSRichael Zhuang 
134129784f35SKrzysztof Karas 	fflush(stdout);
134229784f35SKrzysztof Karas 
134329784f35SKrzysztof Karas 	g_performance_dump_active = false;
134429784f35SKrzysztof Karas 
134529784f35SKrzysztof Karas 	free(stats);
134629784f35SKrzysztof Karas }
134729784f35SKrzysztof Karas 
134829784f35SKrzysztof Karas static void
134929784f35SKrzysztof Karas _performance_dump(void *ctx)
135029784f35SKrzysztof Karas {
135129784f35SKrzysztof Karas 	struct bdevperf_aggregate_stats *stats = ctx;
135229784f35SKrzysztof Karas 
135329784f35SKrzysztof Karas 	performance_dump_job(stats, stats->current_job);
135429784f35SKrzysztof Karas 
135529784f35SKrzysztof Karas 	/* This assumes the jobs list is static after start up time.
135629784f35SKrzysztof Karas 	 * That's true right now, but if that ever changed this would need a lock. */
135729784f35SKrzysztof Karas 	stats->current_job = TAILQ_NEXT(stats->current_job, link);
135829784f35SKrzysztof Karas 	if (stats->current_job == NULL) {
135929784f35SKrzysztof Karas 		spdk_thread_send_msg(g_main_thread, _performance_dump_done, stats);
136029784f35SKrzysztof Karas 	} else {
136129784f35SKrzysztof Karas 		spdk_thread_send_msg(stats->current_job->thread, _performance_dump, stats);
136229784f35SKrzysztof Karas 	}
136329784f35SKrzysztof Karas }
136429784f35SKrzysztof Karas 
136529784f35SKrzysztof Karas static int
136629784f35SKrzysztof Karas performance_statistics_thread(void *arg)
136729784f35SKrzysztof Karas {
136829784f35SKrzysztof Karas 	struct bdevperf_aggregate_stats *stats;
136929784f35SKrzysztof Karas 
137029784f35SKrzysztof Karas 	if (g_performance_dump_active) {
137129784f35SKrzysztof Karas 		return -1;
137229784f35SKrzysztof Karas 	}
137329784f35SKrzysztof Karas 
137429784f35SKrzysztof Karas 	g_performance_dump_active = true;
137529784f35SKrzysztof Karas 
137629784f35SKrzysztof Karas 	stats = calloc(1, sizeof(*stats));
137729784f35SKrzysztof Karas 	if (stats == NULL) {
137829784f35SKrzysztof Karas 		return -1;
137929784f35SKrzysztof Karas 	}
138029784f35SKrzysztof Karas 
1381723dd06eSRichael Zhuang 	stats->min_latency = (double)UINT64_MAX;
1382723dd06eSRichael Zhuang 
138329784f35SKrzysztof Karas 	g_show_performance_period_num++;
138429784f35SKrzysztof Karas 
138529784f35SKrzysztof Karas 	stats->io_time_in_usec = g_show_performance_period_num * g_show_performance_period_in_usec;
138629784f35SKrzysztof Karas 	stats->ema_period = g_show_performance_ema_period;
138729784f35SKrzysztof Karas 
138829784f35SKrzysztof Karas 	/* Iterate all of the jobs to gather stats
138929784f35SKrzysztof Karas 	 * These jobs will not get removed here until a final performance dump is run,
139029784f35SKrzysztof Karas 	 * so this should be safe without locking.
139129784f35SKrzysztof Karas 	 */
139229784f35SKrzysztof Karas 	stats->current_job = TAILQ_FIRST(&g_bdevperf.jobs);
139329784f35SKrzysztof Karas 	if (stats->current_job == NULL) {
139429784f35SKrzysztof Karas 		spdk_thread_send_msg(g_main_thread, _performance_dump_done, stats);
139529784f35SKrzysztof Karas 	} else {
139629784f35SKrzysztof Karas 		spdk_thread_send_msg(stats->current_job->thread, _performance_dump, stats);
139729784f35SKrzysztof Karas 	}
139829784f35SKrzysztof Karas 
139929784f35SKrzysztof Karas 	return -1;
140029784f35SKrzysztof Karas }
140129784f35SKrzysztof Karas 
140229784f35SKrzysztof Karas static void
140329784f35SKrzysztof Karas bdevperf_test(void)
140429784f35SKrzysztof Karas {
140529784f35SKrzysztof Karas 	struct bdevperf_job *job;
140629784f35SKrzysztof Karas 
1407045c781dSRichael Zhuang 	printf("Running I/O for %" PRIu64 " seconds...\n", g_time_in_usec / (uint64_t)SPDK_SEC_TO_USEC);
140829784f35SKrzysztof Karas 	fflush(stdout);
140929784f35SKrzysztof Karas 
141029784f35SKrzysztof Karas 	/* Start a timer to dump performance numbers */
141129784f35SKrzysztof Karas 	g_start_tsc = spdk_get_ticks();
141229784f35SKrzysztof Karas 	if (g_show_performance_real_time && !g_perf_timer) {
1413723dd06eSRichael Zhuang 		printf("%*s\n", 107, "Latency(us)");
1414723dd06eSRichael Zhuang 		printf("\r %-*s: %10s %10s %10s %10s %8s %10s %10s %10s\n",
1415723dd06eSRichael Zhuang 		       28, "Device Information", "runtime(s)", "IOPS", "MiB/s", "Fail/s", "TO/s", "Average", "min", "max");
141629784f35SKrzysztof Karas 
141729784f35SKrzysztof Karas 		g_perf_timer = SPDK_POLLER_REGISTER(performance_statistics_thread, NULL,
141829784f35SKrzysztof Karas 						    g_show_performance_period_in_usec);
141929784f35SKrzysztof Karas 	}
142029784f35SKrzysztof Karas 
142129784f35SKrzysztof Karas 	/* Iterate jobs to start all I/O */
142229784f35SKrzysztof Karas 	TAILQ_FOREACH(job, &g_bdevperf.jobs, link) {
142329784f35SKrzysztof Karas 		g_bdevperf.running_jobs++;
142429784f35SKrzysztof Karas 		spdk_thread_send_msg(job->thread, bdevperf_job_run, job);
142529784f35SKrzysztof Karas 	}
142629784f35SKrzysztof Karas }
142729784f35SKrzysztof Karas 
142829784f35SKrzysztof Karas static void
142929784f35SKrzysztof Karas bdevperf_bdev_removed(enum spdk_bdev_event_type type, struct spdk_bdev *bdev, void *event_ctx)
143029784f35SKrzysztof Karas {
143129784f35SKrzysztof Karas 	struct bdevperf_job *job = event_ctx;
143229784f35SKrzysztof Karas 
143329784f35SKrzysztof Karas 	if (SPDK_BDEV_EVENT_REMOVE == type) {
143429784f35SKrzysztof Karas 		bdevperf_job_drain(job);
143529784f35SKrzysztof Karas 	}
143629784f35SKrzysztof Karas }
143729784f35SKrzysztof Karas 
1438723dd06eSRichael Zhuang static void
1439723dd06eSRichael Zhuang bdevperf_histogram_status_cb(void *cb_arg, int status)
1440723dd06eSRichael Zhuang {
1441723dd06eSRichael Zhuang 	if (status != 0) {
1442723dd06eSRichael Zhuang 		g_run_rc = status;
1443723dd06eSRichael Zhuang 		if (g_continue_on_failure == false) {
1444723dd06eSRichael Zhuang 			g_error_to_exit = true;
1445723dd06eSRichael Zhuang 		}
1446723dd06eSRichael Zhuang 	}
1447723dd06eSRichael Zhuang 
1448723dd06eSRichael Zhuang 	if (--g_bdev_count == 0) {
1449723dd06eSRichael Zhuang 		if (g_run_rc == 0) {
1450723dd06eSRichael Zhuang 			/* Ready to run the test */
1451723dd06eSRichael Zhuang 			bdevperf_test();
1452723dd06eSRichael Zhuang 		} else {
1453723dd06eSRichael Zhuang 			bdevperf_test_done(NULL);
1454723dd06eSRichael Zhuang 		}
1455723dd06eSRichael Zhuang 	}
1456723dd06eSRichael Zhuang }
1457723dd06eSRichael Zhuang 
145829784f35SKrzysztof Karas static uint32_t g_construct_job_count = 0;
145929784f35SKrzysztof Karas 
146056479486SShuhei Matsumoto static int
146156479486SShuhei Matsumoto _bdevperf_enable_histogram(void *ctx, struct spdk_bdev *bdev)
146256479486SShuhei Matsumoto {
146356479486SShuhei Matsumoto 	bool *enable = ctx;
146456479486SShuhei Matsumoto 
146556479486SShuhei Matsumoto 	g_bdev_count++;
146656479486SShuhei Matsumoto 
146756479486SShuhei Matsumoto 	spdk_bdev_histogram_enable(bdev, bdevperf_histogram_status_cb, NULL, *enable);
146856479486SShuhei Matsumoto 
146956479486SShuhei Matsumoto 	return 0;
147056479486SShuhei Matsumoto }
147156479486SShuhei Matsumoto 
147229784f35SKrzysztof Karas static void
147356479486SShuhei Matsumoto bdevperf_enable_histogram(bool enable)
1474723dd06eSRichael Zhuang {
1475723dd06eSRichael Zhuang 	struct spdk_bdev *bdev;
147656479486SShuhei Matsumoto 	int rc;
147756479486SShuhei Matsumoto 
1478723dd06eSRichael Zhuang 	/* increment initial g_bdev_count so that it will never reach 0 in the middle of iteration */
1479723dd06eSRichael Zhuang 	g_bdev_count = 1;
1480723dd06eSRichael Zhuang 
1481723dd06eSRichael Zhuang 	if (g_job_bdev_name != NULL) {
1482723dd06eSRichael Zhuang 		bdev = spdk_bdev_get_by_name(g_job_bdev_name);
1483723dd06eSRichael Zhuang 		if (bdev) {
148456479486SShuhei Matsumoto 			rc = _bdevperf_enable_histogram(&enable, bdev);
1485723dd06eSRichael Zhuang 		} else {
1486723dd06eSRichael Zhuang 			fprintf(stderr, "Unable to find bdev '%s'\n", g_job_bdev_name);
148756479486SShuhei Matsumoto 			rc = -1;
1488723dd06eSRichael Zhuang 		}
1489723dd06eSRichael Zhuang 	} else {
149056479486SShuhei Matsumoto 		rc = spdk_for_each_bdev_leaf(&enable, _bdevperf_enable_histogram);
1491723dd06eSRichael Zhuang 	}
1492723dd06eSRichael Zhuang 
149356479486SShuhei Matsumoto 	bdevperf_histogram_status_cb(NULL, rc);
1494723dd06eSRichael Zhuang }
1495723dd06eSRichael Zhuang 
1496723dd06eSRichael Zhuang static void
149729784f35SKrzysztof Karas _bdevperf_construct_job_done(void *ctx)
149829784f35SKrzysztof Karas {
149929784f35SKrzysztof Karas 	if (--g_construct_job_count == 0) {
150029784f35SKrzysztof Karas 		if (g_run_rc != 0) {
150129784f35SKrzysztof Karas 			/* Something failed. */
150229784f35SKrzysztof Karas 			bdevperf_test_done(NULL);
150329784f35SKrzysztof Karas 			return;
150429784f35SKrzysztof Karas 		}
150529784f35SKrzysztof Karas 
1506723dd06eSRichael Zhuang 		/* always enable histogram. */
150756479486SShuhei Matsumoto 		bdevperf_enable_histogram(true);
150829784f35SKrzysztof Karas 	} else if (g_run_rc != 0) {
150929784f35SKrzysztof Karas 		/* Reset error as some jobs constructed right */
151029784f35SKrzysztof Karas 		g_run_rc = 0;
151129784f35SKrzysztof Karas 		if (g_continue_on_failure == false) {
151229784f35SKrzysztof Karas 			g_error_to_exit = true;
151329784f35SKrzysztof Karas 		}
151429784f35SKrzysztof Karas 	}
151529784f35SKrzysztof Karas }
151629784f35SKrzysztof Karas 
151729784f35SKrzysztof Karas /* Checkformat will not allow to use inlined type,
151829784f35SKrzysztof Karas    this is a workaround */
151929784f35SKrzysztof Karas typedef struct spdk_thread *spdk_thread_t;
152029784f35SKrzysztof Karas 
152129784f35SKrzysztof Karas static spdk_thread_t
152229784f35SKrzysztof Karas construct_job_thread(struct spdk_cpuset *cpumask, const char *tag)
152329784f35SKrzysztof Karas {
152429784f35SKrzysztof Karas 	struct spdk_cpuset tmp;
152529784f35SKrzysztof Karas 
152629784f35SKrzysztof Karas 	/* This function runs on the main thread. */
152729784f35SKrzysztof Karas 	assert(g_main_thread == spdk_get_thread());
152829784f35SKrzysztof Karas 
152929784f35SKrzysztof Karas 	/* Handle default mask */
153029784f35SKrzysztof Karas 	if (spdk_cpuset_count(cpumask) == 0) {
153129784f35SKrzysztof Karas 		cpumask = &g_all_cpuset;
153229784f35SKrzysztof Karas 	}
153329784f35SKrzysztof Karas 
153429784f35SKrzysztof Karas 	/* Warn user that mask might need to be changed */
153529784f35SKrzysztof Karas 	spdk_cpuset_copy(&tmp, cpumask);
153629784f35SKrzysztof Karas 	spdk_cpuset_or(&tmp, &g_all_cpuset);
153729784f35SKrzysztof Karas 	if (!spdk_cpuset_equal(&tmp, &g_all_cpuset)) {
153829784f35SKrzysztof Karas 		fprintf(stderr, "cpumask for '%s' is too big\n", tag);
153929784f35SKrzysztof Karas 	}
154029784f35SKrzysztof Karas 
154129784f35SKrzysztof Karas 	return spdk_thread_create(tag, cpumask);
154229784f35SKrzysztof Karas }
154329784f35SKrzysztof Karas 
154429784f35SKrzysztof Karas static uint32_t
154529784f35SKrzysztof Karas _get_next_core(void)
154629784f35SKrzysztof Karas {
154729784f35SKrzysztof Karas 	static uint32_t current_core = SPDK_ENV_LCORE_ID_ANY;
154829784f35SKrzysztof Karas 
154929784f35SKrzysztof Karas 	if (current_core == SPDK_ENV_LCORE_ID_ANY) {
155029784f35SKrzysztof Karas 		current_core = spdk_env_get_first_core();
155129784f35SKrzysztof Karas 		return current_core;
155229784f35SKrzysztof Karas 	}
155329784f35SKrzysztof Karas 
155429784f35SKrzysztof Karas 	current_core = spdk_env_get_next_core(current_core);
155529784f35SKrzysztof Karas 	if (current_core == SPDK_ENV_LCORE_ID_ANY) {
155629784f35SKrzysztof Karas 		current_core = spdk_env_get_first_core();
155729784f35SKrzysztof Karas 	}
155829784f35SKrzysztof Karas 
155929784f35SKrzysztof Karas 	return current_core;
156029784f35SKrzysztof Karas }
156129784f35SKrzysztof Karas 
156229784f35SKrzysztof Karas static void
156329784f35SKrzysztof Karas _bdevperf_construct_job(void *ctx)
156429784f35SKrzysztof Karas {
156529784f35SKrzysztof Karas 	struct bdevperf_job *job = ctx;
156629784f35SKrzysztof Karas 	int rc;
156729784f35SKrzysztof Karas 
156829784f35SKrzysztof Karas 	rc = spdk_bdev_open_ext(spdk_bdev_get_name(job->bdev), true, bdevperf_bdev_removed, job,
156929784f35SKrzysztof Karas 				&job->bdev_desc);
157029784f35SKrzysztof Karas 	if (rc != 0) {
157129784f35SKrzysztof Karas 		SPDK_ERRLOG("Could not open leaf bdev %s, error=%d\n", spdk_bdev_get_name(job->bdev), rc);
157229784f35SKrzysztof Karas 		g_run_rc = -EINVAL;
157329784f35SKrzysztof Karas 		goto end;
157429784f35SKrzysztof Karas 	}
157529784f35SKrzysztof Karas 
157629784f35SKrzysztof Karas 	if (g_zcopy) {
157729784f35SKrzysztof Karas 		if (!spdk_bdev_io_type_supported(job->bdev, SPDK_BDEV_IO_TYPE_ZCOPY)) {
157829784f35SKrzysztof Karas 			printf("Test requires ZCOPY but bdev module does not support ZCOPY\n");
157929784f35SKrzysztof Karas 			g_run_rc = -ENOTSUP;
158029784f35SKrzysztof Karas 			goto end;
158129784f35SKrzysztof Karas 		}
158229784f35SKrzysztof Karas 	}
158329784f35SKrzysztof Karas 
158429784f35SKrzysztof Karas 	job->ch = spdk_bdev_get_io_channel(job->bdev_desc);
158529784f35SKrzysztof Karas 	if (!job->ch) {
158629784f35SKrzysztof Karas 		SPDK_ERRLOG("Could not get io_channel for device %s, error=%d\n", spdk_bdev_get_name(job->bdev),
158729784f35SKrzysztof Karas 			    rc);
158829784f35SKrzysztof Karas 		spdk_bdev_close(job->bdev_desc);
158929784f35SKrzysztof Karas 		TAILQ_REMOVE(&g_bdevperf.jobs, job, link);
159029784f35SKrzysztof Karas 		g_run_rc = -ENOMEM;
159129784f35SKrzysztof Karas 		goto end;
159229784f35SKrzysztof Karas 	}
159329784f35SKrzysztof Karas 
159429784f35SKrzysztof Karas end:
159529784f35SKrzysztof Karas 	spdk_thread_send_msg(g_main_thread, _bdevperf_construct_job_done, NULL);
159629784f35SKrzysztof Karas }
159729784f35SKrzysztof Karas 
159829784f35SKrzysztof Karas static void
159929784f35SKrzysztof Karas job_init_rw(struct bdevperf_job *job, enum job_config_rw rw)
160029784f35SKrzysztof Karas {
160129784f35SKrzysztof Karas 	switch (rw) {
160229784f35SKrzysztof Karas 	case JOB_CONFIG_RW_READ:
160329784f35SKrzysztof Karas 		job->rw_percentage = 100;
160429784f35SKrzysztof Karas 		break;
160529784f35SKrzysztof Karas 	case JOB_CONFIG_RW_WRITE:
160629784f35SKrzysztof Karas 		job->rw_percentage = 0;
160729784f35SKrzysztof Karas 		break;
160829784f35SKrzysztof Karas 	case JOB_CONFIG_RW_RANDREAD:
160929784f35SKrzysztof Karas 		job->is_random = true;
161029784f35SKrzysztof Karas 		job->rw_percentage = 100;
161129784f35SKrzysztof Karas 		job->seed = rand();
161229784f35SKrzysztof Karas 		break;
161329784f35SKrzysztof Karas 	case JOB_CONFIG_RW_RANDWRITE:
161429784f35SKrzysztof Karas 		job->is_random = true;
161529784f35SKrzysztof Karas 		job->rw_percentage = 0;
161629784f35SKrzysztof Karas 		job->seed = rand();
161729784f35SKrzysztof Karas 		break;
161829784f35SKrzysztof Karas 	case JOB_CONFIG_RW_RW:
161929784f35SKrzysztof Karas 		job->is_random = false;
162029784f35SKrzysztof Karas 		break;
162129784f35SKrzysztof Karas 	case JOB_CONFIG_RW_RANDRW:
162229784f35SKrzysztof Karas 		job->is_random = true;
162329784f35SKrzysztof Karas 		job->seed = rand();
162429784f35SKrzysztof Karas 		break;
162529784f35SKrzysztof Karas 	case JOB_CONFIG_RW_VERIFY:
162629784f35SKrzysztof Karas 		job->verify = true;
162729784f35SKrzysztof Karas 		job->rw_percentage = 50;
162829784f35SKrzysztof Karas 		break;
162929784f35SKrzysztof Karas 	case JOB_CONFIG_RW_RESET:
163029784f35SKrzysztof Karas 		job->reset = true;
163129784f35SKrzysztof Karas 		job->verify = true;
163229784f35SKrzysztof Karas 		job->rw_percentage = 50;
163329784f35SKrzysztof Karas 		break;
163429784f35SKrzysztof Karas 	case JOB_CONFIG_RW_UNMAP:
163529784f35SKrzysztof Karas 		job->unmap = true;
163629784f35SKrzysztof Karas 		break;
163729784f35SKrzysztof Karas 	case JOB_CONFIG_RW_FLUSH:
163829784f35SKrzysztof Karas 		job->flush = true;
163929784f35SKrzysztof Karas 		break;
164029784f35SKrzysztof Karas 	case JOB_CONFIG_RW_WRITE_ZEROES:
164129784f35SKrzysztof Karas 		job->write_zeroes = true;
164229784f35SKrzysztof Karas 		break;
164329784f35SKrzysztof Karas 	}
164429784f35SKrzysztof Karas }
164529784f35SKrzysztof Karas 
164629784f35SKrzysztof Karas static int
164729784f35SKrzysztof Karas bdevperf_construct_job(struct spdk_bdev *bdev, struct job_config *config,
164829784f35SKrzysztof Karas 		       struct spdk_thread *thread)
164929784f35SKrzysztof Karas {
165029784f35SKrzysztof Karas 	struct bdevperf_job *job;
165129784f35SKrzysztof Karas 	struct bdevperf_task *task;
165229784f35SKrzysztof Karas 	int block_size, data_block_size;
165329784f35SKrzysztof Karas 	int rc;
165429784f35SKrzysztof Karas 	int task_num, n;
165529784f35SKrzysztof Karas 
165629784f35SKrzysztof Karas 	block_size = spdk_bdev_get_block_size(bdev);
165729784f35SKrzysztof Karas 	data_block_size = spdk_bdev_get_data_block_size(bdev);
165829784f35SKrzysztof Karas 
165929784f35SKrzysztof Karas 	job = calloc(1, sizeof(struct bdevperf_job));
166029784f35SKrzysztof Karas 	if (!job) {
166129784f35SKrzysztof Karas 		fprintf(stderr, "Unable to allocate memory for new job.\n");
166229784f35SKrzysztof Karas 		return -ENOMEM;
166329784f35SKrzysztof Karas 	}
166429784f35SKrzysztof Karas 
166529784f35SKrzysztof Karas 	job->name = strdup(spdk_bdev_get_name(bdev));
166629784f35SKrzysztof Karas 	if (!job->name) {
166729784f35SKrzysztof Karas 		fprintf(stderr, "Unable to allocate memory for job name.\n");
1668dcb296a3SJim Harris 		bdevperf_job_free(job);
166929784f35SKrzysztof Karas 		return -ENOMEM;
167029784f35SKrzysztof Karas 	}
167129784f35SKrzysztof Karas 
167229784f35SKrzysztof Karas 	job->workload_type = g_workload_type;
167329784f35SKrzysztof Karas 	job->io_size = config->bs;
167429784f35SKrzysztof Karas 	job->rw_percentage = config->rwmixread;
167529784f35SKrzysztof Karas 	job->continue_on_failure = g_continue_on_failure;
167629784f35SKrzysztof Karas 	job->queue_depth = config->iodepth;
167729784f35SKrzysztof Karas 	job->bdev = bdev;
167829784f35SKrzysztof Karas 	job->io_size_blocks = job->io_size / data_block_size;
167929784f35SKrzysztof Karas 	job->buf_size = job->io_size_blocks * block_size;
168029784f35SKrzysztof Karas 	job->abort = g_abort;
168129784f35SKrzysztof Karas 	job_init_rw(job, config->rw);
168229784f35SKrzysztof Karas 
168329784f35SKrzysztof Karas 	if ((job->io_size % data_block_size) != 0) {
168429784f35SKrzysztof Karas 		SPDK_ERRLOG("IO size (%d) is not multiples of data block size of bdev %s (%"PRIu32")\n",
168529784f35SKrzysztof Karas 			    job->io_size, spdk_bdev_get_name(bdev), data_block_size);
1686dcb296a3SJim Harris 		bdevperf_job_free(job);
168729784f35SKrzysztof Karas 		return -ENOTSUP;
168829784f35SKrzysztof Karas 	}
168929784f35SKrzysztof Karas 
169029784f35SKrzysztof Karas 	if (job->unmap && !spdk_bdev_io_type_supported(bdev, SPDK_BDEV_IO_TYPE_UNMAP)) {
169129784f35SKrzysztof Karas 		printf("Skipping %s because it does not support unmap\n", spdk_bdev_get_name(bdev));
1692dcb296a3SJim Harris 		bdevperf_job_free(job);
169329784f35SKrzysztof Karas 		return -ENOTSUP;
169429784f35SKrzysztof Karas 	}
169529784f35SKrzysztof Karas 
169629784f35SKrzysztof Karas 	if (spdk_bdev_is_dif_check_enabled(bdev, SPDK_DIF_CHECK_TYPE_REFTAG)) {
169729784f35SKrzysztof Karas 		job->dif_check_flags |= SPDK_DIF_FLAGS_REFTAG_CHECK;
169829784f35SKrzysztof Karas 	}
169929784f35SKrzysztof Karas 	if (spdk_bdev_is_dif_check_enabled(bdev, SPDK_DIF_CHECK_TYPE_GUARD)) {
170029784f35SKrzysztof Karas 		job->dif_check_flags |= SPDK_DIF_FLAGS_GUARD_CHECK;
170129784f35SKrzysztof Karas 	}
170229784f35SKrzysztof Karas 
170329784f35SKrzysztof Karas 	job->offset_in_ios = 0;
170429784f35SKrzysztof Karas 
170529784f35SKrzysztof Karas 	if (config->length != 0) {
170629784f35SKrzysztof Karas 		/* Use subset of disk */
170729784f35SKrzysztof Karas 		job->size_in_ios = config->length / job->io_size_blocks;
170829784f35SKrzysztof Karas 		job->ios_base = config->offset / job->io_size_blocks;
170929784f35SKrzysztof Karas 	} else {
171029784f35SKrzysztof Karas 		/* Use whole disk */
171129784f35SKrzysztof Karas 		job->size_in_ios = spdk_bdev_get_num_blocks(bdev) / job->io_size_blocks;
171229784f35SKrzysztof Karas 		job->ios_base = 0;
171329784f35SKrzysztof Karas 	}
171429784f35SKrzysztof Karas 
171529784f35SKrzysztof Karas 	if (job->is_random && g_zipf_theta > 0) {
171629784f35SKrzysztof Karas 		job->zipf = spdk_zipf_create(job->size_in_ios, g_zipf_theta, 0);
171729784f35SKrzysztof Karas 	}
171829784f35SKrzysztof Karas 
171929784f35SKrzysztof Karas 	if (job->verify) {
1720652db802SSlawomir Ptak 		if (job->size_in_ios >= UINT32_MAX) {
1721652db802SSlawomir Ptak 			SPDK_ERRLOG("Due to constraints of verify operation, the job storage capacity is too large\n");
1722652db802SSlawomir Ptak 			bdevperf_job_free(job);
1723652db802SSlawomir Ptak 			return -ENOMEM;
1724652db802SSlawomir Ptak 		}
172529784f35SKrzysztof Karas 		job->outstanding = spdk_bit_array_create(job->size_in_ios);
172629784f35SKrzysztof Karas 		if (job->outstanding == NULL) {
172729784f35SKrzysztof Karas 			SPDK_ERRLOG("Could not create outstanding array bitmap for bdev %s\n",
172829784f35SKrzysztof Karas 				    spdk_bdev_get_name(bdev));
1729dcb296a3SJim Harris 			bdevperf_job_free(job);
173029784f35SKrzysztof Karas 			return -ENOMEM;
173129784f35SKrzysztof Karas 		}
1732e58885f9SAlexey Marchuk 		if (job->queue_depth > (int)job->size_in_ios) {
1733e58885f9SAlexey Marchuk 			SPDK_WARNLOG("Due to constraints of verify job, queue depth (-q, %d) can't exceed the number of IO "
1734e58885f9SAlexey Marchuk 				     "requests which can be submitted to the bdev %s simultaneously (%"PRIu64"). "
1735e58885f9SAlexey Marchuk 				     "Queue depth is limited to %"PRIu64"\n",
1736e58885f9SAlexey Marchuk 				     job->queue_depth, job->name, job->size_in_ios, job->size_in_ios);
1737e58885f9SAlexey Marchuk 			job->queue_depth = (int)job->size_in_ios;
1738e58885f9SAlexey Marchuk 		}
173929784f35SKrzysztof Karas 	}
174029784f35SKrzysztof Karas 
1741723dd06eSRichael Zhuang 	job->histogram = spdk_histogram_data_alloc();
1742723dd06eSRichael Zhuang 	if (job->histogram == NULL) {
1743723dd06eSRichael Zhuang 		fprintf(stderr, "Failed to allocate histogram\n");
1744723dd06eSRichael Zhuang 		bdevperf_job_free(job);
1745723dd06eSRichael Zhuang 		return -ENOMEM;
1746723dd06eSRichael Zhuang 	}
1747723dd06eSRichael Zhuang 
174829784f35SKrzysztof Karas 	TAILQ_INIT(&job->task_list);
174929784f35SKrzysztof Karas 
1750d254a3b9SSlawomir Ptak 	if (g_random_map) {
1751d254a3b9SSlawomir Ptak 		if (job->size_in_ios >= UINT32_MAX) {
1752d254a3b9SSlawomir Ptak 			SPDK_ERRLOG("Due to constraints of the random map, the job storage capacity is too large\n");
1753d254a3b9SSlawomir Ptak 			bdevperf_job_free(job);
1754d254a3b9SSlawomir Ptak 			return -ENOMEM;
1755d254a3b9SSlawomir Ptak 		}
1756d254a3b9SSlawomir Ptak 		job->random_map = spdk_bit_array_create(job->size_in_ios);
1757d254a3b9SSlawomir Ptak 		if (job->random_map == NULL) {
1758d254a3b9SSlawomir Ptak 			SPDK_ERRLOG("Could not create random_map array bitmap for bdev %s\n",
1759d254a3b9SSlawomir Ptak 				    spdk_bdev_get_name(bdev));
1760d254a3b9SSlawomir Ptak 			bdevperf_job_free(job);
1761d254a3b9SSlawomir Ptak 			return -ENOMEM;
1762d254a3b9SSlawomir Ptak 		}
1763d254a3b9SSlawomir Ptak 	}
1764d254a3b9SSlawomir Ptak 
176529784f35SKrzysztof Karas 	task_num = job->queue_depth;
176629784f35SKrzysztof Karas 	if (job->reset) {
176729784f35SKrzysztof Karas 		task_num += 1;
176829784f35SKrzysztof Karas 	}
176929784f35SKrzysztof Karas 	if (job->abort) {
177029784f35SKrzysztof Karas 		task_num += job->queue_depth;
177129784f35SKrzysztof Karas 	}
177229784f35SKrzysztof Karas 
177329784f35SKrzysztof Karas 	TAILQ_INSERT_TAIL(&g_bdevperf.jobs, job, link);
177429784f35SKrzysztof Karas 
177529784f35SKrzysztof Karas 	for (n = 0; n < task_num; n++) {
177629784f35SKrzysztof Karas 		task = calloc(1, sizeof(struct bdevperf_task));
177729784f35SKrzysztof Karas 		if (!task) {
177829784f35SKrzysztof Karas 			fprintf(stderr, "Failed to allocate task from memory\n");
17798e8c360bSKrzysztof Karas 			spdk_zipf_free(&job->zipf);
178029784f35SKrzysztof Karas 			return -ENOMEM;
178129784f35SKrzysztof Karas 		}
178229784f35SKrzysztof Karas 
178329784f35SKrzysztof Karas 		task->buf = spdk_zmalloc(job->buf_size, spdk_bdev_get_buf_align(job->bdev), NULL,
178429784f35SKrzysztof Karas 					 SPDK_ENV_LCORE_ID_ANY, SPDK_MALLOC_DMA);
178529784f35SKrzysztof Karas 		if (!task->buf) {
178629784f35SKrzysztof Karas 			fprintf(stderr, "Cannot allocate buf for task=%p\n", task);
17878e8c360bSKrzysztof Karas 			spdk_zipf_free(&job->zipf);
178829784f35SKrzysztof Karas 			free(task);
178929784f35SKrzysztof Karas 			return -ENOMEM;
179029784f35SKrzysztof Karas 		}
179129784f35SKrzysztof Karas 
179229784f35SKrzysztof Karas 		if (spdk_bdev_is_md_separate(job->bdev)) {
179329784f35SKrzysztof Karas 			task->md_buf = spdk_zmalloc(job->io_size_blocks *
179429784f35SKrzysztof Karas 						    spdk_bdev_get_md_size(job->bdev), 0, NULL,
179529784f35SKrzysztof Karas 						    SPDK_ENV_LCORE_ID_ANY, SPDK_MALLOC_DMA);
179629784f35SKrzysztof Karas 			if (!task->md_buf) {
179729784f35SKrzysztof Karas 				fprintf(stderr, "Cannot allocate md buf for task=%p\n", task);
17988e8c360bSKrzysztof Karas 				spdk_zipf_free(&job->zipf);
179929784f35SKrzysztof Karas 				spdk_free(task->buf);
180029784f35SKrzysztof Karas 				free(task);
180129784f35SKrzysztof Karas 				return -ENOMEM;
180229784f35SKrzysztof Karas 			}
180329784f35SKrzysztof Karas 		}
180429784f35SKrzysztof Karas 
180529784f35SKrzysztof Karas 		task->job = job;
180629784f35SKrzysztof Karas 		TAILQ_INSERT_TAIL(&job->task_list, task, link);
180729784f35SKrzysztof Karas 	}
180829784f35SKrzysztof Karas 
180929784f35SKrzysztof Karas 	job->thread = thread;
181029784f35SKrzysztof Karas 
181129784f35SKrzysztof Karas 	g_construct_job_count++;
181229784f35SKrzysztof Karas 
181329784f35SKrzysztof Karas 	rc = spdk_thread_send_msg(thread, _bdevperf_construct_job, job);
181429784f35SKrzysztof Karas 	assert(rc == 0);
181529784f35SKrzysztof Karas 
181629784f35SKrzysztof Karas 	return rc;
181729784f35SKrzysztof Karas }
181829784f35SKrzysztof Karas 
181929784f35SKrzysztof Karas static int
182029784f35SKrzysztof Karas parse_rw(const char *str, enum job_config_rw ret)
182129784f35SKrzysztof Karas {
182229784f35SKrzysztof Karas 	if (str == NULL) {
182329784f35SKrzysztof Karas 		return ret;
182429784f35SKrzysztof Karas 	}
182529784f35SKrzysztof Karas 
182629784f35SKrzysztof Karas 	if (!strcmp(str, "read")) {
182729784f35SKrzysztof Karas 		ret = JOB_CONFIG_RW_READ;
182829784f35SKrzysztof Karas 	} else if (!strcmp(str, "randread")) {
182929784f35SKrzysztof Karas 		ret = JOB_CONFIG_RW_RANDREAD;
183029784f35SKrzysztof Karas 	} else if (!strcmp(str, "write")) {
183129784f35SKrzysztof Karas 		ret = JOB_CONFIG_RW_WRITE;
183229784f35SKrzysztof Karas 	} else if (!strcmp(str, "randwrite")) {
183329784f35SKrzysztof Karas 		ret = JOB_CONFIG_RW_RANDWRITE;
183429784f35SKrzysztof Karas 	} else if (!strcmp(str, "verify")) {
183529784f35SKrzysztof Karas 		ret = JOB_CONFIG_RW_VERIFY;
183629784f35SKrzysztof Karas 	} else if (!strcmp(str, "reset")) {
183729784f35SKrzysztof Karas 		ret = JOB_CONFIG_RW_RESET;
183829784f35SKrzysztof Karas 	} else if (!strcmp(str, "unmap")) {
183929784f35SKrzysztof Karas 		ret = JOB_CONFIG_RW_UNMAP;
184029784f35SKrzysztof Karas 	} else if (!strcmp(str, "write_zeroes")) {
184129784f35SKrzysztof Karas 		ret = JOB_CONFIG_RW_WRITE_ZEROES;
184229784f35SKrzysztof Karas 	} else if (!strcmp(str, "flush")) {
184329784f35SKrzysztof Karas 		ret = JOB_CONFIG_RW_FLUSH;
184429784f35SKrzysztof Karas 	} else if (!strcmp(str, "rw")) {
184529784f35SKrzysztof Karas 		ret = JOB_CONFIG_RW_RW;
184629784f35SKrzysztof Karas 	} else if (!strcmp(str, "randrw")) {
184729784f35SKrzysztof Karas 		ret = JOB_CONFIG_RW_RANDRW;
184829784f35SKrzysztof Karas 	} else {
184929784f35SKrzysztof Karas 		fprintf(stderr, "rw must be one of\n"
185029784f35SKrzysztof Karas 			"(read, write, randread, randwrite, rw, randrw, verify, reset, unmap, flush)\n");
185129784f35SKrzysztof Karas 		ret = BDEVPERF_CONFIG_ERROR;
185229784f35SKrzysztof Karas 	}
185329784f35SKrzysztof Karas 
185429784f35SKrzysztof Karas 	return ret;
185529784f35SKrzysztof Karas }
185629784f35SKrzysztof Karas 
185729784f35SKrzysztof Karas static const char *
185829784f35SKrzysztof Karas config_filename_next(const char *filename, char *out)
185929784f35SKrzysztof Karas {
186029784f35SKrzysztof Karas 	int i, k;
186129784f35SKrzysztof Karas 
186229784f35SKrzysztof Karas 	if (filename == NULL) {
186329784f35SKrzysztof Karas 		out[0] = '\0';
186429784f35SKrzysztof Karas 		return NULL;
186529784f35SKrzysztof Karas 	}
186629784f35SKrzysztof Karas 
186729784f35SKrzysztof Karas 	if (filename[0] == ':') {
186829784f35SKrzysztof Karas 		filename++;
186929784f35SKrzysztof Karas 	}
187029784f35SKrzysztof Karas 
187129784f35SKrzysztof Karas 	for (i = 0, k = 0;
187229784f35SKrzysztof Karas 	     filename[i] != '\0' &&
187329784f35SKrzysztof Karas 	     filename[i] != ':' &&
18740a638e9dSMarcin Spiewak 	     i < BDEVPERF_CONFIG_MAX_FILENAME &&
18750a638e9dSMarcin Spiewak 	     k < (BDEVPERF_CONFIG_MAX_FILENAME - 1);
187629784f35SKrzysztof Karas 	     i++) {
187729784f35SKrzysztof Karas 		if (filename[i] == ' ' || filename[i] == '\t') {
187829784f35SKrzysztof Karas 			continue;
187929784f35SKrzysztof Karas 		}
188029784f35SKrzysztof Karas 
188129784f35SKrzysztof Karas 		out[k++] = filename[i];
188229784f35SKrzysztof Karas 	}
188329784f35SKrzysztof Karas 	out[k] = 0;
188429784f35SKrzysztof Karas 
188529784f35SKrzysztof Karas 	return filename + i;
188629784f35SKrzysztof Karas }
188729784f35SKrzysztof Karas 
18888e373044SShuhei Matsumoto static struct spdk_thread *
18898e373044SShuhei Matsumoto get_lcore_thread(uint32_t lcore)
18908e373044SShuhei Matsumoto {
18918e373044SShuhei Matsumoto 	struct lcore_thread *lthread;
18928e373044SShuhei Matsumoto 
18938e373044SShuhei Matsumoto 	TAILQ_FOREACH(lthread, &g_lcore_thread_list, link) {
18948e373044SShuhei Matsumoto 		if (lthread->lcore == lcore) {
18958e373044SShuhei Matsumoto 			return lthread->thread;
18968e373044SShuhei Matsumoto 		}
18978e373044SShuhei Matsumoto 	}
18988e373044SShuhei Matsumoto 
18998e373044SShuhei Matsumoto 	return NULL;
19008e373044SShuhei Matsumoto }
19018e373044SShuhei Matsumoto 
190229784f35SKrzysztof Karas static void
190329784f35SKrzysztof Karas bdevperf_construct_jobs(void)
190429784f35SKrzysztof Karas {
190529784f35SKrzysztof Karas 	char filename[BDEVPERF_CONFIG_MAX_FILENAME];
190629784f35SKrzysztof Karas 	struct spdk_thread *thread;
190729784f35SKrzysztof Karas 	struct job_config *config;
190829784f35SKrzysztof Karas 	struct spdk_bdev *bdev;
190929784f35SKrzysztof Karas 	const char *filenames;
191029784f35SKrzysztof Karas 	int rc;
191129784f35SKrzysztof Karas 
191229784f35SKrzysztof Karas 	TAILQ_FOREACH(config, &job_config_list, link) {
191329784f35SKrzysztof Karas 		filenames = config->filename;
191429784f35SKrzysztof Karas 
19158e373044SShuhei Matsumoto 		if (!g_one_thread_per_lcore) {
191629784f35SKrzysztof Karas 			thread = construct_job_thread(&config->cpumask, config->name);
19178e373044SShuhei Matsumoto 		} else {
19188e373044SShuhei Matsumoto 			thread = get_lcore_thread(config->lcore);
19198e373044SShuhei Matsumoto 		}
192029784f35SKrzysztof Karas 		assert(thread);
192129784f35SKrzysztof Karas 
192229784f35SKrzysztof Karas 		while (filenames) {
192329784f35SKrzysztof Karas 			filenames = config_filename_next(filenames, filename);
192429784f35SKrzysztof Karas 			if (strlen(filename) == 0) {
192529784f35SKrzysztof Karas 				break;
192629784f35SKrzysztof Karas 			}
192729784f35SKrzysztof Karas 
192829784f35SKrzysztof Karas 			bdev = spdk_bdev_get_by_name(filename);
192929784f35SKrzysztof Karas 			if (!bdev) {
193029784f35SKrzysztof Karas 				fprintf(stderr, "Unable to find bdev '%s'\n", filename);
193129784f35SKrzysztof Karas 				g_run_rc = -EINVAL;
193229784f35SKrzysztof Karas 				return;
193329784f35SKrzysztof Karas 			}
193429784f35SKrzysztof Karas 
193529784f35SKrzysztof Karas 			rc = bdevperf_construct_job(bdev, config, thread);
193629784f35SKrzysztof Karas 			if (rc < 0) {
193729784f35SKrzysztof Karas 				g_run_rc = rc;
193829784f35SKrzysztof Karas 				return;
193929784f35SKrzysztof Karas 			}
194029784f35SKrzysztof Karas 		}
194129784f35SKrzysztof Karas 	}
194229784f35SKrzysztof Karas }
194329784f35SKrzysztof Karas 
194429784f35SKrzysztof Karas static int
194529784f35SKrzysztof Karas make_cli_job_config(const char *filename, int64_t offset, uint64_t range)
194629784f35SKrzysztof Karas {
194729784f35SKrzysztof Karas 	struct job_config *config = calloc(1, sizeof(*config));
194829784f35SKrzysztof Karas 
194929784f35SKrzysztof Karas 	if (config == NULL) {
195029784f35SKrzysztof Karas 		fprintf(stderr, "Unable to allocate memory for job config\n");
195129784f35SKrzysztof Karas 		return -ENOMEM;
195229784f35SKrzysztof Karas 	}
195329784f35SKrzysztof Karas 
195429784f35SKrzysztof Karas 	config->name = filename;
195529784f35SKrzysztof Karas 	config->filename = filename;
19568e373044SShuhei Matsumoto 	config->lcore = _get_next_core();
195729784f35SKrzysztof Karas 	spdk_cpuset_zero(&config->cpumask);
19588e373044SShuhei Matsumoto 	spdk_cpuset_set_cpu(&config->cpumask, config->lcore, true);
195929784f35SKrzysztof Karas 	config->bs = g_io_size;
196029784f35SKrzysztof Karas 	config->iodepth = g_queue_depth;
196129784f35SKrzysztof Karas 	config->rwmixread = g_rw_percentage;
196229784f35SKrzysztof Karas 	config->offset = offset;
196329784f35SKrzysztof Karas 	config->length = range;
196429784f35SKrzysztof Karas 	config->rw = parse_rw(g_workload_type, BDEVPERF_CONFIG_ERROR);
196529784f35SKrzysztof Karas 	if ((int)config->rw == BDEVPERF_CONFIG_ERROR) {
1966ef9168dcSGangCao 		free(config);
196729784f35SKrzysztof Karas 		return -EINVAL;
196829784f35SKrzysztof Karas 	}
196929784f35SKrzysztof Karas 
197029784f35SKrzysztof Karas 	TAILQ_INSERT_TAIL(&job_config_list, config, link);
197129784f35SKrzysztof Karas 	return 0;
197229784f35SKrzysztof Karas }
197329784f35SKrzysztof Karas 
19749ef9d4bdSShuhei Matsumoto static int
197556479486SShuhei Matsumoto bdevperf_construct_multithread_job_config(void *ctx, struct spdk_bdev *bdev)
19769ef9d4bdSShuhei Matsumoto {
197756479486SShuhei Matsumoto 	uint32_t *num_cores = ctx;
19789ef9d4bdSShuhei Matsumoto 	uint32_t i;
19799ef9d4bdSShuhei Matsumoto 	uint64_t blocks_per_job;
19809ef9d4bdSShuhei Matsumoto 	int64_t offset;
19819ef9d4bdSShuhei Matsumoto 	int rc;
19829ef9d4bdSShuhei Matsumoto 
198356479486SShuhei Matsumoto 	blocks_per_job = spdk_bdev_get_num_blocks(bdev) / *num_cores;
19849ef9d4bdSShuhei Matsumoto 	offset = 0;
19859ef9d4bdSShuhei Matsumoto 
19869ef9d4bdSShuhei Matsumoto 	SPDK_ENV_FOREACH_CORE(i) {
19879ef9d4bdSShuhei Matsumoto 		rc = make_cli_job_config(spdk_bdev_get_name(bdev), offset, blocks_per_job);
19889ef9d4bdSShuhei Matsumoto 		if (rc) {
19899ef9d4bdSShuhei Matsumoto 			return rc;
19909ef9d4bdSShuhei Matsumoto 		}
19919ef9d4bdSShuhei Matsumoto 
19929ef9d4bdSShuhei Matsumoto 		offset += blocks_per_job;
19939ef9d4bdSShuhei Matsumoto 	}
19949ef9d4bdSShuhei Matsumoto 
19959ef9d4bdSShuhei Matsumoto 	return 0;
19969ef9d4bdSShuhei Matsumoto }
19979ef9d4bdSShuhei Matsumoto 
199829784f35SKrzysztof Karas static void
199929784f35SKrzysztof Karas bdevperf_construct_multithread_job_configs(void)
200029784f35SKrzysztof Karas {
200129784f35SKrzysztof Karas 	struct spdk_bdev *bdev;
200229784f35SKrzysztof Karas 	uint32_t i;
200329784f35SKrzysztof Karas 	uint32_t num_cores;
200429784f35SKrzysztof Karas 
200529784f35SKrzysztof Karas 	num_cores = 0;
200629784f35SKrzysztof Karas 	SPDK_ENV_FOREACH_CORE(i) {
200729784f35SKrzysztof Karas 		num_cores++;
200829784f35SKrzysztof Karas 	}
200929784f35SKrzysztof Karas 
201029784f35SKrzysztof Karas 	if (num_cores == 0) {
201129784f35SKrzysztof Karas 		g_run_rc = -EINVAL;
201229784f35SKrzysztof Karas 		return;
201329784f35SKrzysztof Karas 	}
201429784f35SKrzysztof Karas 
201529784f35SKrzysztof Karas 	if (g_job_bdev_name != NULL) {
201629784f35SKrzysztof Karas 		bdev = spdk_bdev_get_by_name(g_job_bdev_name);
201729784f35SKrzysztof Karas 		if (!bdev) {
201829784f35SKrzysztof Karas 			fprintf(stderr, "Unable to find bdev '%s'\n", g_job_bdev_name);
201929784f35SKrzysztof Karas 			return;
202029784f35SKrzysztof Karas 		}
202156479486SShuhei Matsumoto 		g_run_rc = bdevperf_construct_multithread_job_config(&num_cores, bdev);
202229784f35SKrzysztof Karas 	} else {
202356479486SShuhei Matsumoto 		g_run_rc = spdk_for_each_bdev_leaf(&num_cores, bdevperf_construct_multithread_job_config);
202429784f35SKrzysztof Karas 	}
202556479486SShuhei Matsumoto 
202629784f35SKrzysztof Karas }
202756479486SShuhei Matsumoto 
202856479486SShuhei Matsumoto static int
202956479486SShuhei Matsumoto bdevperf_construct_job_config(void *ctx, struct spdk_bdev *bdev)
203056479486SShuhei Matsumoto {
203156479486SShuhei Matsumoto 	/* Construct the job */
203256479486SShuhei Matsumoto 	return make_cli_job_config(spdk_bdev_get_name(bdev), 0, 0);
203329784f35SKrzysztof Karas }
203429784f35SKrzysztof Karas 
203529784f35SKrzysztof Karas static void
20368e373044SShuhei Matsumoto create_lcore_thread(uint32_t lcore)
20378e373044SShuhei Matsumoto {
20388e373044SShuhei Matsumoto 	struct lcore_thread *lthread;
20398e373044SShuhei Matsumoto 	struct spdk_cpuset cpumask = {};
20408e373044SShuhei Matsumoto 	char name[32];
20418e373044SShuhei Matsumoto 
20428e373044SShuhei Matsumoto 	lthread = calloc(1, sizeof(*lthread));
20438e373044SShuhei Matsumoto 	assert(lthread != NULL);
20448e373044SShuhei Matsumoto 
20458e373044SShuhei Matsumoto 	lthread->lcore = lcore;
20468e373044SShuhei Matsumoto 
20478e373044SShuhei Matsumoto 	snprintf(name, sizeof(name), "lcore_%u", lcore);
20488e373044SShuhei Matsumoto 	spdk_cpuset_set_cpu(&cpumask, lcore, true);
20498e373044SShuhei Matsumoto 
20508e373044SShuhei Matsumoto 	lthread->thread = spdk_thread_create(name, &cpumask);
20518e373044SShuhei Matsumoto 	assert(lthread->thread != NULL);
20528e373044SShuhei Matsumoto 
20538e373044SShuhei Matsumoto 	TAILQ_INSERT_TAIL(&g_lcore_thread_list, lthread, link);
20548e373044SShuhei Matsumoto }
20558e373044SShuhei Matsumoto 
20568e373044SShuhei Matsumoto static void
205729784f35SKrzysztof Karas bdevperf_construct_job_configs(void)
205829784f35SKrzysztof Karas {
205929784f35SKrzysztof Karas 	struct spdk_bdev *bdev;
20608e373044SShuhei Matsumoto 	uint32_t i;
206129784f35SKrzysztof Karas 
206229784f35SKrzysztof Karas 	/* There are three different modes for allocating jobs. Standard mode
206329784f35SKrzysztof Karas 	 * (the default) creates one spdk_thread per bdev and runs the I/O job there.
206429784f35SKrzysztof Karas 	 *
206529784f35SKrzysztof Karas 	 * The -C flag places bdevperf into "multithread" mode, meaning it creates
206629784f35SKrzysztof Karas 	 * one spdk_thread per bdev PER CORE, and runs a copy of the job on each.
206729784f35SKrzysztof Karas 	 * This runs multiple threads per bdev, effectively.
206829784f35SKrzysztof Karas 	 *
206929784f35SKrzysztof Karas 	 * The -j flag implies "FIO" mode which tries to mimic semantic of FIO jobs.
207029784f35SKrzysztof Karas 	 * In "FIO" mode, threads are spawned per-job instead of per-bdev.
207129784f35SKrzysztof Karas 	 * Each FIO job can be individually parameterized by filename, cpu mask, etc,
207229784f35SKrzysztof Karas 	 * which is different from other modes in that they only support global options.
20738e373044SShuhei Matsumoto 	 *
20748e373044SShuhei Matsumoto 	 * Both for standard mode and "multithread" mode, if the -E flag is specified,
20758e373044SShuhei Matsumoto 	 * it creates one spdk_thread PER CORE. On each core, one spdk_thread is shared by
20768e373044SShuhei Matsumoto 	 * multiple jobs.
207729784f35SKrzysztof Karas 	 */
207829784f35SKrzysztof Karas 
207929784f35SKrzysztof Karas 	if (g_bdevperf_conf) {
208029784f35SKrzysztof Karas 		goto end;
208129784f35SKrzysztof Karas 	}
208229784f35SKrzysztof Karas 
20838e373044SShuhei Matsumoto 	if (g_one_thread_per_lcore) {
20848e373044SShuhei Matsumoto 		SPDK_ENV_FOREACH_CORE(i) {
20858e373044SShuhei Matsumoto 			create_lcore_thread(i);
20868e373044SShuhei Matsumoto 		}
20878e373044SShuhei Matsumoto 	}
20888e373044SShuhei Matsumoto 
20898e373044SShuhei Matsumoto 	if (g_multithread_mode) {
20908e373044SShuhei Matsumoto 		bdevperf_construct_multithread_job_configs();
20918e373044SShuhei Matsumoto 	} else if (g_job_bdev_name != NULL) {
209229784f35SKrzysztof Karas 		bdev = spdk_bdev_get_by_name(g_job_bdev_name);
209329784f35SKrzysztof Karas 		if (bdev) {
209429784f35SKrzysztof Karas 			/* Construct the job */
209529784f35SKrzysztof Karas 			g_run_rc = make_cli_job_config(g_job_bdev_name, 0, 0);
209629784f35SKrzysztof Karas 		} else {
209729784f35SKrzysztof Karas 			fprintf(stderr, "Unable to find bdev '%s'\n", g_job_bdev_name);
209829784f35SKrzysztof Karas 		}
209929784f35SKrzysztof Karas 	} else {
210056479486SShuhei Matsumoto 		g_run_rc = spdk_for_each_bdev_leaf(NULL, bdevperf_construct_job_config);
210129784f35SKrzysztof Karas 	}
210229784f35SKrzysztof Karas 
210329784f35SKrzysztof Karas end:
210429784f35SKrzysztof Karas 	/* Increment initial construct_jobs count so that it will never reach 0 in the middle
210529784f35SKrzysztof Karas 	 * of iteration.
210629784f35SKrzysztof Karas 	 */
210729784f35SKrzysztof Karas 	g_construct_job_count = 1;
210829784f35SKrzysztof Karas 
210929784f35SKrzysztof Karas 	if (g_run_rc == 0) {
211029784f35SKrzysztof Karas 		bdevperf_construct_jobs();
211129784f35SKrzysztof Karas 	}
211229784f35SKrzysztof Karas 
211329784f35SKrzysztof Karas 	_bdevperf_construct_job_done(NULL);
211429784f35SKrzysztof Karas }
211529784f35SKrzysztof Karas 
211629784f35SKrzysztof Karas static int
211729784f35SKrzysztof Karas parse_uint_option(struct spdk_conf_section *s, const char *name, int def)
211829784f35SKrzysztof Karas {
211929784f35SKrzysztof Karas 	const char *job_name;
212029784f35SKrzysztof Karas 	int tmp;
212129784f35SKrzysztof Karas 
212229784f35SKrzysztof Karas 	tmp = spdk_conf_section_get_intval(s, name);
212329784f35SKrzysztof Karas 	if (tmp == -1) {
212429784f35SKrzysztof Karas 		/* Field was not found. Check default value
212529784f35SKrzysztof Karas 		 * In [global] section it is ok to have undefined values
212629784f35SKrzysztof Karas 		 * but for other sections it is not ok */
212729784f35SKrzysztof Karas 		if (def == BDEVPERF_CONFIG_UNDEFINED) {
212829784f35SKrzysztof Karas 			job_name = spdk_conf_section_get_name(s);
212929784f35SKrzysztof Karas 			if (strcmp(job_name, "global") == 0) {
213029784f35SKrzysztof Karas 				return def;
213129784f35SKrzysztof Karas 			}
213229784f35SKrzysztof Karas 
213329784f35SKrzysztof Karas 			fprintf(stderr,
213429784f35SKrzysztof Karas 				"Job '%s' has no '%s' assigned\n",
213529784f35SKrzysztof Karas 				job_name, name);
213629784f35SKrzysztof Karas 			return BDEVPERF_CONFIG_ERROR;
213729784f35SKrzysztof Karas 		}
213829784f35SKrzysztof Karas 		return def;
213929784f35SKrzysztof Karas 	}
214029784f35SKrzysztof Karas 
214129784f35SKrzysztof Karas 	/* NOTE: get_intval returns nonnegative on success */
214229784f35SKrzysztof Karas 	if (tmp < 0) {
214329784f35SKrzysztof Karas 		fprintf(stderr, "Job '%s' has bad '%s' value.\n",
214429784f35SKrzysztof Karas 			spdk_conf_section_get_name(s), name);
214529784f35SKrzysztof Karas 		return BDEVPERF_CONFIG_ERROR;
214629784f35SKrzysztof Karas 	}
214729784f35SKrzysztof Karas 
214829784f35SKrzysztof Karas 	return tmp;
214929784f35SKrzysztof Karas }
215029784f35SKrzysztof Karas 
215129784f35SKrzysztof Karas /* CLI arguments override parameters for global sections */
215229784f35SKrzysztof Karas static void
215329784f35SKrzysztof Karas config_set_cli_args(struct job_config *config)
215429784f35SKrzysztof Karas {
215529784f35SKrzysztof Karas 	if (g_job_bdev_name) {
215629784f35SKrzysztof Karas 		config->filename = g_job_bdev_name;
215729784f35SKrzysztof Karas 	}
215829784f35SKrzysztof Karas 	if (g_io_size > 0) {
215929784f35SKrzysztof Karas 		config->bs = g_io_size;
216029784f35SKrzysztof Karas 	}
216129784f35SKrzysztof Karas 	if (g_queue_depth > 0) {
216229784f35SKrzysztof Karas 		config->iodepth = g_queue_depth;
216329784f35SKrzysztof Karas 	}
216429784f35SKrzysztof Karas 	if (g_rw_percentage > 0) {
216529784f35SKrzysztof Karas 		config->rwmixread = g_rw_percentage;
216629784f35SKrzysztof Karas 	}
216729784f35SKrzysztof Karas 	if (g_workload_type) {
216829784f35SKrzysztof Karas 		config->rw = parse_rw(g_workload_type, config->rw);
216929784f35SKrzysztof Karas 	}
217029784f35SKrzysztof Karas }
217129784f35SKrzysztof Karas 
217229784f35SKrzysztof Karas static int
217329784f35SKrzysztof Karas read_job_config(void)
217429784f35SKrzysztof Karas {
217529784f35SKrzysztof Karas 	struct job_config global_default_config;
217629784f35SKrzysztof Karas 	struct job_config global_config;
217729784f35SKrzysztof Karas 	struct spdk_conf_section *s;
217829784f35SKrzysztof Karas 	struct job_config *config;
217929784f35SKrzysztof Karas 	const char *cpumask;
218029784f35SKrzysztof Karas 	const char *rw;
218129784f35SKrzysztof Karas 	bool is_global;
218229784f35SKrzysztof Karas 	int n = 0;
218329784f35SKrzysztof Karas 	int val;
218429784f35SKrzysztof Karas 
218529784f35SKrzysztof Karas 	if (g_bdevperf_conf_file == NULL) {
218629784f35SKrzysztof Karas 		return 0;
218729784f35SKrzysztof Karas 	}
218829784f35SKrzysztof Karas 
218929784f35SKrzysztof Karas 	g_bdevperf_conf = spdk_conf_allocate();
219029784f35SKrzysztof Karas 	if (g_bdevperf_conf == NULL) {
219129784f35SKrzysztof Karas 		fprintf(stderr, "Could not allocate job config structure\n");
219229784f35SKrzysztof Karas 		return 1;
219329784f35SKrzysztof Karas 	}
219429784f35SKrzysztof Karas 
219529784f35SKrzysztof Karas 	spdk_conf_disable_sections_merge(g_bdevperf_conf);
219629784f35SKrzysztof Karas 	if (spdk_conf_read(g_bdevperf_conf, g_bdevperf_conf_file)) {
219729784f35SKrzysztof Karas 		fprintf(stderr, "Invalid job config");
219829784f35SKrzysztof Karas 		return 1;
219929784f35SKrzysztof Karas 	}
220029784f35SKrzysztof Karas 
220129784f35SKrzysztof Karas 	/* Initialize global defaults */
220229784f35SKrzysztof Karas 	global_default_config.filename = NULL;
220329784f35SKrzysztof Karas 	/* Zero mask is the same as g_all_cpuset
220429784f35SKrzysztof Karas 	 * The g_all_cpuset is not initialized yet,
220529784f35SKrzysztof Karas 	 * so use zero mask as the default instead */
220629784f35SKrzysztof Karas 	spdk_cpuset_zero(&global_default_config.cpumask);
220729784f35SKrzysztof Karas 	global_default_config.bs = BDEVPERF_CONFIG_UNDEFINED;
220829784f35SKrzysztof Karas 	global_default_config.iodepth = BDEVPERF_CONFIG_UNDEFINED;
220929784f35SKrzysztof Karas 	/* bdevperf has no default for -M option but in FIO the default is 50 */
221029784f35SKrzysztof Karas 	global_default_config.rwmixread = 50;
221129784f35SKrzysztof Karas 	global_default_config.offset = 0;
221229784f35SKrzysztof Karas 	/* length 0 means 100% */
221329784f35SKrzysztof Karas 	global_default_config.length = 0;
221429784f35SKrzysztof Karas 	global_default_config.rw = BDEVPERF_CONFIG_UNDEFINED;
221529784f35SKrzysztof Karas 	config_set_cli_args(&global_default_config);
221629784f35SKrzysztof Karas 
221729784f35SKrzysztof Karas 	if ((int)global_default_config.rw == BDEVPERF_CONFIG_ERROR) {
221829784f35SKrzysztof Karas 		return 1;
221929784f35SKrzysztof Karas 	}
222029784f35SKrzysztof Karas 
222129784f35SKrzysztof Karas 	/* There is only a single instance of global job_config
222229784f35SKrzysztof Karas 	 * We just reset its value when we encounter new [global] section */
222329784f35SKrzysztof Karas 	global_config = global_default_config;
222429784f35SKrzysztof Karas 
222529784f35SKrzysztof Karas 	for (s = spdk_conf_first_section(g_bdevperf_conf);
222629784f35SKrzysztof Karas 	     s != NULL;
222729784f35SKrzysztof Karas 	     s = spdk_conf_next_section(s)) {
222829784f35SKrzysztof Karas 		config = calloc(1, sizeof(*config));
222929784f35SKrzysztof Karas 		if (config == NULL) {
223029784f35SKrzysztof Karas 			fprintf(stderr, "Unable to allocate memory for job config\n");
223129784f35SKrzysztof Karas 			return 1;
223229784f35SKrzysztof Karas 		}
223329784f35SKrzysztof Karas 
223429784f35SKrzysztof Karas 		config->name = spdk_conf_section_get_name(s);
223529784f35SKrzysztof Karas 		is_global = strcmp(config->name, "global") == 0;
223629784f35SKrzysztof Karas 
223729784f35SKrzysztof Karas 		if (is_global) {
223829784f35SKrzysztof Karas 			global_config = global_default_config;
223929784f35SKrzysztof Karas 		}
224029784f35SKrzysztof Karas 
224129784f35SKrzysztof Karas 		config->filename = spdk_conf_section_get_val(s, "filename");
224229784f35SKrzysztof Karas 		if (config->filename == NULL) {
224329784f35SKrzysztof Karas 			config->filename = global_config.filename;
224429784f35SKrzysztof Karas 		}
224529784f35SKrzysztof Karas 		if (!is_global) {
224629784f35SKrzysztof Karas 			if (config->filename == NULL) {
224729784f35SKrzysztof Karas 				fprintf(stderr, "Job '%s' expects 'filename' parameter\n", config->name);
224829784f35SKrzysztof Karas 				goto error;
224929784f35SKrzysztof Karas 			} else if (strnlen(config->filename, BDEVPERF_CONFIG_MAX_FILENAME)
225029784f35SKrzysztof Karas 				   >= BDEVPERF_CONFIG_MAX_FILENAME) {
225129784f35SKrzysztof Karas 				fprintf(stderr,
225229784f35SKrzysztof Karas 					"filename for '%s' job is too long. Max length is %d\n",
225329784f35SKrzysztof Karas 					config->name, BDEVPERF_CONFIG_MAX_FILENAME);
225429784f35SKrzysztof Karas 				goto error;
225529784f35SKrzysztof Karas 			}
225629784f35SKrzysztof Karas 		}
225729784f35SKrzysztof Karas 
225829784f35SKrzysztof Karas 		cpumask = spdk_conf_section_get_val(s, "cpumask");
225929784f35SKrzysztof Karas 		if (cpumask == NULL) {
226029784f35SKrzysztof Karas 			config->cpumask = global_config.cpumask;
226129784f35SKrzysztof Karas 		} else if (spdk_cpuset_parse(&config->cpumask, cpumask)) {
226229784f35SKrzysztof Karas 			fprintf(stderr, "Job '%s' has bad 'cpumask' value\n", config->name);
226329784f35SKrzysztof Karas 			goto error;
226429784f35SKrzysztof Karas 		}
226529784f35SKrzysztof Karas 
226629784f35SKrzysztof Karas 		config->bs = parse_uint_option(s, "bs", global_config.bs);
226729784f35SKrzysztof Karas 		if (config->bs == BDEVPERF_CONFIG_ERROR) {
226829784f35SKrzysztof Karas 			goto error;
226929784f35SKrzysztof Karas 		} else if (config->bs == 0) {
227029784f35SKrzysztof Karas 			fprintf(stderr, "'bs' of job '%s' must be greater than 0\n", config->name);
227129784f35SKrzysztof Karas 			goto error;
227229784f35SKrzysztof Karas 		}
227329784f35SKrzysztof Karas 
227429784f35SKrzysztof Karas 		config->iodepth = parse_uint_option(s, "iodepth", global_config.iodepth);
227529784f35SKrzysztof Karas 		if (config->iodepth == BDEVPERF_CONFIG_ERROR) {
227629784f35SKrzysztof Karas 			goto error;
227729784f35SKrzysztof Karas 		} else if (config->iodepth == 0) {
227829784f35SKrzysztof Karas 			fprintf(stderr,
227929784f35SKrzysztof Karas 				"'iodepth' of job '%s' must be greater than 0\n",
228029784f35SKrzysztof Karas 				config->name);
228129784f35SKrzysztof Karas 			goto error;
228229784f35SKrzysztof Karas 		}
228329784f35SKrzysztof Karas 
228429784f35SKrzysztof Karas 		config->rwmixread = parse_uint_option(s, "rwmixread", global_config.rwmixread);
228529784f35SKrzysztof Karas 		if (config->rwmixread == BDEVPERF_CONFIG_ERROR) {
228629784f35SKrzysztof Karas 			goto error;
228729784f35SKrzysztof Karas 		} else if (config->rwmixread > 100) {
228829784f35SKrzysztof Karas 			fprintf(stderr,
228929784f35SKrzysztof Karas 				"'rwmixread' value of '%s' job is not in 0-100 range\n",
229029784f35SKrzysztof Karas 				config->name);
229129784f35SKrzysztof Karas 			goto error;
229229784f35SKrzysztof Karas 		}
229329784f35SKrzysztof Karas 
229429784f35SKrzysztof Karas 		config->offset = parse_uint_option(s, "offset", global_config.offset);
229529784f35SKrzysztof Karas 		if (config->offset == BDEVPERF_CONFIG_ERROR) {
229629784f35SKrzysztof Karas 			goto error;
229729784f35SKrzysztof Karas 		}
229829784f35SKrzysztof Karas 
229929784f35SKrzysztof Karas 		val = parse_uint_option(s, "length", global_config.length);
230029784f35SKrzysztof Karas 		if (val == BDEVPERF_CONFIG_ERROR) {
230129784f35SKrzysztof Karas 			goto error;
230229784f35SKrzysztof Karas 		}
230329784f35SKrzysztof Karas 		config->length = val;
230429784f35SKrzysztof Karas 
230529784f35SKrzysztof Karas 		rw = spdk_conf_section_get_val(s, "rw");
230629784f35SKrzysztof Karas 		config->rw = parse_rw(rw, global_config.rw);
230729784f35SKrzysztof Karas 		if ((int)config->rw == BDEVPERF_CONFIG_ERROR) {
230829784f35SKrzysztof Karas 			fprintf(stderr, "Job '%s' has bad 'rw' value\n", config->name);
230929784f35SKrzysztof Karas 			goto error;
231029784f35SKrzysztof Karas 		} else if (!is_global && (int)config->rw == BDEVPERF_CONFIG_UNDEFINED) {
231129784f35SKrzysztof Karas 			fprintf(stderr, "Job '%s' has no 'rw' assigned\n", config->name);
231229784f35SKrzysztof Karas 			goto error;
231329784f35SKrzysztof Karas 		}
231429784f35SKrzysztof Karas 
231529784f35SKrzysztof Karas 		if (is_global) {
231629784f35SKrzysztof Karas 			config_set_cli_args(config);
231729784f35SKrzysztof Karas 			global_config = *config;
231829784f35SKrzysztof Karas 			free(config);
231929784f35SKrzysztof Karas 		} else {
232029784f35SKrzysztof Karas 			TAILQ_INSERT_TAIL(&job_config_list, config, link);
232129784f35SKrzysztof Karas 			n++;
232229784f35SKrzysztof Karas 		}
232329784f35SKrzysztof Karas 	}
232429784f35SKrzysztof Karas 
232529784f35SKrzysztof Karas 	printf("Using job config with %d jobs\n", n);
232629784f35SKrzysztof Karas 	return 0;
232729784f35SKrzysztof Karas error:
232829784f35SKrzysztof Karas 	free(config);
232929784f35SKrzysztof Karas 	return 1;
233029784f35SKrzysztof Karas }
233129784f35SKrzysztof Karas 
233229784f35SKrzysztof Karas static void
233329784f35SKrzysztof Karas bdevperf_run(void *arg1)
233429784f35SKrzysztof Karas {
233529784f35SKrzysztof Karas 	uint32_t i;
233629784f35SKrzysztof Karas 
233729784f35SKrzysztof Karas 	g_main_thread = spdk_get_thread();
233829784f35SKrzysztof Karas 
233929784f35SKrzysztof Karas 	spdk_cpuset_zero(&g_all_cpuset);
234029784f35SKrzysztof Karas 	SPDK_ENV_FOREACH_CORE(i) {
234129784f35SKrzysztof Karas 		spdk_cpuset_set_cpu(&g_all_cpuset, i, true);
234229784f35SKrzysztof Karas 	}
234329784f35SKrzysztof Karas 
234429784f35SKrzysztof Karas 	if (g_wait_for_tests) {
234529784f35SKrzysztof Karas 		/* Do not perform any tests until RPC is received */
234629784f35SKrzysztof Karas 		return;
234729784f35SKrzysztof Karas 	}
234829784f35SKrzysztof Karas 
234929784f35SKrzysztof Karas 	bdevperf_construct_job_configs();
235029784f35SKrzysztof Karas }
235129784f35SKrzysztof Karas 
235229784f35SKrzysztof Karas static void
2353ed4b89aaSGangCao rpc_perform_tests_reset(void)
2354ed4b89aaSGangCao {
2355ed4b89aaSGangCao 	/* Reset g_run_rc to 0 for the next test run. */
2356ed4b89aaSGangCao 	g_run_rc = 0;
2357ed4b89aaSGangCao 
2358ed4b89aaSGangCao 	/* Reset g_stats to 0 for the next test run. */
2359ed4b89aaSGangCao 	memset(&g_stats, 0, sizeof(g_stats));
2360ed4b89aaSGangCao 
2361ed4b89aaSGangCao 	/* Reset g_show_performance_period_num to 0 for the next test run. */
2362ed4b89aaSGangCao 	g_show_performance_period_num = 0;
2363ed4b89aaSGangCao }
2364ed4b89aaSGangCao 
2365ed4b89aaSGangCao static void
236629784f35SKrzysztof Karas rpc_perform_tests_cb(void)
236729784f35SKrzysztof Karas {
236829784f35SKrzysztof Karas 	struct spdk_json_write_ctx *w;
236929784f35SKrzysztof Karas 	struct spdk_jsonrpc_request *request = g_request;
237029784f35SKrzysztof Karas 
237129784f35SKrzysztof Karas 	g_request = NULL;
237229784f35SKrzysztof Karas 
237329784f35SKrzysztof Karas 	if (g_run_rc == 0) {
237429784f35SKrzysztof Karas 		w = spdk_jsonrpc_begin_result(request);
237529784f35SKrzysztof Karas 		spdk_json_write_uint32(w, g_run_rc);
237629784f35SKrzysztof Karas 		spdk_jsonrpc_end_result(request, w);
237729784f35SKrzysztof Karas 	} else {
237829784f35SKrzysztof Karas 		spdk_jsonrpc_send_error_response_fmt(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR,
237929784f35SKrzysztof Karas 						     "bdevperf failed with error %s", spdk_strerror(-g_run_rc));
238029784f35SKrzysztof Karas 	}
238129784f35SKrzysztof Karas 
2382ed4b89aaSGangCao 	rpc_perform_tests_reset();
238329784f35SKrzysztof Karas }
238429784f35SKrzysztof Karas 
238529784f35SKrzysztof Karas static void
238629784f35SKrzysztof Karas rpc_perform_tests(struct spdk_jsonrpc_request *request, const struct spdk_json_val *params)
238729784f35SKrzysztof Karas {
238829784f35SKrzysztof Karas 	if (params != NULL) {
238929784f35SKrzysztof Karas 		spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS,
239029784f35SKrzysztof Karas 						 "perform_tests method requires no parameters");
239129784f35SKrzysztof Karas 		return;
239229784f35SKrzysztof Karas 	}
239329784f35SKrzysztof Karas 	if (g_request != NULL) {
239429784f35SKrzysztof Karas 		fprintf(stderr, "Another test is already in progress.\n");
239529784f35SKrzysztof Karas 		spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR,
239629784f35SKrzysztof Karas 						 spdk_strerror(-EINPROGRESS));
239729784f35SKrzysztof Karas 		return;
239829784f35SKrzysztof Karas 	}
239929784f35SKrzysztof Karas 	g_request = request;
240029784f35SKrzysztof Karas 
240129784f35SKrzysztof Karas 	/* Only construct job configs at the first test run.  */
240229784f35SKrzysztof Karas 	if (TAILQ_EMPTY(&job_config_list)) {
240329784f35SKrzysztof Karas 		bdevperf_construct_job_configs();
240429784f35SKrzysztof Karas 	} else {
240529784f35SKrzysztof Karas 		bdevperf_construct_jobs();
240629784f35SKrzysztof Karas 	}
240729784f35SKrzysztof Karas }
240829784f35SKrzysztof Karas SPDK_RPC_REGISTER("perform_tests", rpc_perform_tests, SPDK_RPC_RUNTIME)
240929784f35SKrzysztof Karas 
241029784f35SKrzysztof Karas static void
241129784f35SKrzysztof Karas _bdevperf_job_drain(void *ctx)
241229784f35SKrzysztof Karas {
241329784f35SKrzysztof Karas 	bdevperf_job_drain(ctx);
241429784f35SKrzysztof Karas }
241529784f35SKrzysztof Karas 
241629784f35SKrzysztof Karas static void
241729784f35SKrzysztof Karas spdk_bdevperf_shutdown_cb(void)
241829784f35SKrzysztof Karas {
241929784f35SKrzysztof Karas 	g_shutdown = true;
242029784f35SKrzysztof Karas 	struct bdevperf_job *job, *tmp;
242129784f35SKrzysztof Karas 
242229784f35SKrzysztof Karas 	if (g_bdevperf.running_jobs == 0) {
242329784f35SKrzysztof Karas 		bdevperf_test_done(NULL);
242429784f35SKrzysztof Karas 		return;
242529784f35SKrzysztof Karas 	}
242629784f35SKrzysztof Karas 
242729784f35SKrzysztof Karas 	/* Iterate jobs to stop all I/O */
242829784f35SKrzysztof Karas 	TAILQ_FOREACH_SAFE(job, &g_bdevperf.jobs, link, tmp) {
242929784f35SKrzysztof Karas 		spdk_thread_send_msg(job->thread, _bdevperf_job_drain, job);
243029784f35SKrzysztof Karas 	}
243129784f35SKrzysztof Karas }
243229784f35SKrzysztof Karas 
243329784f35SKrzysztof Karas static int
243429784f35SKrzysztof Karas bdevperf_parse_arg(int ch, char *arg)
243529784f35SKrzysztof Karas {
243629784f35SKrzysztof Karas 	long long tmp;
243729784f35SKrzysztof Karas 
243829784f35SKrzysztof Karas 	if (ch == 'w') {
243929784f35SKrzysztof Karas 		g_workload_type = optarg;
244029784f35SKrzysztof Karas 	} else if (ch == 'T') {
244129784f35SKrzysztof Karas 		g_job_bdev_name = optarg;
244229784f35SKrzysztof Karas 	} else if (ch == 'z') {
244329784f35SKrzysztof Karas 		g_wait_for_tests = true;
244429784f35SKrzysztof Karas 	} else if (ch == 'Z') {
244529784f35SKrzysztof Karas 		g_zcopy = true;
244629784f35SKrzysztof Karas 	} else if (ch == 'X') {
244729784f35SKrzysztof Karas 		g_abort = true;
244829784f35SKrzysztof Karas 	} else if (ch == 'C') {
244929784f35SKrzysztof Karas 		g_multithread_mode = true;
245029784f35SKrzysztof Karas 	} else if (ch == 'f') {
245129784f35SKrzysztof Karas 		g_continue_on_failure = true;
245229784f35SKrzysztof Karas 	} else if (ch == 'j') {
245329784f35SKrzysztof Karas 		g_bdevperf_conf_file = optarg;
245429784f35SKrzysztof Karas 	} else if (ch == 'F') {
245529784f35SKrzysztof Karas 		char *endptr;
245629784f35SKrzysztof Karas 
245729784f35SKrzysztof Karas 		errno = 0;
245829784f35SKrzysztof Karas 		g_zipf_theta = strtod(optarg, &endptr);
245929784f35SKrzysztof Karas 		if (errno || optarg == endptr || g_zipf_theta < 0) {
246029784f35SKrzysztof Karas 			fprintf(stderr, "Illegal zipf theta value %s\n", optarg);
246129784f35SKrzysztof Karas 			return -EINVAL;
246229784f35SKrzysztof Karas 		}
2463723dd06eSRichael Zhuang 	} else if (ch == 'l') {
2464723dd06eSRichael Zhuang 		g_latency_display_level++;
2465d254a3b9SSlawomir Ptak 	} else if (ch == 'D') {
2466d254a3b9SSlawomir Ptak 		g_random_map = true;
24678e373044SShuhei Matsumoto 	} else if (ch == 'E') {
24688e373044SShuhei Matsumoto 		g_one_thread_per_lcore = true;
246929784f35SKrzysztof Karas 	} else {
247029784f35SKrzysztof Karas 		tmp = spdk_strtoll(optarg, 10);
247129784f35SKrzysztof Karas 		if (tmp < 0) {
247229784f35SKrzysztof Karas 			fprintf(stderr, "Parse failed for the option %c.\n", ch);
247329784f35SKrzysztof Karas 			return tmp;
247429784f35SKrzysztof Karas 		} else if (tmp >= INT_MAX) {
247529784f35SKrzysztof Karas 			fprintf(stderr, "Parsed option was too large %c.\n", ch);
247629784f35SKrzysztof Karas 			return -ERANGE;
247729784f35SKrzysztof Karas 		}
247829784f35SKrzysztof Karas 
247929784f35SKrzysztof Karas 		switch (ch) {
248029784f35SKrzysztof Karas 		case 'q':
248129784f35SKrzysztof Karas 			g_queue_depth = tmp;
248229784f35SKrzysztof Karas 			break;
248329784f35SKrzysztof Karas 		case 'o':
248429784f35SKrzysztof Karas 			g_io_size = tmp;
248529784f35SKrzysztof Karas 			break;
248629784f35SKrzysztof Karas 		case 't':
248729784f35SKrzysztof Karas 			g_time_in_sec = tmp;
248829784f35SKrzysztof Karas 			break;
248929784f35SKrzysztof Karas 		case 'k':
249029784f35SKrzysztof Karas 			g_timeout_in_sec = tmp;
249129784f35SKrzysztof Karas 			break;
249229784f35SKrzysztof Karas 		case 'M':
249329784f35SKrzysztof Karas 			g_rw_percentage = tmp;
249429784f35SKrzysztof Karas 			g_mix_specified = true;
249529784f35SKrzysztof Karas 			break;
249629784f35SKrzysztof Karas 		case 'P':
249729784f35SKrzysztof Karas 			g_show_performance_ema_period = tmp;
249829784f35SKrzysztof Karas 			break;
249929784f35SKrzysztof Karas 		case 'S':
250029784f35SKrzysztof Karas 			g_show_performance_real_time = 1;
2501045c781dSRichael Zhuang 			g_show_performance_period_in_usec = tmp * SPDK_SEC_TO_USEC;
250229784f35SKrzysztof Karas 			break;
250329784f35SKrzysztof Karas 		default:
250429784f35SKrzysztof Karas 			return -EINVAL;
250529784f35SKrzysztof Karas 		}
250629784f35SKrzysztof Karas 	}
250729784f35SKrzysztof Karas 	return 0;
250829784f35SKrzysztof Karas }
250929784f35SKrzysztof Karas 
251029784f35SKrzysztof Karas static void
251129784f35SKrzysztof Karas bdevperf_usage(void)
251229784f35SKrzysztof Karas {
251329784f35SKrzysztof Karas 	printf(" -q <depth>                io depth\n");
251429784f35SKrzysztof Karas 	printf(" -o <size>                 io size in bytes\n");
251529784f35SKrzysztof Karas 	printf(" -w <type>                 io pattern type, must be one of (read, write, randread, randwrite, rw, randrw, verify, reset, unmap, flush)\n");
251629784f35SKrzysztof Karas 	printf(" -t <time>                 time in seconds\n");
251729784f35SKrzysztof Karas 	printf(" -k <timeout>              timeout in seconds to detect starved I/O (default is 0 and disabled)\n");
251829784f35SKrzysztof Karas 	printf(" -M <percent>              rwmixread (100 for reads, 0 for writes)\n");
251929784f35SKrzysztof Karas 	printf(" -P <num>                  number of moving average period\n");
252029784f35SKrzysztof Karas 	printf("\t\t(If set to n, show weighted mean of the previous n IO/s in real time)\n");
252129784f35SKrzysztof Karas 	printf("\t\t(Formula: M = 2 / (n + 1), EMA[i+1] = IO/s * M + (1 - M) * EMA[i])\n");
252229784f35SKrzysztof Karas 	printf("\t\t(only valid with -S)\n");
252329784f35SKrzysztof Karas 	printf(" -S <period>               show performance result in real time every <period> seconds\n");
252429784f35SKrzysztof Karas 	printf(" -T <bdev>                 bdev to run against. Default: all available bdevs.\n");
252529784f35SKrzysztof Karas 	printf(" -f                        continue processing I/O even after failures\n");
252629784f35SKrzysztof Karas 	printf(" -F <zipf theta>           use zipf distribution for random I/O\n");
252729784f35SKrzysztof Karas 	printf(" -Z                        enable using zcopy bdev API for read or write I/O\n");
252829784f35SKrzysztof Karas 	printf(" -z                        start bdevperf, but wait for RPC to start tests\n");
252929784f35SKrzysztof Karas 	printf(" -X                        abort timed out I/O\n");
253029784f35SKrzysztof Karas 	printf(" -C                        enable every core to send I/Os to each bdev\n");
253129784f35SKrzysztof Karas 	printf(" -j <filename>             use job config file\n");
2532723dd06eSRichael Zhuang 	printf(" -l                        display latency histogram, default: disable. -l display summary, -ll display details\n");
2533d254a3b9SSlawomir Ptak 	printf(" -D                        use a random map for picking offsets not previously read or written (for all jobs)\n");
25348e373044SShuhei Matsumoto 	printf(" -E                        share per lcore thread among jobs. Available only if -j is not used.\n");
253529784f35SKrzysztof Karas }
253629784f35SKrzysztof Karas 
2537*a1f4f11bSShuhei Matsumoto static void
2538*a1f4f11bSShuhei Matsumoto bdevperf_fini(void)
2539*a1f4f11bSShuhei Matsumoto {
2540*a1f4f11bSShuhei Matsumoto 	free_job_config();
2541*a1f4f11bSShuhei Matsumoto }
2542*a1f4f11bSShuhei Matsumoto 
254329784f35SKrzysztof Karas static int
254429784f35SKrzysztof Karas verify_test_params(struct spdk_app_opts *opts)
254529784f35SKrzysztof Karas {
254629784f35SKrzysztof Karas 	/* When RPC is used for starting tests and
254729784f35SKrzysztof Karas 	 * no rpc_addr was configured for the app,
254829784f35SKrzysztof Karas 	 * use the default address. */
254929784f35SKrzysztof Karas 	if (g_wait_for_tests && opts->rpc_addr == NULL) {
255029784f35SKrzysztof Karas 		opts->rpc_addr = SPDK_DEFAULT_RPC_ADDR;
255129784f35SKrzysztof Karas 	}
255229784f35SKrzysztof Karas 
255329784f35SKrzysztof Karas 	if (!g_bdevperf_conf_file && g_queue_depth <= 0) {
255429784f35SKrzysztof Karas 		goto out;
255529784f35SKrzysztof Karas 	}
255629784f35SKrzysztof Karas 	if (!g_bdevperf_conf_file && g_io_size <= 0) {
255729784f35SKrzysztof Karas 		goto out;
255829784f35SKrzysztof Karas 	}
255929784f35SKrzysztof Karas 	if (!g_bdevperf_conf_file && !g_workload_type) {
256029784f35SKrzysztof Karas 		goto out;
256129784f35SKrzysztof Karas 	}
25628e373044SShuhei Matsumoto 	if (g_bdevperf_conf_file && g_one_thread_per_lcore) {
25638e373044SShuhei Matsumoto 		printf("If bdevperf's config file is used, per lcore thread cannot be used\n");
25648e373044SShuhei Matsumoto 		goto out;
25658e373044SShuhei Matsumoto 	}
256629784f35SKrzysztof Karas 	if (g_time_in_sec <= 0) {
256729784f35SKrzysztof Karas 		goto out;
256829784f35SKrzysztof Karas 	}
2569045c781dSRichael Zhuang 	g_time_in_usec = g_time_in_sec * SPDK_SEC_TO_USEC;
257029784f35SKrzysztof Karas 
257129784f35SKrzysztof Karas 	if (g_timeout_in_sec < 0) {
257229784f35SKrzysztof Karas 		goto out;
257329784f35SKrzysztof Karas 	}
257429784f35SKrzysztof Karas 
257529784f35SKrzysztof Karas 	if (g_abort && !g_timeout_in_sec) {
257629784f35SKrzysztof Karas 		printf("Timeout must be set for abort option, Ignoring g_abort\n");
257729784f35SKrzysztof Karas 	}
257829784f35SKrzysztof Karas 
257929784f35SKrzysztof Karas 	if (g_show_performance_ema_period > 0 &&
258029784f35SKrzysztof Karas 	    g_show_performance_real_time == 0) {
258129784f35SKrzysztof Karas 		fprintf(stderr, "-P option must be specified with -S option\n");
258229784f35SKrzysztof Karas 		return 1;
258329784f35SKrzysztof Karas 	}
258429784f35SKrzysztof Karas 
258529784f35SKrzysztof Karas 	if (g_io_size > SPDK_BDEV_LARGE_BUF_MAX_SIZE) {
258629784f35SKrzysztof Karas 		printf("I/O size of %d is greater than zero copy threshold (%d).\n",
258729784f35SKrzysztof Karas 		       g_io_size, SPDK_BDEV_LARGE_BUF_MAX_SIZE);
258829784f35SKrzysztof Karas 		printf("Zero copy mechanism will not be used.\n");
258929784f35SKrzysztof Karas 		g_zcopy = false;
259029784f35SKrzysztof Karas 	}
259129784f35SKrzysztof Karas 
259229784f35SKrzysztof Karas 	if (g_bdevperf_conf_file) {
259329784f35SKrzysztof Karas 		/* workload_type verification happens during config file parsing */
259429784f35SKrzysztof Karas 		return 0;
259529784f35SKrzysztof Karas 	}
259629784f35SKrzysztof Karas 
259729784f35SKrzysztof Karas 	if (!strcmp(g_workload_type, "verify") ||
259829784f35SKrzysztof Karas 	    !strcmp(g_workload_type, "reset")) {
259929784f35SKrzysztof Karas 		g_rw_percentage = 50;
260029784f35SKrzysztof Karas 		if (g_io_size > SPDK_BDEV_LARGE_BUF_MAX_SIZE) {
260129784f35SKrzysztof Karas 			fprintf(stderr, "Unable to exceed max I/O size of %d for verify. (%d provided).\n",
260229784f35SKrzysztof Karas 				SPDK_BDEV_LARGE_BUF_MAX_SIZE, g_io_size);
260329784f35SKrzysztof Karas 			return 1;
260429784f35SKrzysztof Karas 		}
260529784f35SKrzysztof Karas 		g_verify = true;
260629784f35SKrzysztof Karas 		if (!strcmp(g_workload_type, "reset")) {
260729784f35SKrzysztof Karas 			g_reset = true;
260829784f35SKrzysztof Karas 		}
260929784f35SKrzysztof Karas 	}
261029784f35SKrzysztof Karas 
261129784f35SKrzysztof Karas 	if (!strcmp(g_workload_type, "read") ||
261229784f35SKrzysztof Karas 	    !strcmp(g_workload_type, "randread") ||
261329784f35SKrzysztof Karas 	    !strcmp(g_workload_type, "write") ||
261429784f35SKrzysztof Karas 	    !strcmp(g_workload_type, "randwrite") ||
261529784f35SKrzysztof Karas 	    !strcmp(g_workload_type, "verify") ||
261629784f35SKrzysztof Karas 	    !strcmp(g_workload_type, "reset") ||
261729784f35SKrzysztof Karas 	    !strcmp(g_workload_type, "unmap") ||
261829784f35SKrzysztof Karas 	    !strcmp(g_workload_type, "write_zeroes") ||
261929784f35SKrzysztof Karas 	    !strcmp(g_workload_type, "flush")) {
262029784f35SKrzysztof Karas 		if (g_mix_specified) {
262129784f35SKrzysztof Karas 			fprintf(stderr, "Ignoring -M option... Please use -M option"
262229784f35SKrzysztof Karas 				" only when using rw or randrw.\n");
262329784f35SKrzysztof Karas 		}
262429784f35SKrzysztof Karas 	}
262529784f35SKrzysztof Karas 
262629784f35SKrzysztof Karas 	if (!strcmp(g_workload_type, "rw") ||
262729784f35SKrzysztof Karas 	    !strcmp(g_workload_type, "randrw")) {
262829784f35SKrzysztof Karas 		if (g_rw_percentage < 0 || g_rw_percentage > 100) {
262929784f35SKrzysztof Karas 			fprintf(stderr,
263029784f35SKrzysztof Karas 				"-M must be specified to value from 0 to 100 "
263129784f35SKrzysztof Karas 				"for rw or randrw.\n");
263229784f35SKrzysztof Karas 			return 1;
263329784f35SKrzysztof Karas 		}
263429784f35SKrzysztof Karas 	}
263529784f35SKrzysztof Karas 
2636d254a3b9SSlawomir Ptak 	if (strcmp(g_workload_type, "randread") &&
2637d254a3b9SSlawomir Ptak 	    strcmp(g_workload_type, "randwrite") &&
2638d254a3b9SSlawomir Ptak 	    strcmp(g_workload_type, "randrw")) {
2639d254a3b9SSlawomir Ptak 		if (g_random_map) {
2640d254a3b9SSlawomir Ptak 			fprintf(stderr, "Ignoring -D option... Please use -D option"
2641d254a3b9SSlawomir Ptak 				" only when using randread, randwrite or randrw.\n");
2642d254a3b9SSlawomir Ptak 			return 1;
2643d254a3b9SSlawomir Ptak 		}
2644d254a3b9SSlawomir Ptak 	}
2645d254a3b9SSlawomir Ptak 
264629784f35SKrzysztof Karas 	return 0;
264729784f35SKrzysztof Karas out:
264829784f35SKrzysztof Karas 	spdk_app_usage();
264929784f35SKrzysztof Karas 	bdevperf_usage();
265029784f35SKrzysztof Karas 	return 1;
265129784f35SKrzysztof Karas }
265229784f35SKrzysztof Karas 
265329784f35SKrzysztof Karas int
265429784f35SKrzysztof Karas main(int argc, char **argv)
265529784f35SKrzysztof Karas {
265629784f35SKrzysztof Karas 	struct spdk_app_opts opts = {};
265729784f35SKrzysztof Karas 	int rc;
265829784f35SKrzysztof Karas 
265929784f35SKrzysztof Karas 	/* Use the runtime PID to set the random seed */
266029784f35SKrzysztof Karas 	srand(getpid());
266129784f35SKrzysztof Karas 
266229784f35SKrzysztof Karas 	spdk_app_opts_init(&opts, sizeof(opts));
266329784f35SKrzysztof Karas 	opts.name = "bdevperf";
266429784f35SKrzysztof Karas 	opts.rpc_addr = NULL;
266529784f35SKrzysztof Karas 	opts.shutdown_cb = spdk_bdevperf_shutdown_cb;
266629784f35SKrzysztof Karas 
26678e373044SShuhei Matsumoto 	if ((rc = spdk_app_parse_args(argc, argv, &opts, "Zzfq:o:t:w:k:CEF:M:P:S:T:Xlj:D", NULL,
266829784f35SKrzysztof Karas 				      bdevperf_parse_arg, bdevperf_usage)) !=
266929784f35SKrzysztof Karas 	    SPDK_APP_PARSE_ARGS_SUCCESS) {
267029784f35SKrzysztof Karas 		return rc;
267129784f35SKrzysztof Karas 	}
267229784f35SKrzysztof Karas 
267329784f35SKrzysztof Karas 	if (read_job_config()) {
2674*a1f4f11bSShuhei Matsumoto 		bdevperf_fini();
267529784f35SKrzysztof Karas 		return 1;
267629784f35SKrzysztof Karas 	}
267729784f35SKrzysztof Karas 
267829784f35SKrzysztof Karas 	if (verify_test_params(&opts) != 0) {
2679*a1f4f11bSShuhei Matsumoto 		bdevperf_fini();
268029784f35SKrzysztof Karas 		exit(1);
268129784f35SKrzysztof Karas 	}
268229784f35SKrzysztof Karas 
268329784f35SKrzysztof Karas 	rc = spdk_app_start(&opts, bdevperf_run, NULL);
268429784f35SKrzysztof Karas 
268529784f35SKrzysztof Karas 	spdk_app_fini();
2686*a1f4f11bSShuhei Matsumoto 	bdevperf_fini();
268729784f35SKrzysztof Karas 	return rc;
268829784f35SKrzysztof Karas }
2689