Branch data Line data Source code
1 : : /* SPDX-License-Identifier: BSD-3-Clause
2 : : * Copyright (C) 2016 Intel Corporation.
3 : : * Copyright (c) 2022-2023 NVIDIA CORPORATION & AFFILIATES.
4 : : * All rights reserved.
5 : : */
6 : :
7 : : #include "spdk/stdinc.h"
8 : :
9 : : #include "spdk/bdev.h"
10 : : #include "spdk/accel.h"
11 : : #include "spdk/endian.h"
12 : : #include "spdk/env.h"
13 : : #include "spdk/event.h"
14 : : #include "spdk/log.h"
15 : : #include "spdk/util.h"
16 : : #include "spdk/thread.h"
17 : : #include "spdk/string.h"
18 : : #include "spdk/rpc.h"
19 : : #include "spdk/bit_array.h"
20 : : #include "spdk/conf.h"
21 : : #include "spdk/zipf.h"
22 : : #include "spdk/histogram_data.h"
23 : :
24 : : #define BDEVPERF_CONFIG_MAX_FILENAME 1024
25 : : #define BDEVPERF_CONFIG_UNDEFINED -1
26 : : #define BDEVPERF_CONFIG_ERROR -2
27 : : #define PATTERN_TYPES_STR "(read, write, randread, randwrite, rw, randrw, verify, reset, unmap, flush, write_zeroes)"
28 : : #define BDEVPERF_MAX_COREMASK_STRING 64
29 : :
30 : : struct bdevperf_task {
31 : : struct iovec iov;
32 : : struct bdevperf_job *job;
33 : : struct spdk_bdev_io *bdev_io;
34 : : void *buf;
35 : : void *verify_buf;
36 : : void *md_buf;
37 : : uint64_t offset_blocks;
38 : : struct bdevperf_task *task_to_abort;
39 : : enum spdk_bdev_io_type io_type;
40 : : TAILQ_ENTRY(bdevperf_task) link;
41 : : struct spdk_bdev_io_wait_entry bdev_io_wait;
42 : : };
43 : :
44 : : static char *g_workload_type = NULL;
45 : : static int g_io_size = 0;
46 : : /* initialize to invalid value so we can detect if user overrides it. */
47 : : static int g_rw_percentage = -1;
48 : : static bool g_verify = false;
49 : : static bool g_reset = false;
50 : : static bool g_continue_on_failure = false;
51 : : static bool g_abort = false;
52 : : static bool g_error_to_exit = false;
53 : : static int g_queue_depth = 0;
54 : : static uint64_t g_time_in_usec;
55 : : static int g_show_performance_real_time = 0;
56 : : static uint64_t g_show_performance_period_in_usec = SPDK_SEC_TO_USEC;
57 : : static uint64_t g_show_performance_period_num = 0;
58 : : static uint64_t g_show_performance_ema_period = 0;
59 : : static int g_run_rc = 0;
60 : : static bool g_shutdown = false;
61 : : static uint64_t g_start_tsc;
62 : : static uint64_t g_shutdown_tsc;
63 : : static bool g_zcopy = false;
64 : : static struct spdk_thread *g_main_thread;
65 : : static int g_time_in_sec = 0;
66 : : static bool g_mix_specified = false;
67 : : static const char *g_job_bdev_name;
68 : : static bool g_wait_for_tests = false;
69 : : static struct spdk_jsonrpc_request *g_request = NULL;
70 : : static bool g_multithread_mode = false;
71 : : static int g_timeout_in_sec;
72 : : static struct spdk_conf *g_bdevperf_conf = NULL;
73 : : static const char *g_bdevperf_conf_file = NULL;
74 : : static double g_zipf_theta;
75 : : static bool g_random_map = false;
76 : : static bool g_unique_writes = false;
77 : :
78 : : static struct spdk_cpuset g_all_cpuset;
79 : : static struct spdk_poller *g_perf_timer = NULL;
80 : :
81 : : static void bdevperf_submit_single(struct bdevperf_job *job, struct bdevperf_task *task);
82 : : static void rpc_perform_tests_cb(void);
83 : : static int bdevperf_parse_arg(int ch, char *arg);
84 : : static int verify_test_params(void);
85 : : static void bdevperf_usage(void);
86 : :
87 : : static uint32_t g_bdev_count = 0;
88 : : static uint32_t g_latency_display_level;
89 : :
90 : : static bool g_one_thread_per_lcore = false;
91 : :
92 : : static const double g_latency_cutoffs[] = {
93 : : 0.01,
94 : : 0.10,
95 : : 0.25,
96 : : 0.50,
97 : : 0.75,
98 : : 0.90,
99 : : 0.95,
100 : : 0.98,
101 : : 0.99,
102 : : 0.995,
103 : : 0.999,
104 : : 0.9999,
105 : : 0.99999,
106 : : 0.999999,
107 : : 0.9999999,
108 : : -1,
109 : : };
110 : :
111 : : static const char *g_rpc_log_file_name = NULL;
112 : : static FILE *g_rpc_log_file = NULL;
113 : :
114 : : struct latency_info {
115 : : uint64_t min;
116 : : uint64_t max;
117 : : uint64_t total;
118 : : };
119 : :
120 : :
121 : : enum job_config_rw {
122 : : JOB_CONFIG_RW_READ = 0,
123 : : JOB_CONFIG_RW_WRITE,
124 : : JOB_CONFIG_RW_RANDREAD,
125 : : JOB_CONFIG_RW_RANDWRITE,
126 : : JOB_CONFIG_RW_RW,
127 : : JOB_CONFIG_RW_RANDRW,
128 : : JOB_CONFIG_RW_VERIFY,
129 : : JOB_CONFIG_RW_RESET,
130 : : JOB_CONFIG_RW_UNMAP,
131 : : JOB_CONFIG_RW_FLUSH,
132 : : JOB_CONFIG_RW_WRITE_ZEROES,
133 : : };
134 : :
135 : : struct bdevperf_job {
136 : : char *name;
137 : : struct spdk_bdev *bdev;
138 : : struct spdk_bdev_desc *bdev_desc;
139 : : struct spdk_io_channel *ch;
140 : : TAILQ_ENTRY(bdevperf_job) link;
141 : : struct spdk_thread *thread;
142 : :
143 : : enum job_config_rw workload_type;
144 : : int io_size;
145 : : int rw_percentage;
146 : : bool is_random;
147 : : bool verify;
148 : : bool reset;
149 : : bool continue_on_failure;
150 : : bool unmap;
151 : : bool write_zeroes;
152 : : bool flush;
153 : : bool abort;
154 : : int queue_depth;
155 : : unsigned int seed;
156 : :
157 : : uint64_t io_completed;
158 : : uint64_t io_failed;
159 : : uint64_t io_timeout;
160 : : uint64_t prev_io_completed;
161 : : double ema_io_per_second;
162 : : int current_queue_depth;
163 : : uint64_t size_in_ios;
164 : : uint64_t ios_base;
165 : : uint64_t offset_in_ios;
166 : : uint64_t io_size_blocks;
167 : : uint64_t buf_size;
168 : : uint32_t dif_check_flags;
169 : : bool is_draining;
170 : : struct spdk_poller *run_timer;
171 : : struct spdk_poller *reset_timer;
172 : : struct spdk_bit_array *outstanding;
173 : : struct spdk_zipf *zipf;
174 : : TAILQ_HEAD(, bdevperf_task) task_list;
175 : : uint64_t run_time_in_usec;
176 : :
177 : : /* keep channel's histogram data before being destroyed */
178 : : struct spdk_histogram_data *histogram;
179 : : struct spdk_bit_array *random_map;
180 : :
181 : : /* counter used for generating unique write data (-U option) */
182 : : uint32_t write_io_count;
183 : : };
184 : :
185 : : struct spdk_bdevperf {
186 : : TAILQ_HEAD(, bdevperf_job) jobs;
187 : : uint32_t running_jobs;
188 : : };
189 : :
190 : : static struct spdk_bdevperf g_bdevperf = {
191 : : .jobs = TAILQ_HEAD_INITIALIZER(g_bdevperf.jobs),
192 : : .running_jobs = 0,
193 : : };
194 : :
195 : : /* Storing values from a section of job config file */
196 : : struct job_config {
197 : : const char *name;
198 : : const char *filename;
199 : : struct spdk_cpuset cpumask;
200 : : int bs;
201 : : int iodepth;
202 : : int rwmixread;
203 : : uint32_t lcore;
204 : : int64_t offset;
205 : : uint64_t length;
206 : : enum job_config_rw rw;
207 : : TAILQ_ENTRY(job_config) link;
208 : : };
209 : :
210 : : TAILQ_HEAD(, job_config) job_config_list
211 : : = TAILQ_HEAD_INITIALIZER(job_config_list);
212 : :
213 : : static bool g_performance_dump_active = false;
214 : :
215 : : struct bdevperf_aggregate_stats {
216 : : struct bdevperf_job *current_job;
217 : : uint64_t io_time_in_usec;
218 : : uint64_t ema_period;
219 : : double total_io_per_second;
220 : : double total_mb_per_second;
221 : : double total_failed_per_second;
222 : : double total_timeout_per_second;
223 : : double min_latency;
224 : : double max_latency;
225 : : uint64_t total_io_completed;
226 : : uint64_t total_tsc;
227 : : };
228 : :
229 : : static struct bdevperf_aggregate_stats g_stats = {.min_latency = (double)UINT64_MAX};
230 : :
231 : : struct lcore_thread {
232 : : struct spdk_thread *thread;
233 : : uint32_t lcore;
234 : : TAILQ_ENTRY(lcore_thread) link;
235 : : };
236 : :
237 : : TAILQ_HEAD(, lcore_thread) g_lcore_thread_list
238 : : = TAILQ_HEAD_INITIALIZER(g_lcore_thread_list);
239 : :
240 : :
241 : : static char *
242 : 1447 : parse_workload_type(enum job_config_rw ret)
243 : : {
244 [ + + + + : 1447 : switch (ret) {
+ + + + +
+ + - ]
245 : 48 : case JOB_CONFIG_RW_READ:
246 : 48 : return "read";
247 : 126 : case JOB_CONFIG_RW_RANDREAD:
248 : 126 : return "randread";
249 : 28 : case JOB_CONFIG_RW_WRITE:
250 : 28 : return "write";
251 : 126 : case JOB_CONFIG_RW_RANDWRITE:
252 : 126 : return "randwrite";
253 : 705 : case JOB_CONFIG_RW_VERIFY:
254 : 705 : return "verify";
255 : 1 : case JOB_CONFIG_RW_RESET:
256 : 1 : return "reset";
257 : 9 : case JOB_CONFIG_RW_UNMAP:
258 : 9 : return "unmap";
259 : 129 : case JOB_CONFIG_RW_WRITE_ZEROES:
260 : 129 : return "write_zeroes";
261 : 8 : case JOB_CONFIG_RW_FLUSH:
262 : 8 : return "flush";
263 : 40 : case JOB_CONFIG_RW_RW:
264 : 40 : return "rw";
265 : 227 : case JOB_CONFIG_RW_RANDRW:
266 : 227 : return "randrw";
267 : 0 : default:
268 [ # # # # ]: 0 : fprintf(stderr, "wrong workload_type code\n");
269 : : }
270 : :
271 : 0 : return NULL;
272 : : }
273 : :
274 : : /*
275 : : * Cumulative Moving Average (CMA): average of all data up to current
276 : : * Exponential Moving Average (EMA): weighted mean of the previous n data and more weight is given to recent
277 : : * Simple Moving Average (SMA): unweighted mean of the previous n data
278 : : *
279 : : * Bdevperf supports CMA and EMA.
280 : : */
281 : : static double
282 : 1190 : get_cma_io_per_second(struct bdevperf_job *job, uint64_t io_time_in_usec)
283 : : {
284 : 1190 : return (double)job->io_completed * SPDK_SEC_TO_USEC / io_time_in_usec;
285 : : }
286 : :
287 : : static double
288 : 0 : get_ema_io_per_second(struct bdevperf_job *job, uint64_t ema_period)
289 : : {
290 : : double io_completed, io_per_second;
291 : :
292 : 0 : io_completed = job->io_completed;
293 : 0 : io_per_second = (double)(io_completed - job->prev_io_completed) * SPDK_SEC_TO_USEC
294 : 0 : / g_show_performance_period_in_usec;
295 : 0 : job->prev_io_completed = io_completed;
296 : :
297 : 0 : job->ema_io_per_second += (io_per_second - job->ema_io_per_second) * 2
298 : 0 : / (ema_period + 1);
299 : 0 : return job->ema_io_per_second;
300 : : }
301 : :
302 : : static void
303 : 8834556 : get_avg_latency(void *ctx, uint64_t start, uint64_t end, uint64_t count,
304 : : uint64_t total, uint64_t so_far)
305 : : {
306 : 8834556 : struct latency_info *latency_info = ctx;
307 : :
308 [ + + ]: 8834556 : if (count == 0) {
309 : 8552431 : return;
310 : : }
311 : :
312 : 282126 : latency_info->total += (start + end) / 2 * count;
313 : :
314 [ + + ]: 282126 : if (so_far == count) {
315 : 1110 : latency_info->min = start;
316 : : }
317 : :
318 [ + + ]: 282126 : if (so_far == total) {
319 : 1110 : latency_info->max = end;
320 : : }
321 : : }
322 : :
323 : : static void
324 : 1190 : performance_dump_job(struct bdevperf_aggregate_stats *stats, struct bdevperf_job *job,
325 : : struct spdk_json_write_ctx *w)
326 : : {
327 : : double io_per_second, mb_per_second, failed_per_second, timeout_per_second;
328 : 1190 : double average_latency = 0.0, min_latency, max_latency;
329 : : uint64_t time_in_usec;
330 : : uint64_t tsc_rate;
331 : : uint64_t total_io;
332 : 1190 : struct latency_info latency_info = {};
333 : 1190 : char core_mask_string[BDEVPERF_MAX_COREMASK_STRING] = {0};
334 : :
335 [ + + ]: 1190 : if (w) {
336 : 257 : spdk_json_write_named_string(w, "job", job->name);
337 [ - + + + ]: 257 : if (g_shutdown) {
338 : 6 : spdk_json_write_named_string(w, "test_status", "terminated");
339 : : } else {
340 : 251 : spdk_json_write_named_string(w, "test_status", "finished");
341 : : }
342 [ - + ]: 257 : snprintf(core_mask_string, BDEVPERF_MAX_COREMASK_STRING,
343 : : "0x%s", spdk_cpuset_fmt(spdk_thread_get_cpumask(job->thread)));
344 : 257 : spdk_json_write_named_string(w, "core_mask", core_mask_string);
345 : 257 : spdk_json_write_named_string(w, "workload", parse_workload_type(job->workload_type));
346 : : }
347 [ + + + + ]: 1190 : if (job->workload_type == JOB_CONFIG_RW_RW || job->workload_type == JOB_CONFIG_RW_RANDRW) {
348 [ - + ]: 155 : printf("\r Job: %s (Core Mask 0x%s, workload: %s, percentage: %d, depth: %d, IO size: %d)\n",
349 : : job->name, spdk_cpuset_fmt(spdk_thread_get_cpumask(job->thread)),
350 : : parse_workload_type(job->workload_type), job->rw_percentage,
351 : : job->queue_depth, job->io_size);
352 [ + + ]: 155 : if (w) {
353 : 112 : spdk_json_write_named_uint32(w, "percentage", job->rw_percentage);
354 : : }
355 : : } else {
356 [ - + ]: 1035 : printf("\r Job: %s (Core Mask 0x%s, workload: %s, depth: %d, IO size: %d)\n",
357 : : job->name, spdk_cpuset_fmt(spdk_thread_get_cpumask(job->thread)),
358 : : parse_workload_type(job->workload_type), job->queue_depth, job->io_size);
359 : : }
360 : :
361 : :
362 [ + + + + : 1190 : if (job->io_failed > 0 && !job->reset && !job->continue_on_failure) {
+ + - + +
+ ]
363 [ - + ]: 18 : printf("\r Job: %s ended in about %.2f seconds with error\n",
364 : 18 : job->name, (double)job->run_time_in_usec / SPDK_SEC_TO_USEC);
365 [ - + ]: 18 : if (w) {
366 : 0 : spdk_json_write_named_string(w, "job_status", "io_failed");
367 : 0 : spdk_json_write_named_double(w, "error_in_sec", (double)job->run_time_in_usec / SPDK_SEC_TO_USEC);
368 : : }
369 : : }
370 : :
371 [ + + + + ]: 1190 : if (job->verify) {
372 [ - + ]: 644 : printf("\t Verification LBA range: start 0x%" PRIx64 " length 0x%" PRIx64 "\n",
373 : : job->ios_base, job->size_in_ios);
374 [ + + ]: 644 : if (w) {
375 : 62 : spdk_json_write_named_uint64(w, "verify_LBA_range_start", job->ios_base);
376 : 62 : spdk_json_write_named_uint64(w, "verify_LBA_range_len", job->size_in_ios);
377 : : }
378 : : }
379 : :
380 [ - + + + ]: 1190 : if (g_performance_dump_active == true) {
381 : : /* Use job's actual run time as Job has ended */
382 [ - + - - : 80 : if (job->io_failed > 0 && !job->continue_on_failure) {
- - ]
383 : 0 : time_in_usec = job->run_time_in_usec;
384 : : } else {
385 : 80 : time_in_usec = stats->io_time_in_usec;
386 : : }
387 : : } else {
388 : 1110 : time_in_usec = job->run_time_in_usec;
389 : : }
390 : :
391 [ + - ]: 1190 : if (stats->ema_period == 0) {
392 : 1190 : io_per_second = get_cma_io_per_second(job, time_in_usec);
393 : : } else {
394 : 0 : io_per_second = get_ema_io_per_second(job, stats->ema_period);
395 : : }
396 : :
397 : 1190 : tsc_rate = spdk_get_ticks_hz();
398 : 1190 : mb_per_second = io_per_second * job->io_size / (1024 * 1024);
399 : :
400 : 1190 : spdk_histogram_data_iterate(job->histogram, get_avg_latency, &latency_info);
401 : :
402 : 1190 : total_io = job->io_completed + job->io_failed;
403 [ + - ]: 1190 : if (total_io != 0) {
404 : 1190 : average_latency = (double)latency_info.total / total_io * SPDK_SEC_TO_USEC / tsc_rate;
405 : : }
406 : 1190 : min_latency = (double)latency_info.min * SPDK_SEC_TO_USEC / tsc_rate;
407 : 1190 : max_latency = (double)latency_info.max * SPDK_SEC_TO_USEC / tsc_rate;
408 : :
409 : 1190 : failed_per_second = (double)job->io_failed * SPDK_SEC_TO_USEC / time_in_usec;
410 : 1190 : timeout_per_second = (double)job->io_timeout * SPDK_SEC_TO_USEC / time_in_usec;
411 : :
412 [ - + ]: 1190 : printf("\t %-20s: %10.2f %10.2f %10.2f",
413 : 1190 : job->name, (float)time_in_usec / SPDK_SEC_TO_USEC, io_per_second, mb_per_second);
414 [ - + ]: 1190 : printf(" %10.2f %8.2f",
415 : : failed_per_second, timeout_per_second);
416 [ - + ]: 1190 : printf(" %10.2f %10.2f %10.2f\n",
417 : : average_latency, min_latency, max_latency);
418 : :
419 : 1190 : stats->total_io_per_second += io_per_second;
420 : 1190 : stats->total_mb_per_second += mb_per_second;
421 : 1190 : stats->total_failed_per_second += failed_per_second;
422 : 1190 : stats->total_timeout_per_second += timeout_per_second;
423 : 1190 : stats->total_io_completed += job->io_completed + job->io_failed;
424 : 1190 : stats->total_tsc += latency_info.total;
425 [ + + ]: 1190 : if (min_latency < stats->min_latency) {
426 : 625 : stats->min_latency = min_latency;
427 : : }
428 [ + + ]: 1190 : if (max_latency > stats->max_latency) {
429 : 479 : stats->max_latency = max_latency;
430 : : }
431 [ + + ]: 1190 : if (w) {
432 : 257 : spdk_json_write_named_uint32(w, "queue_depth", job->queue_depth);
433 : 257 : spdk_json_write_named_uint32(w, "io_size", job->io_size);
434 : 257 : spdk_json_write_named_double(w, "runtime", (float)time_in_usec / SPDK_SEC_TO_USEC);
435 : 257 : spdk_json_write_named_double(w, "io_per_second", io_per_second);
436 : 257 : spdk_json_write_named_double(w, "MiB_per_second", mb_per_second);
437 : 257 : spdk_json_write_named_double(w, "fails_per_second", failed_per_second);
438 : 257 : spdk_json_write_named_double(w, "timeout_per_second", timeout_per_second);
439 : 257 : spdk_json_write_named_double(w, "average_latency_us", average_latency);
440 : 257 : spdk_json_write_named_double(w, "min_latency_us", min_latency);
441 : 257 : spdk_json_write_named_double(w, "max_latency_us", max_latency);
442 : : }
443 : 1190 : }
444 : :
445 : : static void
446 : 8875552 : generate_data(struct bdevperf_job *job, void *buf, void *md_buf, bool unique)
447 : : {
448 : 8875552 : int offset_blocks = 0, md_offset, data_block_size, inner_offset;
449 : 8875552 : int buf_len = job->buf_size;
450 : 8875552 : int block_size = spdk_bdev_get_block_size(job->bdev);
451 : 8875552 : int md_size = spdk_bdev_get_md_size(job->bdev);
452 : 8875552 : int num_blocks = job->io_size_blocks;
453 : :
454 [ - + ]: 8875552 : if (buf_len < num_blocks * block_size) {
455 : 0 : return;
456 : : }
457 : :
458 [ + + ]: 8875552 : if (md_buf == NULL) {
459 : 8829536 : data_block_size = block_size - md_size;
460 : 8829536 : md_buf = (char *)buf + data_block_size;
461 : 8829536 : md_offset = block_size;
462 : : } else {
463 : 46016 : data_block_size = block_size;
464 : 46016 : md_offset = md_size;
465 : : }
466 : :
467 [ + + ]: 8875552 : if (unique) {
468 : 10279 : uint64_t io_count = job->write_io_count++;
469 : : unsigned int i;
470 : :
471 [ - + - - ]: 10279 : assert(md_size == 0 || md_size >= (int)sizeof(uint64_t));
472 : :
473 [ + + ]: 63164465 : while (offset_blocks < num_blocks) {
474 : 63154208 : inner_offset = 0;
475 [ + + ]: 4105021520 : while (inner_offset < data_block_size) {
476 : 4041867712 : *(uint64_t *)buf = (io_count << 32) | (offset_blocks + inner_offset);
477 : 4041867712 : inner_offset += sizeof(uint64_t);
478 : 4041867712 : buf += sizeof(uint64_t);
479 : : }
480 [ - + ]: 63154208 : for (i = 0; i < md_size / sizeof(uint64_t); i++) {
481 : 0 : ((uint64_t *)md_buf)[i] = (io_count << 32) | offset_blocks;
482 : : }
483 : 63154208 : md_buf += md_offset;
484 : 63154208 : offset_blocks++;
485 : : }
486 : 10279 : return;
487 : : }
488 : :
489 [ + + ]: 77845545 : while (offset_blocks < num_blocks) {
490 : 68980274 : inner_offset = 0;
491 [ + + ]:14092850430 : while (inner_offset < data_block_size) {
492 :14023870416 : *(uint32_t *)buf = offset_blocks + inner_offset;
493 :14023870416 : inner_offset += sizeof(uint32_t);
494 :14023870416 : buf += sizeof(uint32_t);
495 : : }
496 [ - + ]: 68980274 : memset(md_buf, offset_blocks, md_size);
497 : 68980274 : md_buf += md_offset;
498 : 68980274 : offset_blocks++;
499 : : }
500 : : }
501 : :
502 : : static bool
503 : 0 : copy_data(void *wr_buf, int wr_buf_len, void *rd_buf, int rd_buf_len, int block_size,
504 : : void *wr_md_buf, void *rd_md_buf, int md_size, int num_blocks)
505 : : {
506 [ # # # # ]: 0 : if (wr_buf_len < num_blocks * block_size || rd_buf_len < num_blocks * block_size) {
507 : 0 : return false;
508 : : }
509 : :
510 [ # # ]: 0 : assert((wr_md_buf != NULL) == (rd_md_buf != NULL));
511 : :
512 [ # # # # ]: 0 : memcpy(wr_buf, rd_buf, block_size * num_blocks);
513 : :
514 [ # # ]: 0 : if (wr_md_buf != NULL) {
515 [ # # # # ]: 0 : memcpy(wr_md_buf, rd_md_buf, md_size * num_blocks);
516 : : }
517 : :
518 : 0 : return true;
519 : : }
520 : :
521 : : static bool
522 : 8508626 : verify_data(void *wr_buf, int wr_buf_len, void *rd_buf, int rd_buf_len, int block_size,
523 : : void *wr_md_buf, void *rd_md_buf, int md_size, int num_blocks, bool md_check)
524 : : {
525 : 8508626 : int offset_blocks = 0, md_offset, data_block_size;
526 : :
527 [ + - - + ]: 8508626 : if (wr_buf_len < num_blocks * block_size || rd_buf_len < num_blocks * block_size) {
528 : 0 : return false;
529 : : }
530 : :
531 [ - + ]: 8508626 : assert((wr_md_buf != NULL) == (rd_md_buf != NULL));
532 : :
533 [ + + ]: 8508626 : if (wr_md_buf == NULL) {
534 : 8462610 : data_block_size = block_size - md_size;
535 : 8462610 : wr_md_buf = (char *)wr_buf + data_block_size;
536 : 8462610 : rd_md_buf = (char *)rd_buf + data_block_size;
537 : 8462610 : md_offset = block_size;
538 : : } else {
539 : 46016 : data_block_size = block_size;
540 : 46016 : md_offset = md_size;
541 : : }
542 : :
543 [ + + ]: 74535882 : while (offset_blocks < num_blocks) {
544 [ - + - + : 66027258 : if (memcmp(wr_buf, rd_buf, data_block_size) != 0) {
- + ]
545 : 0 : printf("data_block_size %d, num_blocks %d, offset %d\n", data_block_size, num_blocks,
546 : : offset_blocks);
547 : 0 : spdk_log_dump(stdout, "rd_buf", rd_buf, data_block_size);
548 : 0 : spdk_log_dump(stdout, "wr_buf", wr_buf, data_block_size);
549 : 0 : return false;
550 : : }
551 : :
552 : 66027258 : wr_buf += block_size;
553 : 66027258 : rd_buf += block_size;
554 : :
555 [ + - ]: 66027258 : if (md_check) {
556 [ - + - + : 66027258 : if (memcmp(wr_md_buf, rd_md_buf, md_size) != 0) {
- + ]
557 : 0 : printf("md_size %d, num_blocks %d, offset %d\n", md_size, num_blocks, offset_blocks);
558 : 0 : spdk_log_dump(stdout, "rd_md_buf", rd_md_buf, md_size);
559 : 0 : spdk_log_dump(stdout, "wr_md_buf", wr_md_buf, md_size);
560 : 0 : return false;
561 : : }
562 : :
563 : 66027258 : wr_md_buf += md_offset;
564 : 66027258 : rd_md_buf += md_offset;
565 : : }
566 : :
567 : 66027258 : offset_blocks++;
568 : : }
569 : :
570 : 8508626 : return true;
571 : : }
572 : :
573 : : static void
574 : 1121 : free_job_config(void)
575 : : {
576 : : struct job_config *config, *tmp;
577 : :
578 : 1121 : spdk_conf_free(g_bdevperf_conf);
579 : 1121 : g_bdevperf_conf = NULL;
580 : :
581 [ + + ]: 2211 : TAILQ_FOREACH_SAFE(config, &job_config_list, link, tmp) {
582 [ + + ]: 1090 : TAILQ_REMOVE(&job_config_list, config, link);
583 : 1090 : free(config);
584 : : }
585 : 1121 : }
586 : :
587 : : static void
588 : 1110 : bdevperf_job_free(struct bdevperf_job *job)
589 : : {
590 : 1110 : spdk_histogram_data_free(job->histogram);
591 : 1110 : spdk_bit_array_free(&job->outstanding);
592 : 1110 : spdk_bit_array_free(&job->random_map);
593 : 1110 : spdk_zipf_free(&job->zipf);
594 : 1110 : free(job->name);
595 : 1110 : free(job);
596 : 1110 : }
597 : :
598 : : static void
599 : 1110 : job_thread_exit(void *ctx)
600 : : {
601 : 1110 : spdk_thread_exit(spdk_get_thread());
602 : 1110 : }
603 : :
604 : : static void
605 : 0 : check_cutoff(void *ctx, uint64_t start, uint64_t end, uint64_t count,
606 : : uint64_t total, uint64_t so_far)
607 : : {
608 : : double so_far_pct;
609 : 0 : double **cutoff = ctx;
610 : : uint64_t tsc_rate;
611 : :
612 [ # # ]: 0 : if (count == 0) {
613 : 0 : return;
614 : : }
615 : :
616 : 0 : tsc_rate = spdk_get_ticks_hz();
617 : 0 : so_far_pct = (double)so_far / total;
618 [ # # # # ]: 0 : while (so_far_pct >= **cutoff && **cutoff > 0) {
619 [ # # ]: 0 : printf("%9.5f%% : %9.3fus\n", **cutoff * 100, (double)end * SPDK_SEC_TO_USEC / tsc_rate);
620 : 0 : (*cutoff)++;
621 : : }
622 : : }
623 : :
624 : : static void
625 : 0 : print_bucket(void *ctx, uint64_t start, uint64_t end, uint64_t count,
626 : : uint64_t total, uint64_t so_far)
627 : : {
628 : : double so_far_pct;
629 : : uint64_t tsc_rate;
630 : :
631 [ # # ]: 0 : if (count == 0) {
632 : 0 : return;
633 : : }
634 : :
635 : 0 : tsc_rate = spdk_get_ticks_hz();
636 : 0 : so_far_pct = (double)so_far * 100 / total;
637 : 0 : printf("%9.3f - %9.3f: %9.4f%% (%9ju)\n",
638 : 0 : (double)start * SPDK_SEC_TO_USEC / tsc_rate,
639 [ # # ]: 0 : (double)end * SPDK_SEC_TO_USEC / tsc_rate,
640 : : so_far_pct, count);
641 : : }
642 : :
643 : : static void
644 : 658 : bdevperf_test_done(void *ctx)
645 : : {
646 : : struct bdevperf_job *job, *jtmp;
647 : : struct bdevperf_task *task, *ttmp;
648 : : struct lcore_thread *lthread, *lttmp;
649 : 658 : double average_latency = 0.0;
650 : : uint64_t time_in_usec;
651 : : int rc;
652 : 658 : struct spdk_json_write_ctx *w = NULL;
653 : :
654 [ + - ]: 658 : if (g_time_in_usec) {
655 : 658 : g_stats.io_time_in_usec = g_time_in_usec;
656 : :
657 [ + + - + : 658 : if (!g_run_rc && g_performance_dump_active) {
- + ]
658 : 0 : spdk_thread_send_msg(spdk_get_thread(), bdevperf_test_done, NULL);
659 : 0 : return;
660 : : }
661 : : }
662 : :
663 [ + + ]: 658 : if (g_show_performance_real_time) {
664 : 16 : spdk_poller_unregister(&g_perf_timer);
665 : : }
666 : :
667 [ + + + + ]: 658 : if (g_shutdown) {
668 : 286 : g_shutdown_tsc = spdk_get_ticks() - g_start_tsc;
669 [ - + ]: 286 : time_in_usec = g_shutdown_tsc * SPDK_SEC_TO_USEC / spdk_get_ticks_hz();
670 : 286 : g_time_in_usec = (g_time_in_usec > time_in_usec) ? time_in_usec : g_time_in_usec;
671 : 286 : printf("Received shutdown signal, test time was about %.6f seconds\n",
672 [ - + ]: 286 : (double)g_time_in_usec / SPDK_SEC_TO_USEC);
673 : : }
674 : : /* Send RPC response if g_run_rc indicate success, or shutdown request was sent to bdevperf.
675 : : * rpc_perform_tests_cb will send error response in case of error.
676 : : */
677 [ + + - + : 658 : if ((g_run_rc == 0 || g_shutdown) && g_request) {
+ + + + ]
678 : 229 : w = spdk_jsonrpc_begin_result(g_request);
679 : 229 : spdk_json_write_object_begin(w);
680 : 229 : spdk_json_write_named_uint32(w, "core_count", spdk_env_get_core_count());
681 : 229 : spdk_json_write_named_array_begin(w, "test_results");
682 : : }
683 : :
684 [ - + ]: 658 : printf("\n%*s\n", 107, "Latency(us)");
685 [ - + ]: 658 : printf("\r %-*s: %10s %10s %10s %10s %8s %10s %10s %10s\n",
686 : : 28, "Device Information", "runtime(s)", "IOPS", "MiB/s", "Fail/s", "TO/s", "Average", "min", "max");
687 : :
688 [ + + ]: 1768 : TAILQ_FOREACH_SAFE(job, &g_bdevperf.jobs, link, jtmp) {
689 [ + + ]: 1110 : if (w) {
690 : 257 : spdk_json_write_object_begin(w);
691 : 257 : performance_dump_job(&g_stats, job, w);
692 : 257 : spdk_json_write_object_end(w);
693 : : } else {
694 : 853 : performance_dump_job(&g_stats, job, NULL);
695 : : }
696 : : }
697 : :
698 [ + + ]: 658 : if (w) {
699 : 229 : spdk_json_write_array_end(w);
700 : 229 : spdk_json_write_object_end(w);
701 : 229 : spdk_jsonrpc_end_result(g_request, w);
702 : : }
703 [ - + ]: 658 : printf("\r =================================================================================="
704 : : "=================================\n");
705 [ - + ]: 658 : printf("\r %-28s: %10s %10.2f %10.2f",
706 : : "Total", "", g_stats.total_io_per_second, g_stats.total_mb_per_second);
707 [ - + ]: 658 : printf(" %10.2f %8.2f",
708 : : g_stats.total_failed_per_second, g_stats.total_timeout_per_second);
709 : :
710 [ + + ]: 658 : if (g_stats.total_io_completed != 0) {
711 : 381 : average_latency = ((double)g_stats.total_tsc / g_stats.total_io_completed) * SPDK_SEC_TO_USEC /
712 : 381 : spdk_get_ticks_hz();
713 : : }
714 [ - + ]: 658 : printf(" %10.2f %10.2f %10.2f\n", average_latency, g_stats.min_latency, g_stats.max_latency);
715 : :
716 [ - + - - ]: 658 : if (g_latency_display_level == 0 || g_stats.total_io_completed == 0) {
717 : 658 : goto clean;
718 : : }
719 : :
720 [ # # ]: 0 : printf("\n Latency summary\n");
721 [ # # ]: 0 : TAILQ_FOREACH_SAFE(job, &g_bdevperf.jobs, link, jtmp) {
722 [ # # ]: 0 : printf("\r =============================================\n");
723 [ # # ]: 0 : printf("\r Job: %s (Core Mask 0x%s)\n", job->name,
724 : : spdk_cpuset_fmt(spdk_thread_get_cpumask(job->thread)));
725 : :
726 : 0 : const double *cutoff = g_latency_cutoffs;
727 : :
728 : 0 : spdk_histogram_data_iterate(job->histogram, check_cutoff, &cutoff);
729 : :
730 : 0 : printf("\n");
731 : : }
732 : :
733 [ # # ]: 0 : if (g_latency_display_level == 1) {
734 : 0 : goto clean;
735 : : }
736 : :
737 [ # # ]: 0 : printf("\r Latency histogram\n");
738 [ # # ]: 0 : TAILQ_FOREACH_SAFE(job, &g_bdevperf.jobs, link, jtmp) {
739 [ # # ]: 0 : printf("\r =============================================\n");
740 [ # # ]: 0 : printf("\r Job: %s (Core Mask 0x%s)\n", job->name,
741 : : spdk_cpuset_fmt(spdk_thread_get_cpumask(job->thread)));
742 : :
743 : 0 : spdk_histogram_data_iterate(job->histogram, print_bucket, NULL);
744 : 0 : printf("\n");
745 : : }
746 : :
747 : 0 : clean:
748 : 658 : fflush(stdout);
749 : :
750 [ + + ]: 1768 : TAILQ_FOREACH_SAFE(job, &g_bdevperf.jobs, link, jtmp) {
751 [ + + ]: 1110 : TAILQ_REMOVE(&g_bdevperf.jobs, job, link);
752 : :
753 [ + + + - ]: 1110 : if (!g_one_thread_per_lcore) {
754 : 1110 : spdk_thread_send_msg(job->thread, job_thread_exit, NULL);
755 : : }
756 : :
757 [ + + ]: 132974 : TAILQ_FOREACH_SAFE(task, &job->task_list, link, ttmp) {
758 [ + + ]: 131864 : TAILQ_REMOVE(&job->task_list, task, link);
759 : 131864 : spdk_free(task->buf);
760 : 131864 : spdk_free(task->verify_buf);
761 : 131864 : spdk_free(task->md_buf);
762 : 131864 : free(task);
763 : : }
764 : :
765 : 1110 : bdevperf_job_free(job);
766 : : }
767 : :
768 [ - + - + ]: 658 : if (g_one_thread_per_lcore) {
769 [ # # ]: 0 : TAILQ_FOREACH_SAFE(lthread, &g_lcore_thread_list, link, lttmp) {
770 [ # # ]: 0 : TAILQ_REMOVE(&g_lcore_thread_list, lthread, link);
771 : 0 : spdk_thread_send_msg(lthread->thread, job_thread_exit, NULL);
772 : 0 : free(lthread);
773 : : }
774 : : }
775 : :
776 [ + + ]: 658 : if (g_bdevperf_conf == NULL) {
777 : 638 : free_job_config();
778 : : }
779 : :
780 : 658 : rc = g_run_rc;
781 [ + + + + : 658 : if (g_request && !g_shutdown) {
+ + ]
782 : 228 : rpc_perform_tests_cb();
783 [ + + ]: 228 : if (rc != 0) {
784 : 5 : spdk_app_stop(rc);
785 : : }
786 : : } else {
787 : 430 : spdk_app_stop(rc);
788 : : }
789 : : }
790 : :
791 : : static void
792 : 1110 : bdevperf_job_end(void *ctx)
793 : : {
794 [ - + ]: 1110 : assert(g_main_thread == spdk_get_thread());
795 : :
796 [ + + ]: 1110 : if (--g_bdevperf.running_jobs == 0) {
797 : 381 : bdevperf_test_done(NULL);
798 : : }
799 : 1110 : }
800 : :
801 : : static void
802 : 1110 : bdevperf_channel_get_histogram_cb(void *cb_arg, int status, struct spdk_histogram_data *histogram)
803 : : {
804 : 1110 : struct spdk_histogram_data *job_hist = cb_arg;
805 : :
806 [ + - ]: 1110 : if (status == 0) {
807 : 1110 : spdk_histogram_data_merge(job_hist, histogram);
808 : : }
809 : 1110 : }
810 : :
811 : : static void
812 : 1110 : bdevperf_job_empty(struct bdevperf_job *job)
813 : : {
814 : 1110 : uint64_t end_tsc = 0;
815 : :
816 : 1110 : end_tsc = spdk_get_ticks() - g_start_tsc;
817 [ - + ]: 1110 : job->run_time_in_usec = end_tsc * SPDK_SEC_TO_USEC / spdk_get_ticks_hz();
818 : : /* keep histogram info before channel is destroyed */
819 : 1110 : spdk_bdev_channel_get_histogram(job->ch, bdevperf_channel_get_histogram_cb,
820 : 1110 : job->histogram);
821 : 1110 : spdk_put_io_channel(job->ch);
822 : 1110 : spdk_bdev_close(job->bdev_desc);
823 : 1110 : spdk_thread_send_msg(g_main_thread, bdevperf_job_end, NULL);
824 : 1110 : }
825 : :
826 : : static void
827 : 131863 : bdevperf_end_task(struct bdevperf_task *task)
828 : : {
829 : 131863 : struct bdevperf_job *job = task->job;
830 : :
831 : 131863 : TAILQ_INSERT_TAIL(&job->task_list, task, link);
832 [ + + + - ]: 131863 : if (job->is_draining) {
833 [ + + ]: 131863 : if (job->current_queue_depth == 0) {
834 : 1110 : bdevperf_job_empty(job);
835 : : }
836 : : }
837 : 131863 : }
838 : :
839 : : static void
840 : 238520 : bdevperf_queue_io_wait_with_cb(struct bdevperf_task *task, spdk_bdev_io_wait_cb cb_fn)
841 : : {
842 : 238520 : struct bdevperf_job *job = task->job;
843 : :
844 : 238520 : task->bdev_io_wait.bdev = job->bdev;
845 : 238520 : task->bdev_io_wait.cb_fn = cb_fn;
846 : 238520 : task->bdev_io_wait.cb_arg = task;
847 : 238520 : spdk_bdev_queue_io_wait(job->bdev, job->ch, &task->bdev_io_wait);
848 : 238520 : }
849 : :
850 : : static int
851 : 2079 : bdevperf_job_drain(void *ctx)
852 : : {
853 : 2079 : struct bdevperf_job *job = ctx;
854 : :
855 : 2079 : spdk_poller_unregister(&job->run_timer);
856 [ - + + + ]: 2079 : if (job->reset) {
857 : 1 : spdk_poller_unregister(&job->reset_timer);
858 : : }
859 : :
860 : 2079 : job->is_draining = true;
861 : :
862 : 2079 : return -1;
863 : : }
864 : :
865 : : static int
866 : 915 : bdevperf_job_drain_timer(void *ctx)
867 : : {
868 : 915 : struct bdevperf_job *job = ctx;
869 : :
870 : 915 : bdevperf_job_drain(ctx);
871 [ - + ]: 915 : if (job->current_queue_depth == 0) {
872 : 0 : bdevperf_job_empty(job);
873 : : }
874 : :
875 : 915 : return SPDK_POLLER_BUSY;
876 : : }
877 : :
878 : : static void
879 : 0 : bdevperf_abort_complete(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg)
880 : : {
881 : 0 : struct bdevperf_task *task = cb_arg;
882 : 0 : struct bdevperf_job *job = task->job;
883 : :
884 : 0 : job->current_queue_depth--;
885 : :
886 [ # # ]: 0 : if (success) {
887 : 0 : job->io_completed++;
888 : : } else {
889 : 0 : job->io_failed++;
890 [ # # # # ]: 0 : if (!job->continue_on_failure) {
891 : 0 : bdevperf_job_drain(job);
892 : 0 : g_run_rc = -1;
893 : : }
894 : : }
895 : :
896 : 0 : spdk_bdev_free_io(bdev_io);
897 : 0 : bdevperf_end_task(task);
898 : 0 : }
899 : :
900 : : static int
901 : 0 : bdevperf_verify_dif(struct bdevperf_task *task)
902 : : {
903 : 0 : struct bdevperf_job *job = task->job;
904 : 0 : struct spdk_bdev *bdev = job->bdev;
905 : 0 : struct spdk_dif_ctx dif_ctx;
906 : 0 : struct spdk_dif_error err_blk = {};
907 : : int rc;
908 : 0 : struct spdk_dif_ctx_init_ext_opts dif_opts;
909 : :
910 : 0 : dif_opts.size = SPDK_SIZEOF(&dif_opts, dif_pi_format);
911 : 0 : dif_opts.dif_pi_format = SPDK_DIF_PI_FORMAT_16;
912 : 0 : rc = spdk_dif_ctx_init(&dif_ctx,
913 : : spdk_bdev_get_block_size(bdev),
914 : : spdk_bdev_get_md_size(bdev),
915 : 0 : spdk_bdev_is_md_interleaved(bdev),
916 : 0 : spdk_bdev_is_dif_head_of_md(bdev),
917 : : spdk_bdev_get_dif_type(bdev),
918 : : job->dif_check_flags,
919 : 0 : task->offset_blocks, 0, 0, 0, 0, &dif_opts);
920 [ # # ]: 0 : if (rc != 0) {
921 [ # # # # ]: 0 : fprintf(stderr, "Initialization of DIF context failed\n");
922 : 0 : return rc;
923 : : }
924 : :
925 [ # # ]: 0 : if (spdk_bdev_is_md_interleaved(bdev)) {
926 : 0 : rc = spdk_dif_verify(&task->iov, 1, job->io_size_blocks, &dif_ctx, &err_blk);
927 : : } else {
928 : 0 : struct iovec md_iov = {
929 : 0 : .iov_base = task->md_buf,
930 : 0 : .iov_len = spdk_bdev_get_md_size(bdev) * job->io_size_blocks,
931 : : };
932 : :
933 : 0 : rc = spdk_dix_verify(&task->iov, 1, &md_iov, job->io_size_blocks, &dif_ctx, &err_blk);
934 : : }
935 : :
936 [ # # ]: 0 : if (rc != 0) {
937 [ # # ]: 0 : fprintf(stderr, "DIF/DIX error detected. type=%d, offset=%" PRIu32 "\n",
938 [ # # ]: 0 : err_blk.err_type, err_blk.err_offset);
939 : : }
940 : :
941 : 0 : return rc;
942 : : }
943 : :
944 : : static void
945 : 49210377 : bdevperf_complete(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg)
946 : : {
947 : : struct bdevperf_job *job;
948 : 49210377 : struct bdevperf_task *task = cb_arg;
949 : : bool md_check;
950 : : uint64_t offset_in_ios;
951 : : int rc;
952 : :
953 : 49210377 : job = task->job;
954 : 49210377 : md_check = spdk_bdev_get_dif_type(job->bdev) == SPDK_DIF_DISABLE;
955 : :
956 [ + + + + ]: 49210377 : if (g_error_to_exit == true) {
957 : 983 : bdevperf_job_drain(job);
958 [ + + ]: 49209395 : } else if (!success) {
959 [ + + + + : 356165 : if (!job->reset && !job->continue_on_failure) {
- + + + ]
960 : 9 : bdevperf_job_drain(job);
961 : 9 : g_run_rc = -1;
962 : 9 : g_error_to_exit = true;
963 [ - + ]: 9 : printf("task offset: %" PRIu64 " on job bdev=%s fails\n",
964 : : task->offset_blocks, job->name);
965 : : }
966 [ + + + + : 48853238 : } else if (job->verify || job->reset) {
- + - + ]
967 [ - + ]: 17017252 : if (!verify_data(task->buf, job->buf_size,
968 : 8508626 : task->iov.iov_base, job->buf_size,
969 : 8508626 : spdk_bdev_get_block_size(job->bdev),
970 : : task->md_buf, spdk_bdev_io_get_md_buf(bdev_io),
971 : 8508626 : spdk_bdev_get_md_size(job->bdev),
972 : 8508626 : job->io_size_blocks, md_check)) {
973 [ # # ]: 0 : printf("Buffer mismatch! Target: %s Disk Offset: %" PRIu64 "\n", job->name, task->offset_blocks);
974 : 0 : bdevperf_job_drain(job);
975 : 0 : g_run_rc = -1;
976 : : }
977 [ - + ]: 40344604 : } else if (job->dif_check_flags != 0) {
978 [ # # # # ]: 0 : if (task->io_type == SPDK_BDEV_IO_TYPE_READ && spdk_bdev_get_md_size(job->bdev) != 0) {
979 : 0 : rc = bdevperf_verify_dif(task);
980 [ # # ]: 0 : if (rc != 0) {
981 [ # # ]: 0 : printf("DIF error detected. task offset: %" PRIu64 " on job bdev=%s\n",
982 : : task->offset_blocks, job->name);
983 : :
984 : 0 : success = false;
985 [ # # # # : 0 : if (!job->reset && !job->continue_on_failure) {
# # # # ]
986 : 0 : bdevperf_job_drain(job);
987 : 0 : g_run_rc = -1;
988 : 0 : g_error_to_exit = true;
989 : : }
990 : : }
991 : : }
992 : : }
993 : :
994 : 49210377 : job->current_queue_depth--;
995 : :
996 [ + + ]: 49210377 : if (success) {
997 : 48853366 : job->io_completed++;
998 : : } else {
999 : 357013 : job->io_failed++;
1000 : : }
1001 : :
1002 [ + + + + ]: 49210377 : if (job->verify) {
1003 [ - + - + ]: 8865273 : assert(task->offset_blocks / job->io_size_blocks >= job->ios_base);
1004 [ - + ]: 8865273 : offset_in_ios = task->offset_blocks / job->io_size_blocks - job->ios_base;
1005 : :
1006 [ - + ]: 8865273 : assert(spdk_bit_array_get(job->outstanding, offset_in_ios) == true);
1007 : 8865273 : spdk_bit_array_clear(job->outstanding, offset_in_ios);
1008 : : }
1009 : :
1010 : 49210377 : spdk_bdev_free_io(bdev_io);
1011 : :
1012 : : /*
1013 : : * is_draining indicates when time has expired for the test run
1014 : : * and we are just waiting for the previously submitted I/O
1015 : : * to complete. In this case, do not submit a new I/O to replace
1016 : : * the one just completed.
1017 : : */
1018 [ + + + + ]: 49210377 : if (!job->is_draining) {
1019 : 49078518 : bdevperf_submit_single(job, task);
1020 : : } else {
1021 : 131863 : bdevperf_end_task(task);
1022 : : }
1023 : 49210377 : }
1024 : :
1025 : : static void
1026 : 8510111 : bdevperf_verify_submit_read(void *cb_arg)
1027 : : {
1028 : : struct bdevperf_job *job;
1029 : 8510111 : struct bdevperf_task *task = cb_arg;
1030 : : int rc;
1031 : :
1032 : 8510111 : job = task->job;
1033 : :
1034 : 8510111 : task->iov.iov_base = task->verify_buf;
1035 : 8510111 : task->iov.iov_len = job->buf_size;
1036 : :
1037 : : /* Read the data back in */
1038 : 8510111 : rc = spdk_bdev_readv_blocks_with_md(job->bdev_desc, job->ch, &task->iov, 1, NULL,
1039 : : task->offset_blocks, job->io_size_blocks,
1040 : : bdevperf_complete, task);
1041 : :
1042 [ - + ]: 8510111 : if (rc == -ENOMEM) {
1043 : 0 : bdevperf_queue_io_wait_with_cb(task, bdevperf_verify_submit_read);
1044 [ - + ]: 8510111 : } else if (rc != 0) {
1045 [ # # ]: 0 : printf("Failed to submit read: %d\n", rc);
1046 : 0 : bdevperf_job_drain(job);
1047 : 0 : g_run_rc = rc;
1048 : : }
1049 : 8510111 : }
1050 : :
1051 : : static void
1052 : 8865273 : bdevperf_verify_write_complete(struct spdk_bdev_io *bdev_io, bool success,
1053 : : void *cb_arg)
1054 : : {
1055 [ + + ]: 8865273 : if (success) {
1056 : 8510111 : spdk_bdev_free_io(bdev_io);
1057 : 8510111 : bdevperf_verify_submit_read(cb_arg);
1058 : : } else {
1059 : 355162 : bdevperf_complete(bdev_io, success, cb_arg);
1060 : : }
1061 : 8865273 : }
1062 : :
1063 : : static void
1064 : 0 : bdevperf_zcopy_populate_complete(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg)
1065 : : {
1066 [ # # ]: 0 : if (!success) {
1067 : 0 : bdevperf_complete(bdev_io, success, cb_arg);
1068 : 0 : return;
1069 : : }
1070 : :
1071 : 0 : spdk_bdev_zcopy_end(bdev_io, false, bdevperf_complete, cb_arg);
1072 : : }
1073 : :
1074 : : static int
1075 : 0 : bdevperf_generate_dif(struct bdevperf_task *task)
1076 : : {
1077 : 0 : struct bdevperf_job *job = task->job;
1078 : 0 : struct spdk_bdev *bdev = job->bdev;
1079 : 0 : struct spdk_dif_ctx dif_ctx;
1080 : : int rc;
1081 : 0 : struct spdk_dif_ctx_init_ext_opts dif_opts;
1082 : :
1083 : 0 : dif_opts.size = SPDK_SIZEOF(&dif_opts, dif_pi_format);
1084 : 0 : dif_opts.dif_pi_format = SPDK_DIF_PI_FORMAT_16;
1085 : 0 : rc = spdk_dif_ctx_init(&dif_ctx,
1086 : : spdk_bdev_get_block_size(bdev),
1087 : : spdk_bdev_get_md_size(bdev),
1088 : 0 : spdk_bdev_is_md_interleaved(bdev),
1089 : 0 : spdk_bdev_is_dif_head_of_md(bdev),
1090 : : spdk_bdev_get_dif_type(bdev),
1091 : : job->dif_check_flags,
1092 : 0 : task->offset_blocks, 0, 0, 0, 0, &dif_opts);
1093 [ # # ]: 0 : if (rc != 0) {
1094 [ # # # # ]: 0 : fprintf(stderr, "Initialization of DIF context failed\n");
1095 : 0 : return rc;
1096 : : }
1097 : :
1098 [ # # ]: 0 : if (spdk_bdev_is_md_interleaved(bdev)) {
1099 : 0 : rc = spdk_dif_generate(&task->iov, 1, job->io_size_blocks, &dif_ctx);
1100 : : } else {
1101 : 0 : struct iovec md_iov = {
1102 : 0 : .iov_base = task->md_buf,
1103 : 0 : .iov_len = spdk_bdev_get_md_size(bdev) * job->io_size_blocks,
1104 : : };
1105 : :
1106 : 0 : rc = spdk_dix_generate(&task->iov, 1, &md_iov, job->io_size_blocks, &dif_ctx);
1107 : : }
1108 : :
1109 [ # # ]: 0 : if (rc != 0) {
1110 [ # # # # ]: 0 : fprintf(stderr, "Generation of DIF/DIX failed\n");
1111 : : }
1112 : :
1113 : 0 : return rc;
1114 : : }
1115 : :
1116 : : static void
1117 : 49448897 : bdevperf_submit_task(void *arg)
1118 : : {
1119 : 49448897 : struct bdevperf_task *task = arg;
1120 : 49448897 : struct bdevperf_job *job = task->job;
1121 : : struct spdk_bdev_desc *desc;
1122 : : struct spdk_io_channel *ch;
1123 : : spdk_bdev_io_completion_cb cb_fn;
1124 : : uint64_t offset_in_ios;
1125 : 49448897 : int rc = 0;
1126 : :
1127 : 49448897 : desc = job->bdev_desc;
1128 : 49448897 : ch = job->ch;
1129 : :
1130 [ + + + + : 49448897 : switch (task->io_type) {
+ - - ]
1131 : 17565011 : case SPDK_BDEV_IO_TYPE_WRITE:
1132 [ + + - + ]: 17565011 : if (spdk_bdev_get_md_size(job->bdev) != 0 && job->dif_check_flags != 0) {
1133 : 0 : rc = bdevperf_generate_dif(task);
1134 : : }
1135 [ + - ]: 17565011 : if (rc == 0) {
1136 [ + + + + : 17565011 : cb_fn = (job->verify || job->reset) ? bdevperf_verify_write_complete : bdevperf_complete;
- + - + ]
1137 : :
1138 [ - + - + ]: 17565011 : if (g_zcopy) {
1139 : 0 : spdk_bdev_zcopy_end(task->bdev_io, true, cb_fn, task);
1140 : 0 : return;
1141 : : } else {
1142 : 17565011 : rc = spdk_bdev_writev_blocks_with_md(desc, ch, &task->iov, 1,
1143 : : task->md_buf,
1144 : : task->offset_blocks,
1145 : : job->io_size_blocks,
1146 : : cb_fn, task);
1147 : : }
1148 : : }
1149 : 17565011 : break;
1150 : 1242294 : case SPDK_BDEV_IO_TYPE_FLUSH:
1151 : 1242294 : rc = spdk_bdev_flush_blocks(desc, ch, task->offset_blocks,
1152 : : job->io_size_blocks, bdevperf_complete, task);
1153 : 1242294 : break;
1154 : 674966 : case SPDK_BDEV_IO_TYPE_UNMAP:
1155 : 674966 : rc = spdk_bdev_unmap_blocks(desc, ch, task->offset_blocks,
1156 : : job->io_size_blocks, bdevperf_complete, task);
1157 : 674966 : break;
1158 : 1661962 : case SPDK_BDEV_IO_TYPE_WRITE_ZEROES:
1159 : 1661962 : rc = spdk_bdev_write_zeroes_blocks(desc, ch, task->offset_blocks,
1160 : : job->io_size_blocks, bdevperf_complete, task);
1161 : 1661962 : break;
1162 : 28304666 : case SPDK_BDEV_IO_TYPE_READ:
1163 [ - + - + ]: 28304666 : if (g_zcopy) {
1164 : 0 : rc = spdk_bdev_zcopy_start(desc, ch, NULL, 0, task->offset_blocks, job->io_size_blocks,
1165 : : true, bdevperf_zcopy_populate_complete, task);
1166 : : } else {
1167 : 28304666 : rc = spdk_bdev_readv_blocks_with_md(desc, ch, &task->iov, 1,
1168 : : task->md_buf,
1169 : : task->offset_blocks,
1170 : : job->io_size_blocks,
1171 : : bdevperf_complete, task);
1172 : : }
1173 : 28304666 : break;
1174 : 0 : case SPDK_BDEV_IO_TYPE_ABORT:
1175 : 0 : rc = spdk_bdev_abort(desc, ch, task->task_to_abort, bdevperf_abort_complete, task);
1176 : 0 : break;
1177 : 0 : default:
1178 : 0 : assert(false);
1179 : : rc = -EINVAL;
1180 : : break;
1181 : : }
1182 : :
1183 [ + + ]: 49448897 : if (rc == -ENOMEM) {
1184 : 238520 : bdevperf_queue_io_wait_with_cb(task, bdevperf_submit_task);
1185 : 238520 : return;
1186 [ - + ]: 49210377 : } else if (rc != 0) {
1187 [ # # ]: 0 : printf("Failed to submit bdev_io: %d\n", rc);
1188 [ # # # # ]: 0 : if (job->verify) {
1189 [ # # # # ]: 0 : assert(task->offset_blocks / job->io_size_blocks >= job->ios_base);
1190 [ # # ]: 0 : offset_in_ios = task->offset_blocks / job->io_size_blocks - job->ios_base;
1191 : :
1192 [ # # ]: 0 : assert(spdk_bit_array_get(job->outstanding, offset_in_ios) == true);
1193 : 0 : spdk_bit_array_clear(job->outstanding, offset_in_ios);
1194 : : }
1195 : 0 : bdevperf_job_drain(job);
1196 : 0 : g_run_rc = rc;
1197 : 0 : return;
1198 : : }
1199 : :
1200 : 49210377 : job->current_queue_depth++;
1201 : : }
1202 : :
1203 : : static void
1204 : 0 : bdevperf_zcopy_get_buf_complete(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg)
1205 : : {
1206 : 0 : struct bdevperf_task *task = cb_arg;
1207 : 0 : struct bdevperf_job *job = task->job;
1208 : 0 : struct iovec *iovs;
1209 : 0 : int iovcnt;
1210 : :
1211 [ # # ]: 0 : if (!success) {
1212 : 0 : bdevperf_job_drain(job);
1213 : 0 : g_run_rc = -1;
1214 : 0 : return;
1215 : : }
1216 : :
1217 : 0 : task->bdev_io = bdev_io;
1218 : 0 : task->io_type = SPDK_BDEV_IO_TYPE_WRITE;
1219 : :
1220 [ # # # # : 0 : if (job->verify || job->reset) {
# # # # ]
1221 : : /* When job->verify or job->reset is enabled, task->buf is used for
1222 : : * verification of read after write. For write I/O, when zcopy APIs
1223 : : * are used, task->buf cannot be used, and data must be written to
1224 : : * the data buffer allocated underneath bdev layer instead.
1225 : : * Hence we copy task->buf to the allocated data buffer here.
1226 : : */
1227 : 0 : spdk_bdev_io_get_iovec(bdev_io, &iovs, &iovcnt);
1228 [ # # ]: 0 : assert(iovcnt == 1);
1229 [ # # ]: 0 : assert(iovs != NULL);
1230 : :
1231 : 0 : copy_data(iovs[0].iov_base, iovs[0].iov_len, task->buf, job->buf_size,
1232 : 0 : spdk_bdev_get_block_size(job->bdev),
1233 : : spdk_bdev_io_get_md_buf(bdev_io), task->md_buf,
1234 : 0 : spdk_bdev_get_md_size(job->bdev), job->io_size_blocks);
1235 : : }
1236 : :
1237 : 0 : bdevperf_submit_task(task);
1238 : : }
1239 : :
1240 : : static void
1241 : 0 : bdevperf_prep_zcopy_write_task(void *arg)
1242 : : {
1243 : 0 : struct bdevperf_task *task = arg;
1244 : 0 : struct bdevperf_job *job = task->job;
1245 : : int rc;
1246 : :
1247 : 0 : rc = spdk_bdev_zcopy_start(job->bdev_desc, job->ch, NULL, 0,
1248 : : task->offset_blocks, job->io_size_blocks,
1249 : : false, bdevperf_zcopy_get_buf_complete, task);
1250 [ # # ]: 0 : if (rc != 0) {
1251 [ # # ]: 0 : assert(rc == -ENOMEM);
1252 : 0 : bdevperf_queue_io_wait_with_cb(task, bdevperf_prep_zcopy_write_task);
1253 : 0 : return;
1254 : : }
1255 : :
1256 : 0 : job->current_queue_depth++;
1257 : : }
1258 : :
1259 : : static struct bdevperf_task *
1260 : 131863 : bdevperf_job_get_task(struct bdevperf_job *job)
1261 : : {
1262 : : struct bdevperf_task *task;
1263 : :
1264 : 131863 : task = TAILQ_FIRST(&job->task_list);
1265 [ - + ]: 131863 : if (!task) {
1266 [ # # ]: 0 : printf("Task allocation failed\n");
1267 : 0 : abort();
1268 : : }
1269 : :
1270 [ + + ]: 131863 : TAILQ_REMOVE(&job->task_list, task, link);
1271 : 131863 : return task;
1272 : : }
1273 : :
1274 : : static void
1275 : 49210377 : bdevperf_submit_single(struct bdevperf_job *job, struct bdevperf_task *task)
1276 : : {
1277 : : uint64_t offset_in_ios;
1278 : : uint64_t rand_value;
1279 : : uint32_t first_clear;
1280 : :
1281 [ - + ]: 49210377 : if (job->zipf) {
1282 : 0 : offset_in_ios = spdk_zipf_generate(job->zipf);
1283 [ + + + + ]: 49210377 : } else if (job->is_random) {
1284 : : /* RAND_MAX is only INT32_MAX, so use 2 calls to rand_r to
1285 : : * get a large enough value to ensure we are issuing I/O
1286 : : * uniformly across the whole bdev.
1287 : : */
1288 : 27666220 : rand_value = (uint64_t)rand_r(&job->seed) * RAND_MAX + rand_r(&job->seed);
1289 [ - + ]: 27666220 : offset_in_ios = rand_value % job->size_in_ios;
1290 : :
1291 [ - + - + ]: 27666220 : if (g_random_map) {
1292 : : /* Make sure, that the offset does not exceed the maximum size
1293 : : * of the bit array (verified during job creation)
1294 : : */
1295 [ # # ]: 0 : assert(offset_in_ios < UINT32_MAX);
1296 : :
1297 : 0 : first_clear = spdk_bit_array_find_first_clear(job->random_map, (uint32_t)offset_in_ios);
1298 : :
1299 [ # # ]: 0 : if (first_clear == UINT32_MAX) {
1300 : 0 : first_clear = spdk_bit_array_find_first_clear(job->random_map, 0);
1301 : :
1302 [ # # ]: 0 : if (first_clear == UINT32_MAX) {
1303 : : /* If there are no more clear bits in the array, we start over
1304 : : * and select the previously selected random value.
1305 : : */
1306 : 0 : spdk_bit_array_clear_mask(job->random_map);
1307 : 0 : first_clear = (uint32_t)offset_in_ios;
1308 : : }
1309 : : }
1310 : :
1311 : 0 : spdk_bit_array_set(job->random_map, first_clear);
1312 : :
1313 : 0 : offset_in_ios = first_clear;
1314 : : }
1315 : : } else {
1316 : 21544167 : offset_in_ios = job->offset_in_ios++;
1317 [ + + ]: 21544167 : if (job->offset_in_ios == job->size_in_ios) {
1318 : 3002 : job->offset_in_ios = 0;
1319 : : }
1320 : :
1321 : : /* Increment of offset_in_ios if there's already an outstanding IO
1322 : : * to that location. We only need this with job->verify as random
1323 : : * offsets are not supported with job->verify at this time.
1324 : : */
1325 [ + + + + ]: 21544167 : if (job->verify) {
1326 [ - + ]: 8865273 : assert(spdk_bit_array_find_first_clear(job->outstanding, 0) != UINT32_MAX);
1327 : :
1328 [ + + ]: 8914451 : while (spdk_bit_array_get(job->outstanding, offset_in_ios)) {
1329 : 49178 : offset_in_ios = job->offset_in_ios++;
1330 [ + + ]: 49178 : if (job->offset_in_ios == job->size_in_ios) {
1331 : 628 : job->offset_in_ios = 0;
1332 : : }
1333 : : }
1334 : 8865273 : spdk_bit_array_set(job->outstanding, offset_in_ios);
1335 : : }
1336 : : }
1337 : :
1338 : : /* For multi-thread to same job, offset_in_ios is relative
1339 : : * to the LBA range assigned for that job. job->offset_blocks
1340 : : * is absolute (entire bdev LBA range).
1341 : : */
1342 : 49210377 : task->offset_blocks = (offset_in_ios + job->ios_base) * job->io_size_blocks;
1343 : :
1344 [ - + + + ]: 49210377 : if (job->flush) {
1345 : 1242294 : task->io_type = SPDK_BDEV_IO_TYPE_FLUSH;
1346 [ - + + + ]: 47968083 : } else if (job->unmap) {
1347 : 580989 : task->io_type = SPDK_BDEV_IO_TYPE_UNMAP;
1348 [ + + + + ]: 47387094 : } else if (job->write_zeroes) {
1349 : 1661962 : task->io_type = SPDK_BDEV_IO_TYPE_WRITE_ZEROES;
1350 [ + + ]: 45725133 : } else if ((job->rw_percentage == 100) ||
1351 [ + + + + ]: 22490825 : (job->rw_percentage != 0 && ((rand_r(&job->seed) % 100) < job->rw_percentage))) {
1352 [ - + - + ]: 28209549 : assert(!job->verify);
1353 : 28209549 : task->io_type = SPDK_BDEV_IO_TYPE_READ;
1354 [ + + + - ]: 28209549 : if (!g_zcopy) {
1355 : 28209549 : task->iov.iov_base = task->buf;
1356 : 28209549 : task->iov.iov_len = job->buf_size;
1357 : : }
1358 : : } else {
1359 [ + + + + : 17515585 : if (job->verify || job->reset || g_unique_writes) {
+ + + - -
+ + + ]
1360 [ - + ]: 8875552 : generate_data(job, task->buf, task->md_buf, g_unique_writes);
1361 : : }
1362 [ - + - + ]: 17515585 : if (g_zcopy) {
1363 : 0 : bdevperf_prep_zcopy_write_task(task);
1364 : 0 : return;
1365 : : } else {
1366 : 17515585 : task->iov.iov_base = task->buf;
1367 : 17515585 : task->iov.iov_len = job->buf_size;
1368 : 17515585 : task->io_type = SPDK_BDEV_IO_TYPE_WRITE;
1369 : : }
1370 : : }
1371 : :
1372 : 49210377 : bdevperf_submit_task(task);
1373 : : }
1374 : :
1375 : : static int reset_job(void *arg);
1376 : :
1377 : : static void
1378 : 0 : reset_cb(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg)
1379 : : {
1380 : 0 : struct bdevperf_task *task = cb_arg;
1381 : 0 : struct bdevperf_job *job = task->job;
1382 : :
1383 [ # # ]: 0 : if (!success) {
1384 [ # # ]: 0 : printf("Reset blockdev=%s failed\n", spdk_bdev_get_name(job->bdev));
1385 : 0 : bdevperf_job_drain(job);
1386 : 0 : g_run_rc = -1;
1387 : : }
1388 : :
1389 : 0 : TAILQ_INSERT_TAIL(&job->task_list, task, link);
1390 : 0 : spdk_bdev_free_io(bdev_io);
1391 : :
1392 : 0 : job->reset_timer = SPDK_POLLER_REGISTER(reset_job, job,
1393 : : 10 * SPDK_SEC_TO_USEC);
1394 : 0 : }
1395 : :
1396 : : static int
1397 : 0 : reset_job(void *arg)
1398 : : {
1399 : 0 : struct bdevperf_job *job = arg;
1400 : : struct bdevperf_task *task;
1401 : : int rc;
1402 : :
1403 : 0 : spdk_poller_unregister(&job->reset_timer);
1404 : :
1405 : : /* Do reset. */
1406 : 0 : task = bdevperf_job_get_task(job);
1407 : 0 : rc = spdk_bdev_reset(job->bdev_desc, job->ch,
1408 : : reset_cb, task);
1409 [ # # ]: 0 : if (rc) {
1410 [ # # ]: 0 : printf("Reset failed: %d\n", rc);
1411 : 0 : bdevperf_job_drain(job);
1412 : 0 : g_run_rc = -1;
1413 : : }
1414 : :
1415 : 0 : return -1;
1416 : : }
1417 : :
1418 : : static void
1419 : 0 : bdevperf_timeout_cb(void *cb_arg, struct spdk_bdev_io *bdev_io)
1420 : : {
1421 : 0 : struct bdevperf_job *job = cb_arg;
1422 : : struct bdevperf_task *task;
1423 : :
1424 : 0 : job->io_timeout++;
1425 : :
1426 [ # # # # : 0 : if (job->is_draining || !job->abort ||
# # # # ]
1427 [ # # ]: 0 : !spdk_bdev_io_type_supported(job->bdev, SPDK_BDEV_IO_TYPE_ABORT)) {
1428 : 0 : return;
1429 : : }
1430 : :
1431 : 0 : task = bdevperf_job_get_task(job);
1432 [ # # ]: 0 : if (task == NULL) {
1433 : 0 : return;
1434 : : }
1435 : :
1436 : 0 : task->task_to_abort = spdk_bdev_io_get_cb_arg(bdev_io);
1437 : 0 : task->io_type = SPDK_BDEV_IO_TYPE_ABORT;
1438 : :
1439 : 0 : bdevperf_submit_task(task);
1440 : : }
1441 : :
1442 : : static void
1443 : 1110 : bdevperf_job_run(void *ctx)
1444 : : {
1445 : 1110 : struct bdevperf_job *job = ctx;
1446 : : struct bdevperf_task *task;
1447 : : int i;
1448 : :
1449 : : /* Submit initial I/O for this job. Each time one
1450 : : * completes, another will be submitted. */
1451 : :
1452 : : /* Start a timer to stop this I/O chain when the run is over */
1453 : 1110 : job->run_timer = SPDK_POLLER_REGISTER(bdevperf_job_drain_timer, job, g_time_in_usec);
1454 [ - + + + ]: 1110 : if (job->reset) {
1455 : 1 : job->reset_timer = SPDK_POLLER_REGISTER(reset_job, job,
1456 : : 10 * SPDK_SEC_TO_USEC);
1457 : : }
1458 : :
1459 : 1110 : spdk_bdev_set_timeout(job->bdev_desc, g_timeout_in_sec, bdevperf_timeout_cb, job);
1460 : :
1461 [ + + ]: 132973 : for (i = 0; i < job->queue_depth; i++) {
1462 : 131863 : task = bdevperf_job_get_task(job);
1463 : 131863 : bdevperf_submit_single(job, task);
1464 : : }
1465 : 1110 : }
1466 : :
1467 : : static void
1468 : 80 : _performance_dump_done(void *ctx)
1469 : : {
1470 : 80 : struct bdevperf_aggregate_stats *stats = ctx;
1471 : : double average_latency;
1472 : :
1473 [ - + ]: 80 : printf("\r =================================================================================="
1474 : : "=================================\n");
1475 [ - + ]: 80 : printf("\r %-28s: %10s %10.2f %10.2f",
1476 : : "Total", "", stats->total_io_per_second, stats->total_mb_per_second);
1477 [ - + ]: 80 : printf(" %10.2f %8.2f",
1478 : : stats->total_failed_per_second, stats->total_timeout_per_second);
1479 : :
1480 : 80 : average_latency = ((double)stats->total_tsc / stats->total_io_completed) * SPDK_SEC_TO_USEC /
1481 : 80 : spdk_get_ticks_hz();
1482 [ - + ]: 80 : printf(" %10.2f %10.2f %10.2f\n", average_latency, stats->min_latency, stats->max_latency);
1483 : 80 : printf("\n");
1484 : :
1485 : 80 : fflush(stdout);
1486 : :
1487 : 80 : g_performance_dump_active = false;
1488 : :
1489 : 80 : free(stats);
1490 : 80 : }
1491 : :
1492 : : static void
1493 : 80 : _performance_dump(void *ctx)
1494 : : {
1495 : 80 : struct bdevperf_aggregate_stats *stats = ctx;
1496 : 80 : performance_dump_job(stats, stats->current_job, NULL);
1497 : :
1498 : : /* This assumes the jobs list is static after start up time.
1499 : : * That's true right now, but if that ever changed this would need a lock. */
1500 : 80 : stats->current_job = TAILQ_NEXT(stats->current_job, link);
1501 [ + - ]: 80 : if (stats->current_job == NULL) {
1502 : 80 : spdk_thread_send_msg(g_main_thread, _performance_dump_done, stats);
1503 : : } else {
1504 : 0 : spdk_thread_send_msg(stats->current_job->thread, _performance_dump, stats);
1505 : : }
1506 : 80 : }
1507 : :
1508 : : static int
1509 : 80 : performance_statistics_thread(void *arg)
1510 : : {
1511 : : struct bdevperf_aggregate_stats *stats;
1512 : :
1513 [ - + - + ]: 80 : if (g_performance_dump_active) {
1514 : 0 : return -1;
1515 : : }
1516 : :
1517 : 80 : g_performance_dump_active = true;
1518 : :
1519 : 80 : stats = calloc(1, sizeof(*stats));
1520 [ - + ]: 80 : if (stats == NULL) {
1521 : 0 : return -1;
1522 : : }
1523 : :
1524 : 80 : stats->min_latency = (double)UINT64_MAX;
1525 : :
1526 : 80 : g_show_performance_period_num++;
1527 : :
1528 : 80 : stats->io_time_in_usec = g_show_performance_period_num * g_show_performance_period_in_usec;
1529 : 80 : stats->ema_period = g_show_performance_ema_period;
1530 : :
1531 : : /* Iterate all of the jobs to gather stats
1532 : : * These jobs will not get removed here until a final performance dump is run,
1533 : : * so this should be safe without locking.
1534 : : */
1535 : 80 : stats->current_job = TAILQ_FIRST(&g_bdevperf.jobs);
1536 [ - + ]: 80 : if (stats->current_job == NULL) {
1537 : 0 : spdk_thread_send_msg(g_main_thread, _performance_dump_done, stats);
1538 : : } else {
1539 : 80 : spdk_thread_send_msg(stats->current_job->thread, _performance_dump, stats);
1540 : : }
1541 : :
1542 : 80 : return -1;
1543 : : }
1544 : :
1545 : : static void
1546 : 382 : bdevperf_test(void)
1547 : : {
1548 : : struct bdevperf_job *job;
1549 : :
1550 : : /* Start a timer to dump performance numbers */
1551 : 382 : g_start_tsc = spdk_get_ticks();
1552 [ + + + - ]: 382 : if (g_show_performance_real_time && !g_perf_timer) {
1553 [ - + ]: 8 : printf("%*s\n", 107, "Latency(us)");
1554 [ - + ]: 8 : printf("\r %-*s: %10s %10s %10s %10s %8s %10s %10s %10s\n",
1555 : : 28, "Device Information", "runtime(s)", "IOPS", "MiB/s", "Fail/s", "TO/s", "Average", "min", "max");
1556 : :
1557 : 8 : g_perf_timer = SPDK_POLLER_REGISTER(performance_statistics_thread, NULL,
1558 : : g_show_performance_period_in_usec);
1559 : : }
1560 : : /* Iterate jobs to start all I/O */
1561 [ + + ]: 1492 : TAILQ_FOREACH(job, &g_bdevperf.jobs, link) {
1562 : 1110 : g_bdevperf.running_jobs++;
1563 : 1110 : spdk_thread_send_msg(job->thread, bdevperf_job_run, job);
1564 : : }
1565 : : /* If no jobs/bdevs were defined, send RPC error response */
1566 [ + + ]: 382 : if (g_bdevperf.running_jobs == 0) {
1567 [ - + ]: 1 : if (g_request) {
1568 : 0 : spdk_jsonrpc_send_error_response_fmt(g_request, SPDK_JSONRPC_ERROR_INVALID_PARAMS,
1569 : : "No jobs defined or bdevs created");
1570 : 0 : g_request = NULL;
1571 : : }
1572 : : } else {
1573 [ - + ]: 381 : printf("Running I/O for %" PRIu64 " seconds...\n", g_time_in_usec / (uint64_t)SPDK_SEC_TO_USEC);
1574 : 381 : fflush(stdout);
1575 : : }
1576 : 382 : }
1577 : :
1578 : : static void
1579 : 146 : bdevperf_bdev_removed(enum spdk_bdev_event_type type, struct spdk_bdev *bdev, void *event_ctx)
1580 : : {
1581 : 146 : struct bdevperf_job *job = event_ctx;
1582 : :
1583 [ + + ]: 146 : if (SPDK_BDEV_EVENT_REMOVE == type) {
1584 : 145 : bdevperf_job_drain(job);
1585 : : }
1586 : 146 : }
1587 : :
1588 : : static void
1589 : 1163 : bdevperf_histogram_status_cb(void *cb_arg, int status)
1590 : : {
1591 [ - + ]: 1163 : if (status != 0) {
1592 : 0 : g_run_rc = status;
1593 [ # # # # ]: 0 : if (g_continue_on_failure == false) {
1594 : 0 : g_error_to_exit = true;
1595 : : }
1596 : : }
1597 : :
1598 [ + + ]: 1163 : if (--g_bdev_count == 0) {
1599 [ + - ]: 382 : if (g_run_rc == 0) {
1600 : : /* Ready to run the test */
1601 : 382 : bdevperf_test();
1602 : : } else {
1603 : 0 : bdevperf_test_done(NULL);
1604 : : }
1605 : : }
1606 : 1163 : }
1607 : :
1608 : : static uint32_t g_construct_job_count = 0;
1609 : :
1610 : : static int
1611 : 781 : _bdevperf_enable_histogram(void *ctx, struct spdk_bdev *bdev)
1612 : : {
1613 : 781 : bool *enable = ctx;
1614 : :
1615 : 781 : g_bdev_count++;
1616 : :
1617 [ - + ]: 781 : spdk_bdev_histogram_enable(bdev, bdevperf_histogram_status_cb, NULL, *enable);
1618 : :
1619 : 781 : return 0;
1620 : : }
1621 : :
1622 : : static void
1623 : 382 : bdevperf_enable_histogram(bool enable)
1624 : : {
1625 : : struct spdk_bdev *bdev;
1626 : : int rc;
1627 : :
1628 : : /* increment initial g_bdev_count so that it will never reach 0 in the middle of iteration */
1629 : 382 : g_bdev_count = 1;
1630 : :
1631 [ + + ]: 382 : if (g_job_bdev_name != NULL) {
1632 : 111 : bdev = spdk_bdev_get_by_name(g_job_bdev_name);
1633 [ + - ]: 111 : if (bdev) {
1634 : 111 : rc = _bdevperf_enable_histogram(&enable, bdev);
1635 : : } else {
1636 [ # # # # ]: 0 : fprintf(stderr, "Unable to find bdev '%s'\n", g_job_bdev_name);
1637 : 0 : rc = -1;
1638 : : }
1639 : : } else {
1640 : 271 : rc = spdk_for_each_bdev_leaf(&enable, _bdevperf_enable_histogram);
1641 : : }
1642 : :
1643 : 382 : bdevperf_histogram_status_cb(NULL, rc);
1644 : 382 : }
1645 : :
1646 : : static void
1647 : 1492 : _bdevperf_construct_job_done(void *ctx)
1648 : : {
1649 [ + + ]: 1492 : if (--g_construct_job_count == 0) {
1650 [ - + ]: 382 : if (g_run_rc != 0) {
1651 : : /* Something failed. */
1652 : 0 : bdevperf_test_done(NULL);
1653 : 0 : return;
1654 : : }
1655 : :
1656 : : /* always enable histogram. */
1657 : 382 : bdevperf_enable_histogram(true);
1658 [ - + ]: 1110 : } else if (g_run_rc != 0) {
1659 : : /* Reset error as some jobs constructed right */
1660 : 0 : g_run_rc = 0;
1661 [ # # # # ]: 0 : if (g_continue_on_failure == false) {
1662 : 0 : g_error_to_exit = true;
1663 : : }
1664 : : }
1665 : : }
1666 : :
1667 : : /* Checkformat will not allow to use inlined type,
1668 : : this is a workaround */
1669 : : typedef struct spdk_thread *spdk_thread_t;
1670 : :
1671 : : static spdk_thread_t
1672 : 1090 : construct_job_thread(struct spdk_cpuset *cpumask, const char *tag)
1673 : : {
1674 : 650 : struct spdk_cpuset tmp;
1675 : :
1676 : : /* This function runs on the main thread. */
1677 [ - + ]: 1090 : assert(g_main_thread == spdk_get_thread());
1678 : :
1679 : : /* Handle default mask */
1680 [ - + ]: 1090 : if (spdk_cpuset_count(cpumask) == 0) {
1681 : 0 : cpumask = &g_all_cpuset;
1682 : : }
1683 : :
1684 : : /* Warn user that mask might need to be changed */
1685 : 1090 : spdk_cpuset_copy(&tmp, cpumask);
1686 : 1090 : spdk_cpuset_or(&tmp, &g_all_cpuset);
1687 [ + + ]: 1090 : if (!spdk_cpuset_equal(&tmp, &g_all_cpuset)) {
1688 [ - + - + ]: 75 : fprintf(stderr, "cpumask for '%s' is too big\n", tag);
1689 : : }
1690 : :
1691 : 1090 : return spdk_thread_create(tag, cpumask);
1692 : : }
1693 : :
1694 : : static uint32_t
1695 : 1015 : _get_next_core(void)
1696 : : {
1697 : : static uint32_t current_core = SPDK_ENV_LCORE_ID_ANY;
1698 : :
1699 [ + + ]: 1015 : if (current_core == SPDK_ENV_LCORE_ID_ANY) {
1700 : 357 : current_core = spdk_env_get_first_core();
1701 : 357 : return current_core;
1702 : : }
1703 : :
1704 : 658 : current_core = spdk_env_get_next_core(current_core);
1705 [ + + ]: 658 : if (current_core == SPDK_ENV_LCORE_ID_ANY) {
1706 : 382 : current_core = spdk_env_get_first_core();
1707 : : }
1708 : :
1709 : 658 : return current_core;
1710 : : }
1711 : :
1712 : : static void
1713 : 1110 : _bdevperf_construct_job(void *ctx)
1714 : : {
1715 : 1110 : struct bdevperf_job *job = ctx;
1716 : : int rc;
1717 : :
1718 : 1110 : rc = spdk_bdev_open_ext(spdk_bdev_get_name(job->bdev), true, bdevperf_bdev_removed, job,
1719 : : &job->bdev_desc);
1720 [ - + ]: 1110 : if (rc != 0) {
1721 : 0 : SPDK_ERRLOG("Could not open leaf bdev %s, error=%d\n", spdk_bdev_get_name(job->bdev), rc);
1722 : 0 : g_run_rc = -EINVAL;
1723 : 0 : goto end;
1724 : : }
1725 : :
1726 [ - + - + ]: 1110 : if (g_zcopy) {
1727 [ # # ]: 0 : if (!spdk_bdev_io_type_supported(job->bdev, SPDK_BDEV_IO_TYPE_ZCOPY)) {
1728 [ # # ]: 0 : printf("Test requires ZCOPY but bdev module does not support ZCOPY\n");
1729 : 0 : g_run_rc = -ENOTSUP;
1730 : 0 : goto end;
1731 : : }
1732 : : }
1733 : :
1734 : 1110 : job->ch = spdk_bdev_get_io_channel(job->bdev_desc);
1735 [ + - ]: 1110 : if (!job->ch) {
1736 : 0 : SPDK_ERRLOG("Could not get io_channel for device %s, error=%d\n", spdk_bdev_get_name(job->bdev),
1737 : : rc);
1738 : 0 : spdk_bdev_close(job->bdev_desc);
1739 [ # # ]: 0 : TAILQ_REMOVE(&g_bdevperf.jobs, job, link);
1740 : 0 : g_run_rc = -ENOMEM;
1741 : 0 : goto end;
1742 : : }
1743 : :
1744 : 1110 : end:
1745 : 1110 : spdk_thread_send_msg(g_main_thread, _bdevperf_construct_job_done, NULL);
1746 : 1110 : }
1747 : :
1748 : : static void
1749 : 1110 : job_init_rw(struct bdevperf_job *job, enum job_config_rw rw)
1750 : : {
1751 [ + + + + : 1110 : switch (rw) {
+ + + + +
+ + - ]
1752 : 47 : case JOB_CONFIG_RW_READ:
1753 : 47 : job->rw_percentage = 100;
1754 : 47 : break;
1755 : 26 : case JOB_CONFIG_RW_WRITE:
1756 : 26 : job->rw_percentage = 0;
1757 : 26 : break;
1758 : 69 : case JOB_CONFIG_RW_RANDREAD:
1759 : 69 : job->is_random = true;
1760 : 69 : job->rw_percentage = 100;
1761 : 69 : job->seed = rand();
1762 : 69 : break;
1763 : 23 : case JOB_CONFIG_RW_RANDWRITE:
1764 : 23 : job->is_random = true;
1765 : 23 : job->rw_percentage = 0;
1766 : 23 : job->seed = rand();
1767 : 23 : break;
1768 : 40 : case JOB_CONFIG_RW_RW:
1769 : 40 : job->is_random = false;
1770 : 40 : break;
1771 : 115 : case JOB_CONFIG_RW_RANDRW:
1772 : 115 : job->is_random = true;
1773 : 115 : job->seed = rand();
1774 : 115 : break;
1775 : 1 : case JOB_CONFIG_RW_RESET:
1776 : : /* Reset shares the flow with verify. */
1777 : 1 : job->reset = true;
1778 : : /* fallthrough */
1779 : 644 : case JOB_CONFIG_RW_VERIFY:
1780 : 644 : job->verify = true;
1781 : : /* For verify flow read is done on write completion
1782 : : * callback only, rw_percentage shall not be used. */
1783 : 644 : job->rw_percentage = 0;
1784 : 644 : break;
1785 : 9 : case JOB_CONFIG_RW_UNMAP:
1786 : 9 : job->unmap = true;
1787 : 9 : break;
1788 : 8 : case JOB_CONFIG_RW_FLUSH:
1789 : 8 : job->flush = true;
1790 : 8 : break;
1791 : 129 : case JOB_CONFIG_RW_WRITE_ZEROES:
1792 : 129 : job->write_zeroes = true;
1793 : 129 : break;
1794 : : }
1795 : 1110 : }
1796 : :
1797 : : static int
1798 : 1110 : bdevperf_construct_job(struct spdk_bdev *bdev, struct job_config *config,
1799 : : struct spdk_thread *thread)
1800 : : {
1801 : : struct bdevperf_job *job;
1802 : : struct bdevperf_task *task;
1803 : : int block_size, data_block_size;
1804 : : int rc;
1805 : : int task_num, n;
1806 : :
1807 : 1110 : block_size = spdk_bdev_get_block_size(bdev);
1808 : 1110 : data_block_size = spdk_bdev_get_data_block_size(bdev);
1809 : :
1810 : 1110 : job = calloc(1, sizeof(struct bdevperf_job));
1811 [ - + ]: 1110 : if (!job) {
1812 [ # # # # ]: 0 : fprintf(stderr, "Unable to allocate memory for new job.\n");
1813 : 0 : return -ENOMEM;
1814 : : }
1815 : :
1816 [ - + ]: 1110 : job->name = strdup(spdk_bdev_get_name(bdev));
1817 [ - + ]: 1110 : if (!job->name) {
1818 [ # # # # ]: 0 : fprintf(stderr, "Unable to allocate memory for job name.\n");
1819 : 0 : bdevperf_job_free(job);
1820 : 0 : return -ENOMEM;
1821 : : }
1822 : :
1823 : 1110 : job->workload_type = config->rw;
1824 : 1110 : job->io_size = config->bs;
1825 : 1110 : job->rw_percentage = config->rwmixread;
1826 [ - + ]: 1110 : job->continue_on_failure = g_continue_on_failure;
1827 : 1110 : job->queue_depth = config->iodepth;
1828 : 1110 : job->bdev = bdev;
1829 [ - + ]: 1110 : job->io_size_blocks = job->io_size / data_block_size;
1830 : 1110 : job->buf_size = job->io_size_blocks * block_size;
1831 [ - + ]: 1110 : job->abort = g_abort;
1832 : 1110 : job_init_rw(job, config->rw);
1833 : :
1834 [ - + - + ]: 1110 : if ((job->io_size % data_block_size) != 0) {
1835 : 0 : SPDK_ERRLOG("IO size (%d) is not multiples of data block size of bdev %s (%"PRIu32")\n",
1836 : : job->io_size, spdk_bdev_get_name(bdev), data_block_size);
1837 : 0 : bdevperf_job_free(job);
1838 : 0 : return -ENOTSUP;
1839 : : }
1840 : :
1841 [ - + + + : 1110 : if (job->unmap && !spdk_bdev_io_type_supported(bdev, SPDK_BDEV_IO_TYPE_UNMAP)) {
- + ]
1842 [ # # ]: 0 : printf("Skipping %s because it does not support unmap\n", spdk_bdev_get_name(bdev));
1843 : 0 : bdevperf_job_free(job);
1844 : 0 : return -ENOTSUP;
1845 : : }
1846 : :
1847 [ - + ]: 1110 : if (spdk_bdev_is_dif_check_enabled(bdev, SPDK_DIF_CHECK_TYPE_REFTAG)) {
1848 : 0 : job->dif_check_flags |= SPDK_DIF_FLAGS_REFTAG_CHECK;
1849 : : }
1850 [ - + ]: 1110 : if (spdk_bdev_is_dif_check_enabled(bdev, SPDK_DIF_CHECK_TYPE_GUARD)) {
1851 : 0 : job->dif_check_flags |= SPDK_DIF_FLAGS_GUARD_CHECK;
1852 : : }
1853 : :
1854 : 1110 : job->offset_in_ios = 0;
1855 : :
1856 [ + + ]: 1110 : if (config->length != 0) {
1857 : : /* Use subset of disk */
1858 [ - + ]: 548 : job->size_in_ios = config->length / job->io_size_blocks;
1859 [ - + ]: 548 : job->ios_base = config->offset / job->io_size_blocks;
1860 : : } else {
1861 : : /* Use whole disk */
1862 [ - + ]: 562 : job->size_in_ios = spdk_bdev_get_num_blocks(bdev) / job->io_size_blocks;
1863 : 562 : job->ios_base = 0;
1864 : : }
1865 : :
1866 [ + + + + : 1110 : if (job->is_random && g_zipf_theta > 0) {
- + ]
1867 : 0 : job->zipf = spdk_zipf_create(job->size_in_ios, g_zipf_theta, 0);
1868 : : }
1869 : :
1870 [ + + + + ]: 1110 : if (job->verify) {
1871 [ - + ]: 644 : if (job->size_in_ios >= UINT32_MAX) {
1872 : 0 : SPDK_ERRLOG("Due to constraints of verify operation, the job storage capacity is too large\n");
1873 : 0 : bdevperf_job_free(job);
1874 : 0 : return -ENOMEM;
1875 : : }
1876 : 644 : job->outstanding = spdk_bit_array_create(job->size_in_ios);
1877 [ - + ]: 644 : if (job->outstanding == NULL) {
1878 : 0 : SPDK_ERRLOG("Could not create outstanding array bitmap for bdev %s\n",
1879 : : spdk_bdev_get_name(bdev));
1880 : 0 : bdevperf_job_free(job);
1881 : 0 : return -ENOMEM;
1882 : : }
1883 [ + + ]: 644 : if (job->queue_depth > (int)job->size_in_ios) {
1884 : 90 : SPDK_WARNLOG("Due to constraints of verify job, queue depth (-q, %d) can't exceed the number of IO "
1885 : : "requests which can be submitted to the bdev %s simultaneously (%"PRIu64"). "
1886 : : "Queue depth is limited to %"PRIu64"\n",
1887 : : job->queue_depth, job->name, job->size_in_ios, job->size_in_ios);
1888 : 90 : job->queue_depth = (int)job->size_in_ios;
1889 : : }
1890 : : }
1891 : :
1892 : 1110 : job->histogram = spdk_histogram_data_alloc();
1893 [ - + ]: 1110 : if (job->histogram == NULL) {
1894 [ # # # # ]: 0 : fprintf(stderr, "Failed to allocate histogram\n");
1895 : 0 : bdevperf_job_free(job);
1896 : 0 : return -ENOMEM;
1897 : : }
1898 : :
1899 : 1110 : TAILQ_INIT(&job->task_list);
1900 : :
1901 [ - + - + ]: 1110 : if (g_random_map) {
1902 [ # # ]: 0 : if (job->size_in_ios >= UINT32_MAX) {
1903 : 0 : SPDK_ERRLOG("Due to constraints of the random map, the job storage capacity is too large\n");
1904 : 0 : bdevperf_job_free(job);
1905 : 0 : return -ENOMEM;
1906 : : }
1907 : 0 : job->random_map = spdk_bit_array_create(job->size_in_ios);
1908 [ # # ]: 0 : if (job->random_map == NULL) {
1909 : 0 : SPDK_ERRLOG("Could not create random_map array bitmap for bdev %s\n",
1910 : : spdk_bdev_get_name(bdev));
1911 : 0 : bdevperf_job_free(job);
1912 : 0 : return -ENOMEM;
1913 : : }
1914 : : }
1915 : :
1916 : 1110 : task_num = job->queue_depth;
1917 [ - + + + ]: 1110 : if (job->reset) {
1918 : 1 : task_num += 1;
1919 : : }
1920 [ - + - + ]: 1110 : if (job->abort) {
1921 : 0 : task_num += job->queue_depth;
1922 : : }
1923 : :
1924 : 1110 : TAILQ_INSERT_TAIL(&g_bdevperf.jobs, job, link);
1925 : :
1926 [ + + ]: 132974 : for (n = 0; n < task_num; n++) {
1927 : 131864 : task = calloc(1, sizeof(struct bdevperf_task));
1928 [ - + ]: 131864 : if (!task) {
1929 [ # # # # ]: 0 : fprintf(stderr, "Failed to allocate task from memory\n");
1930 : 0 : spdk_zipf_free(&job->zipf);
1931 : 0 : return -ENOMEM;
1932 : : }
1933 : :
1934 : 131864 : task->buf = spdk_zmalloc(job->buf_size, spdk_bdev_get_buf_align(job->bdev), NULL,
1935 : : SPDK_ENV_LCORE_ID_ANY, SPDK_MALLOC_DMA);
1936 [ - + ]: 131864 : if (!task->buf) {
1937 [ # # # # ]: 0 : fprintf(stderr, "Cannot allocate buf for task=%p\n", task);
1938 : 0 : spdk_zipf_free(&job->zipf);
1939 : 0 : free(task);
1940 : 0 : return -ENOMEM;
1941 : : }
1942 : :
1943 [ + + + + : 131864 : if (job->verify && job->buf_size > SPDK_BDEV_LARGE_BUF_MAX_SIZE) {
- + ]
1944 : 0 : task->verify_buf = spdk_zmalloc(job->buf_size, spdk_bdev_get_buf_align(job->bdev), NULL,
1945 : : SPDK_ENV_LCORE_ID_ANY, SPDK_MALLOC_DMA);
1946 [ # # ]: 0 : if (!task->verify_buf) {
1947 [ # # # # ]: 0 : fprintf(stderr, "Cannot allocate buf_verify for task=%p\n", task);
1948 : 0 : spdk_free(task->buf);
1949 : 0 : spdk_zipf_free(&job->zipf);
1950 : 0 : free(task);
1951 : 0 : return -ENOMEM;
1952 : : }
1953 : :
1954 : : }
1955 : :
1956 [ + + ]: 131864 : if (spdk_bdev_is_md_separate(job->bdev)) {
1957 : 3840 : task->md_buf = spdk_zmalloc(job->io_size_blocks *
1958 : 1920 : spdk_bdev_get_md_size(job->bdev), 0, NULL,
1959 : : SPDK_ENV_LCORE_ID_ANY, SPDK_MALLOC_DMA);
1960 [ - + ]: 1920 : if (!task->md_buf) {
1961 [ # # # # ]: 0 : fprintf(stderr, "Cannot allocate md buf for task=%p\n", task);
1962 : 0 : spdk_zipf_free(&job->zipf);
1963 : 0 : spdk_free(task->verify_buf);
1964 : 0 : spdk_free(task->buf);
1965 : 0 : free(task);
1966 : 0 : return -ENOMEM;
1967 : : }
1968 : : }
1969 : :
1970 : 131864 : task->job = job;
1971 : 131864 : TAILQ_INSERT_TAIL(&job->task_list, task, link);
1972 : : }
1973 : :
1974 : 1110 : job->thread = thread;
1975 : :
1976 : 1110 : g_construct_job_count++;
1977 : :
1978 : 1110 : rc = spdk_thread_send_msg(thread, _bdevperf_construct_job, job);
1979 [ - + ]: 1110 : assert(rc == 0);
1980 : :
1981 : 1110 : return rc;
1982 : : }
1983 : :
1984 : : static int
1985 : 1120 : parse_rw(const char *str, enum job_config_rw ret)
1986 : : {
1987 [ + + ]: 1120 : if (str == NULL) {
1988 : 75 : return ret;
1989 : : }
1990 : :
1991 [ + + + + ]: 1045 : if (!strcmp(str, "read")) {
1992 : 17 : ret = JOB_CONFIG_RW_READ;
1993 [ + + + + ]: 1028 : } else if (!strcmp(str, "randread")) {
1994 : 69 : ret = JOB_CONFIG_RW_RANDREAD;
1995 [ + + + + ]: 959 : } else if (!strcmp(str, "write")) {
1996 : 26 : ret = JOB_CONFIG_RW_WRITE;
1997 [ - + + + ]: 933 : } else if (!strcmp(str, "randwrite")) {
1998 : 23 : ret = JOB_CONFIG_RW_RANDWRITE;
1999 [ + + + + ]: 910 : } else if (!strcmp(str, "verify")) {
2000 : 643 : ret = JOB_CONFIG_RW_VERIFY;
2001 [ - + + + ]: 267 : } else if (!strcmp(str, "reset")) {
2002 : 1 : ret = JOB_CONFIG_RW_RESET;
2003 [ - + + + ]: 266 : } else if (!strcmp(str, "unmap")) {
2004 : 9 : ret = JOB_CONFIG_RW_UNMAP;
2005 [ + + + + ]: 257 : } else if (!strcmp(str, "write_zeroes")) {
2006 : 129 : ret = JOB_CONFIG_RW_WRITE_ZEROES;
2007 [ - + + + ]: 128 : } else if (!strcmp(str, "flush")) {
2008 : 8 : ret = JOB_CONFIG_RW_FLUSH;
2009 [ + + + + ]: 120 : } else if (!strcmp(str, "rw")) {
2010 : 5 : ret = JOB_CONFIG_RW_RW;
2011 [ + + + - ]: 115 : } else if (!strcmp(str, "randrw")) {
2012 : 115 : ret = JOB_CONFIG_RW_RANDRW;
2013 : : } else {
2014 [ # # ]: 0 : fprintf(stderr, "rw must be one of\n"
2015 : : PATTERN_TYPES_STR "\n");
2016 : 0 : ret = BDEVPERF_CONFIG_ERROR;
2017 : : }
2018 : :
2019 : 1045 : return ret;
2020 : : }
2021 : :
2022 : : static const char *
2023 : 2200 : config_filename_next(const char *filename, char *out)
2024 : : {
2025 : : int i, k;
2026 : :
2027 [ - + ]: 2200 : if (filename == NULL) {
2028 : 0 : out[0] = '\0';
2029 : 0 : return NULL;
2030 : : }
2031 : :
2032 [ + + ]: 2200 : if (filename[0] == ':') {
2033 : 20 : filename++;
2034 : : }
2035 : :
2036 : 2200 : for (i = 0, k = 0;
2037 [ + + ]: 12494 : filename[i] != '\0' &&
2038 [ + + + - ]: 8937 : filename[i] != ':' &&
2039 [ + + + - ]: 17265 : i < BDEVPERF_CONFIG_MAX_FILENAME &&
2040 : : k < (BDEVPERF_CONFIG_MAX_FILENAME - 1);
2041 : 8917 : i++) {
2042 [ + - - + ]: 8917 : if (filename[i] == ' ' || filename[i] == '\t') {
2043 : 0 : continue;
2044 : : }
2045 : :
2046 : 8917 : out[k++] = filename[i];
2047 : : }
2048 : 2200 : out[k] = 0;
2049 : :
2050 : 2200 : return filename + i;
2051 : : }
2052 : :
2053 : : static struct spdk_thread *
2054 : 0 : get_lcore_thread(uint32_t lcore)
2055 : : {
2056 : : struct lcore_thread *lthread;
2057 : :
2058 [ # # ]: 0 : TAILQ_FOREACH(lthread, &g_lcore_thread_list, link) {
2059 [ # # ]: 0 : if (lthread->lcore == lcore) {
2060 : 0 : return lthread->thread;
2061 : : }
2062 : : }
2063 : :
2064 : 0 : return NULL;
2065 : : }
2066 : :
2067 : : static void
2068 : 0 : create_lcore_thread(uint32_t lcore)
2069 : : {
2070 : : struct lcore_thread *lthread;
2071 : 0 : struct spdk_cpuset cpumask = {};
2072 : 0 : char name[32];
2073 : :
2074 : 0 : lthread = calloc(1, sizeof(*lthread));
2075 [ # # ]: 0 : assert(lthread != NULL);
2076 : :
2077 : 0 : lthread->lcore = lcore;
2078 : :
2079 [ # # ]: 0 : snprintf(name, sizeof(name), "lcore_%u", lcore);
2080 : 0 : spdk_cpuset_set_cpu(&cpumask, lcore, true);
2081 : :
2082 : 0 : lthread->thread = spdk_thread_create(name, &cpumask);
2083 [ # # ]: 0 : assert(lthread->thread != NULL);
2084 : :
2085 : 0 : TAILQ_INSERT_TAIL(&g_lcore_thread_list, lthread, link);
2086 : 0 : }
2087 : :
2088 : : static void
2089 : 382 : bdevperf_construct_jobs(void)
2090 : : {
2091 : 169 : char filename[BDEVPERF_CONFIG_MAX_FILENAME];
2092 : : struct spdk_thread *thread;
2093 : : struct job_config *config;
2094 : : struct spdk_bdev *bdev;
2095 : : const char *filenames;
2096 : : uint32_t i;
2097 : : int rc;
2098 : :
2099 [ - + - + ]: 382 : if (g_one_thread_per_lcore) {
2100 [ # # ]: 0 : SPDK_ENV_FOREACH_CORE(i) {
2101 : 0 : create_lcore_thread(i);
2102 : : }
2103 : : }
2104 : :
2105 [ + + ]: 1472 : TAILQ_FOREACH(config, &job_config_list, link) {
2106 : 1090 : filenames = config->filename;
2107 : :
2108 [ + + + - ]: 1090 : if (!g_one_thread_per_lcore) {
2109 : 1090 : thread = construct_job_thread(&config->cpumask, config->name);
2110 : : } else {
2111 : 0 : thread = get_lcore_thread(config->lcore);
2112 : : }
2113 [ - + ]: 1090 : assert(thread);
2114 : :
2115 [ + - ]: 2200 : while (filenames) {
2116 : 2200 : filenames = config_filename_next(filenames, filename);
2117 [ + + ]: 2200 : if (strlen(filename) == 0) {
2118 : 1090 : break;
2119 : : }
2120 : :
2121 : 1110 : bdev = spdk_bdev_get_by_name(filename);
2122 [ - + ]: 1110 : if (!bdev) {
2123 [ # # # # ]: 0 : fprintf(stderr, "Unable to find bdev '%s'\n", filename);
2124 : 0 : g_run_rc = -EINVAL;
2125 : 0 : return;
2126 : : }
2127 : :
2128 : 1110 : rc = bdevperf_construct_job(bdev, config, thread);
2129 [ - + ]: 1110 : if (rc < 0) {
2130 : 0 : g_run_rc = rc;
2131 : 0 : return;
2132 : : }
2133 : : }
2134 : : }
2135 : : }
2136 : :
2137 : : static int
2138 : 1015 : make_cli_job_config(const char *filename, int64_t offset, uint64_t range)
2139 : : {
2140 : 1015 : struct job_config *config = calloc(1, sizeof(*config));
2141 : :
2142 [ - + ]: 1015 : if (config == NULL) {
2143 [ # # # # ]: 0 : fprintf(stderr, "Unable to allocate memory for job config\n");
2144 : 0 : return -ENOMEM;
2145 : : }
2146 : :
2147 : 1015 : config->name = filename;
2148 : 1015 : config->filename = filename;
2149 : 1015 : config->lcore = _get_next_core();
2150 : 1015 : spdk_cpuset_zero(&config->cpumask);
2151 : 1015 : spdk_cpuset_set_cpu(&config->cpumask, config->lcore, true);
2152 : 1015 : config->bs = g_io_size;
2153 : 1015 : config->iodepth = g_queue_depth;
2154 : 1015 : config->rwmixread = g_rw_percentage;
2155 : 1015 : config->offset = offset;
2156 : 1015 : config->length = range;
2157 : 1015 : config->rw = parse_rw(g_workload_type, BDEVPERF_CONFIG_ERROR);
2158 [ - + ]: 1015 : if ((int)config->rw == BDEVPERF_CONFIG_ERROR) {
2159 : 0 : free(config);
2160 : 0 : return -EINVAL;
2161 : : }
2162 : :
2163 : 1015 : TAILQ_INSERT_TAIL(&job_config_list, config, link);
2164 : 1015 : return 0;
2165 : : }
2166 : :
2167 : : static int
2168 : 274 : bdevperf_construct_multithread_job_config(void *ctx, struct spdk_bdev *bdev)
2169 : : {
2170 : 274 : uint32_t *num_cores = ctx;
2171 : : uint32_t i;
2172 : : uint64_t blocks_per_job;
2173 : : int64_t offset;
2174 : : int rc;
2175 : :
2176 [ - + ]: 274 : blocks_per_job = spdk_bdev_get_num_blocks(bdev) / *num_cores;
2177 : 274 : offset = 0;
2178 : :
2179 [ + + ]: 822 : SPDK_ENV_FOREACH_CORE(i) {
2180 : 548 : rc = make_cli_job_config(spdk_bdev_get_name(bdev), offset, blocks_per_job);
2181 [ - + ]: 548 : if (rc) {
2182 : 0 : return rc;
2183 : : }
2184 : :
2185 : 548 : offset += blocks_per_job;
2186 : : }
2187 : :
2188 : 274 : return 0;
2189 : : }
2190 : :
2191 : : static void
2192 : 64 : bdevperf_construct_multithread_job_configs(void)
2193 : : {
2194 : : struct spdk_bdev *bdev;
2195 : : uint32_t i;
2196 : 34 : uint32_t num_cores;
2197 : :
2198 : 64 : num_cores = 0;
2199 [ + + ]: 192 : SPDK_ENV_FOREACH_CORE(i) {
2200 : 128 : num_cores++;
2201 : : }
2202 : :
2203 [ - + ]: 64 : if (num_cores == 0) {
2204 : 0 : g_run_rc = -EINVAL;
2205 : 0 : return;
2206 : : }
2207 : :
2208 [ - + ]: 64 : if (g_job_bdev_name != NULL) {
2209 : 0 : bdev = spdk_bdev_get_by_name(g_job_bdev_name);
2210 [ # # ]: 0 : if (!bdev) {
2211 [ # # # # ]: 0 : fprintf(stderr, "Unable to find bdev '%s'\n", g_job_bdev_name);
2212 : 0 : return;
2213 : : }
2214 : 0 : g_run_rc = bdevperf_construct_multithread_job_config(&num_cores, bdev);
2215 : : } else {
2216 : 64 : g_run_rc = spdk_for_each_bdev_leaf(&num_cores, bdevperf_construct_multithread_job_config);
2217 : : }
2218 : :
2219 : : }
2220 : :
2221 : : static int
2222 : 356 : bdevperf_construct_job_config(void *ctx, struct spdk_bdev *bdev)
2223 : : {
2224 : : /* Construct the job */
2225 : 356 : return make_cli_job_config(spdk_bdev_get_name(bdev), 0, 0);
2226 : : }
2227 : :
2228 : : static void
2229 : 382 : bdevperf_construct_job_configs(void)
2230 : : {
2231 : : struct spdk_bdev *bdev;
2232 : :
2233 : : /* There are three different modes for allocating jobs. Standard mode
2234 : : * (the default) creates one spdk_thread per bdev and runs the I/O job there.
2235 : : *
2236 : : * The -C flag places bdevperf into "multithread" mode, meaning it creates
2237 : : * one spdk_thread per bdev PER CORE, and runs a copy of the job on each.
2238 : : * This runs multiple threads per bdev, effectively.
2239 : : *
2240 : : * The -j flag implies "FIO" mode which tries to mimic semantic of FIO jobs.
2241 : : * In "FIO" mode, threads are spawned per-job instead of per-bdev.
2242 : : * Each FIO job can be individually parameterized by filename, cpu mask, etc,
2243 : : * which is different from other modes in that they only support global options.
2244 : : *
2245 : : * Both for standard mode and "multithread" mode, if the -E flag is specified,
2246 : : * it creates one spdk_thread PER CORE. On each core, one spdk_thread is shared by
2247 : : * multiple jobs.
2248 : : */
2249 : :
2250 [ + + ]: 382 : if (g_bdevperf_conf) {
2251 : 20 : goto end;
2252 : : }
2253 : :
2254 [ + + + + ]: 362 : if (g_multithread_mode) {
2255 : 64 : bdevperf_construct_multithread_job_configs();
2256 [ + + ]: 298 : } else if (g_job_bdev_name != NULL) {
2257 : 111 : bdev = spdk_bdev_get_by_name(g_job_bdev_name);
2258 [ + - ]: 111 : if (bdev) {
2259 : : /* Construct the job */
2260 : 111 : g_run_rc = make_cli_job_config(g_job_bdev_name, 0, 0);
2261 : : } else {
2262 [ # # # # ]: 0 : fprintf(stderr, "Unable to find bdev '%s'\n", g_job_bdev_name);
2263 : : }
2264 : : } else {
2265 : 187 : g_run_rc = spdk_for_each_bdev_leaf(NULL, bdevperf_construct_job_config);
2266 : : }
2267 : :
2268 : 382 : end:
2269 : : /* Increment initial construct_jobs count so that it will never reach 0 in the middle
2270 : : * of iteration.
2271 : : */
2272 : 382 : g_construct_job_count = 1;
2273 : :
2274 [ + - ]: 382 : if (g_run_rc == 0) {
2275 : 382 : bdevperf_construct_jobs();
2276 : : }
2277 : :
2278 : 382 : _bdevperf_construct_job_done(NULL);
2279 : 382 : }
2280 : :
2281 : : static int
2282 : 525 : parse_uint_option(struct spdk_conf_section *s, const char *name, int def)
2283 : : {
2284 : : const char *job_name;
2285 : : int tmp;
2286 : :
2287 : 525 : tmp = spdk_conf_section_get_intval(s, name);
2288 [ + + ]: 525 : if (tmp == -1) {
2289 : : /* Field was not found. Check default value
2290 : : * In [global] section it is ok to have undefined values
2291 : : * but for other sections it is not ok */
2292 [ + + ]: 255 : if (def == BDEVPERF_CONFIG_UNDEFINED) {
2293 : 30 : job_name = spdk_conf_section_get_name(s);
2294 [ + + + - ]: 30 : if (strcmp(job_name, "global") == 0) {
2295 : 30 : return def;
2296 : : }
2297 : :
2298 [ # # ]: 0 : fprintf(stderr,
2299 : : "Job '%s' has no '%s' assigned\n",
2300 : : job_name, name);
2301 : 0 : return BDEVPERF_CONFIG_ERROR;
2302 : : }
2303 : 225 : return def;
2304 : : }
2305 : :
2306 : : /* NOTE: get_intval returns nonnegative on success */
2307 [ - + ]: 270 : if (tmp < 0) {
2308 [ # # ]: 0 : fprintf(stderr, "Job '%s' has bad '%s' value.\n",
2309 : : spdk_conf_section_get_name(s), name);
2310 : 0 : return BDEVPERF_CONFIG_ERROR;
2311 : : }
2312 : :
2313 : 270 : return tmp;
2314 : : }
2315 : :
2316 : : /* CLI arguments override parameters for global sections */
2317 : : static void
2318 : 50 : config_set_cli_args(struct job_config *config)
2319 : : {
2320 [ - + ]: 50 : if (g_job_bdev_name) {
2321 : 0 : config->filename = g_job_bdev_name;
2322 : : }
2323 [ - + ]: 50 : if (g_io_size > 0) {
2324 : 0 : config->bs = g_io_size;
2325 : : }
2326 [ - + ]: 50 : if (g_queue_depth > 0) {
2327 : 0 : config->iodepth = g_queue_depth;
2328 : : }
2329 [ - + ]: 50 : if (g_rw_percentage > 0) {
2330 : 0 : config->rwmixread = g_rw_percentage;
2331 : : }
2332 [ - + ]: 50 : if (g_workload_type) {
2333 : 0 : config->rw = parse_rw(g_workload_type, config->rw);
2334 : : }
2335 : 50 : }
2336 : :
2337 : : static int
2338 : 483 : read_job_config(void)
2339 : : {
2340 : 222 : struct job_config global_default_config;
2341 : : struct job_config global_config;
2342 : : struct spdk_conf_section *s;
2343 : 483 : struct job_config *config = NULL;
2344 : : const char *cpumask;
2345 : : const char *rw;
2346 : : bool is_global;
2347 : 483 : int n = 0;
2348 : : int val;
2349 : :
2350 [ + + ]: 483 : if (g_bdevperf_conf_file == NULL) {
2351 : 463 : return 0;
2352 : : }
2353 : :
2354 : 20 : g_bdevperf_conf = spdk_conf_allocate();
2355 [ - + ]: 20 : if (g_bdevperf_conf == NULL) {
2356 [ # # ]: 0 : fprintf(stderr, "Could not allocate job config structure\n");
2357 : 0 : return 1;
2358 : : }
2359 : :
2360 : 20 : spdk_conf_disable_sections_merge(g_bdevperf_conf);
2361 [ - + ]: 20 : if (spdk_conf_read(g_bdevperf_conf, g_bdevperf_conf_file)) {
2362 [ # # ]: 0 : fprintf(stderr, "Invalid job config");
2363 : 0 : return 1;
2364 : : }
2365 : :
2366 : : /* Initialize global defaults */
2367 : 20 : global_default_config.filename = NULL;
2368 : : /* Zero mask is the same as g_all_cpuset
2369 : : * The g_all_cpuset is not initialized yet,
2370 : : * so use zero mask as the default instead */
2371 : 20 : spdk_cpuset_zero(&global_default_config.cpumask);
2372 : 20 : global_default_config.bs = BDEVPERF_CONFIG_UNDEFINED;
2373 : 20 : global_default_config.iodepth = BDEVPERF_CONFIG_UNDEFINED;
2374 : : /* bdevperf has no default for -M option but in FIO the default is 50 */
2375 : 20 : global_default_config.rwmixread = 50;
2376 : 20 : global_default_config.offset = 0;
2377 : : /* length 0 means 100% */
2378 : 20 : global_default_config.length = 0;
2379 : 20 : global_default_config.rw = BDEVPERF_CONFIG_UNDEFINED;
2380 : 20 : config_set_cli_args(&global_default_config);
2381 : :
2382 [ - + ]: 20 : if ((int)global_default_config.rw == BDEVPERF_CONFIG_ERROR) {
2383 : 0 : return 1;
2384 : : }
2385 : :
2386 : : /* There is only a single instance of global job_config
2387 : : * We just reset its value when we encounter new [global] section */
2388 : 20 : global_config = global_default_config;
2389 : :
2390 [ + + ]: 62 : for (s = spdk_conf_first_section(g_bdevperf_conf);
2391 [ + + ]: 75 : s != NULL;
2392 : 105 : s = spdk_conf_next_section(s)) {
2393 : 105 : config = calloc(1, sizeof(*config));
2394 [ - + ]: 105 : if (config == NULL) {
2395 [ # # ]: 0 : fprintf(stderr, "Unable to allocate memory for job config\n");
2396 : 0 : return 1;
2397 : : }
2398 : :
2399 : 105 : config->name = spdk_conf_section_get_name(s);
2400 [ - + ]: 105 : is_global = strcmp(config->name, "global") == 0;
2401 : :
2402 [ + + ]: 105 : if (is_global) {
2403 : 30 : global_config = global_default_config;
2404 : : }
2405 : :
2406 : 105 : config->filename = spdk_conf_section_get_val(s, "filename");
2407 [ + + ]: 105 : if (config->filename == NULL) {
2408 : 60 : config->filename = global_config.filename;
2409 : : }
2410 [ + + ]: 105 : if (!is_global) {
2411 [ - + ]: 75 : if (config->filename == NULL) {
2412 [ # # ]: 0 : fprintf(stderr, "Job '%s' expects 'filename' parameter\n", config->name);
2413 : 0 : goto error;
2414 [ - + - + ]: 75 : } else if (strnlen(config->filename, BDEVPERF_CONFIG_MAX_FILENAME)
2415 : : >= BDEVPERF_CONFIG_MAX_FILENAME) {
2416 [ # # ]: 0 : fprintf(stderr,
2417 : : "filename for '%s' job is too long. Max length is %d\n",
2418 : : config->name, BDEVPERF_CONFIG_MAX_FILENAME);
2419 : 0 : goto error;
2420 : : }
2421 : : }
2422 : :
2423 : 105 : cpumask = spdk_conf_section_get_val(s, "cpumask");
2424 [ + + ]: 105 : if (cpumask == NULL) {
2425 : 15 : config->cpumask = global_config.cpumask;
2426 [ - + ]: 90 : } else if (spdk_cpuset_parse(&config->cpumask, cpumask)) {
2427 [ # # ]: 0 : fprintf(stderr, "Job '%s' has bad 'cpumask' value\n", config->name);
2428 : 0 : goto error;
2429 : : }
2430 : :
2431 : 105 : config->bs = parse_uint_option(s, "bs", global_config.bs);
2432 [ - + ]: 105 : if (config->bs == BDEVPERF_CONFIG_ERROR) {
2433 : 0 : goto error;
2434 [ - + ]: 105 : } else if (config->bs == 0) {
2435 [ # # ]: 0 : fprintf(stderr, "'bs' of job '%s' must be greater than 0\n", config->name);
2436 : 0 : goto error;
2437 : : }
2438 : :
2439 : 105 : config->iodepth = parse_uint_option(s, "iodepth", global_config.iodepth);
2440 [ - + ]: 105 : if (config->iodepth == BDEVPERF_CONFIG_ERROR) {
2441 : 0 : goto error;
2442 [ - + ]: 105 : } else if (config->iodepth == 0) {
2443 [ # # ]: 0 : fprintf(stderr,
2444 : : "'iodepth' of job '%s' must be greater than 0\n",
2445 : : config->name);
2446 : 0 : goto error;
2447 : : }
2448 : :
2449 : 105 : config->rwmixread = parse_uint_option(s, "rwmixread", global_config.rwmixread);
2450 [ - + ]: 105 : if (config->rwmixread == BDEVPERF_CONFIG_ERROR) {
2451 : 0 : goto error;
2452 [ - + ]: 105 : } else if (config->rwmixread > 100) {
2453 [ # # ]: 0 : fprintf(stderr,
2454 : : "'rwmixread' value of '%s' job is not in 0-100 range\n",
2455 : : config->name);
2456 : 0 : goto error;
2457 : : }
2458 : :
2459 : 105 : config->offset = parse_uint_option(s, "offset", global_config.offset);
2460 [ - + ]: 105 : if (config->offset == BDEVPERF_CONFIG_ERROR) {
2461 : 0 : goto error;
2462 : : }
2463 : :
2464 : 105 : val = parse_uint_option(s, "length", global_config.length);
2465 [ - + ]: 105 : if (val == BDEVPERF_CONFIG_ERROR) {
2466 : 0 : goto error;
2467 : : }
2468 : 105 : config->length = val;
2469 : :
2470 : 105 : rw = spdk_conf_section_get_val(s, "rw");
2471 : 105 : config->rw = parse_rw(rw, global_config.rw);
2472 [ - + ]: 105 : if ((int)config->rw == BDEVPERF_CONFIG_ERROR) {
2473 [ # # ]: 0 : fprintf(stderr, "Job '%s' has bad 'rw' value\n", config->name);
2474 : 0 : goto error;
2475 [ + + - + ]: 105 : } else if (!is_global && (int)config->rw == BDEVPERF_CONFIG_UNDEFINED) {
2476 [ # # ]: 0 : fprintf(stderr, "Job '%s' has no 'rw' assigned\n", config->name);
2477 : 0 : goto error;
2478 : : }
2479 : :
2480 [ + + ]: 105 : if (is_global) {
2481 : 30 : config_set_cli_args(config);
2482 : 30 : global_config = *config;
2483 : 30 : free(config);
2484 : 30 : config = NULL;
2485 : : } else {
2486 : 75 : TAILQ_INSERT_TAIL(&job_config_list, config, link);
2487 : 75 : n++;
2488 : : }
2489 : : }
2490 : :
2491 [ - + ]: 20 : if (g_rpc_log_file_name != NULL) {
2492 : 0 : g_rpc_log_file = fopen(g_rpc_log_file_name, "a");
2493 [ # # ]: 0 : if (g_rpc_log_file == NULL) {
2494 [ # # ]: 0 : fprintf(stderr, "Failed to open %s\n", g_rpc_log_file_name);
2495 : 0 : goto error;
2496 : : }
2497 : : }
2498 : :
2499 : 20 : printf("Using job config with %d jobs\n", n);
2500 : 20 : return 0;
2501 : 0 : error:
2502 : 0 : free(config);
2503 : 0 : return 1;
2504 : : }
2505 : :
2506 : : static void
2507 : 435 : bdevperf_run(void *arg1)
2508 : : {
2509 : : uint32_t i;
2510 : :
2511 : 435 : g_main_thread = spdk_get_thread();
2512 : :
2513 : 435 : spdk_cpuset_zero(&g_all_cpuset);
2514 [ + + ]: 937 : SPDK_ENV_FOREACH_CORE(i) {
2515 : 502 : spdk_cpuset_set_cpu(&g_all_cpuset, i, true);
2516 : : }
2517 : :
2518 [ + + + + ]: 435 : if (g_wait_for_tests) {
2519 : : /* Do not perform any tests until RPC is received */
2520 : 287 : return;
2521 : : }
2522 : :
2523 : 148 : bdevperf_construct_job_configs();
2524 : : }
2525 : :
2526 : : static void
2527 : 228 : rpc_perform_tests_reset(void)
2528 : : {
2529 : : /* Reset g_run_rc to 0 for the next test run. */
2530 : 228 : g_run_rc = 0;
2531 : :
2532 : : /* Reset g_stats to 0 for the next test run. */
2533 [ - + ]: 228 : memset(&g_stats, 0, sizeof(g_stats));
2534 : :
2535 : : /* Reset g_show_performance_period_num to 0 for the next test run. */
2536 : 228 : g_show_performance_period_num = 0;
2537 : 228 : }
2538 : :
2539 : : static void
2540 : 228 : rpc_perform_tests_cb(void)
2541 : : {
2542 : 228 : struct spdk_jsonrpc_request *request = g_request;
2543 : :
2544 : 228 : g_request = NULL;
2545 : :
2546 [ + + ]: 228 : if (g_run_rc) {
2547 : 5 : spdk_jsonrpc_send_error_response_fmt(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR,
2548 : : "bdevperf failed with error %s", spdk_strerror(-g_run_rc));
2549 : : }
2550 : :
2551 : 228 : rpc_perform_tests_reset();
2552 : 228 : }
2553 : :
2554 : : struct rpc_bdevperf_params {
2555 : : int time_in_sec;
2556 : : char *workload_type;
2557 : : int queue_depth;
2558 : : char *io_size;
2559 : : int rw_percentage;
2560 : : };
2561 : :
2562 : : static const struct spdk_json_object_decoder rpc_bdevperf_params_decoders[] = {
2563 : : {"time_in_sec", offsetof(struct rpc_bdevperf_params, time_in_sec), spdk_json_decode_int32, true},
2564 : : {"workload_type", offsetof(struct rpc_bdevperf_params, workload_type), spdk_json_decode_string, true},
2565 : : {"queue_depth", offsetof(struct rpc_bdevperf_params, queue_depth), spdk_json_decode_int32, true},
2566 : : {"io_size", offsetof(struct rpc_bdevperf_params, io_size), spdk_json_decode_string, true},
2567 : : {"rw_percentage", offsetof(struct rpc_bdevperf_params, rw_percentage), spdk_json_decode_int32, true},
2568 : : };
2569 : :
2570 : : static void
2571 : 3 : rpc_apply_bdevperf_params(struct rpc_bdevperf_params *params)
2572 : : {
2573 [ + - ]: 3 : if (params->workload_type) {
2574 : : /* we need to clear previously settled parameter to avoid memory leak */
2575 : 3 : free(g_workload_type);
2576 [ - + ]: 3 : g_workload_type = strdup(params->workload_type);
2577 : : }
2578 [ + - ]: 3 : if (params->queue_depth) {
2579 : 3 : g_queue_depth = params->queue_depth;
2580 : : }
2581 [ + - ]: 3 : if (params->io_size) {
2582 : 3 : bdevperf_parse_arg('o', params->io_size);
2583 : : }
2584 [ + - ]: 3 : if (params->time_in_sec) {
2585 : 3 : g_time_in_sec = params->time_in_sec;
2586 : : }
2587 [ - + ]: 3 : if (params->rw_percentage) {
2588 : 0 : g_rw_percentage = params->rw_percentage;
2589 : 0 : g_mix_specified = true;
2590 : : } else {
2591 : 3 : g_mix_specified = false;
2592 : : }
2593 : 3 : }
2594 : :
2595 : : static void
2596 : 234 : rpc_perform_tests(struct spdk_jsonrpc_request *request, const struct spdk_json_val *params)
2597 : : {
2598 : 234 : struct rpc_bdevperf_params req = {}, backup = {};
2599 : : int rc;
2600 : :
2601 [ - + ]: 234 : if (g_request != NULL) {
2602 [ # # # # ]: 0 : fprintf(stderr, "Another test is already in progress.\n");
2603 : 0 : spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR,
2604 : : spdk_strerror(-EINPROGRESS));
2605 : 0 : return;
2606 : : }
2607 : :
2608 [ + + ]: 234 : if (params) {
2609 [ - + ]: 3 : if (spdk_json_decode_object_relaxed(params, rpc_bdevperf_params_decoders,
2610 : : SPDK_COUNTOF(rpc_bdevperf_params_decoders),
2611 : : &req)) {
2612 : 0 : spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_PARSE_ERROR,
2613 : : "spdk_json_decode_object failed");
2614 : 0 : return;
2615 : : }
2616 : :
2617 [ + + ]: 3 : if (g_workload_type) {
2618 [ - + ]: 2 : backup.workload_type = strdup(g_workload_type);
2619 : : }
2620 : 3 : backup.queue_depth = g_queue_depth;
2621 [ - + ]: 3 : if (asprintf(&backup.io_size, "%d", g_io_size) < 0) {
2622 [ # # # # ]: 0 : fprintf(stderr, "Couldn't allocate memory for queue depth");
2623 : 0 : goto rpc_error;
2624 : : }
2625 : 3 : backup.time_in_sec = g_time_in_sec;
2626 : 3 : backup.rw_percentage = g_rw_percentage;
2627 : :
2628 : 3 : rpc_apply_bdevperf_params(&req);
2629 : :
2630 : 3 : free(req.workload_type);
2631 : 3 : free(req.io_size);
2632 : : }
2633 : :
2634 : 234 : rc = verify_test_params();
2635 : :
2636 [ - + ]: 234 : if (rc) {
2637 : 0 : spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_PARSE_ERROR,
2638 : : "Invalid parameters provided");
2639 : : /* restore old params on error */
2640 : 0 : rpc_apply_bdevperf_params(&backup);
2641 : 0 : goto rpc_error;
2642 : : }
2643 : :
2644 : 234 : g_request = request;
2645 : :
2646 : : /* Only construct job configs at the first test run. */
2647 [ + - ]: 234 : if (TAILQ_EMPTY(&job_config_list)) {
2648 : 234 : bdevperf_construct_job_configs();
2649 : : } else {
2650 : 0 : bdevperf_construct_jobs();
2651 : : }
2652 : :
2653 : 234 : rpc_error:
2654 : 234 : free(backup.io_size);
2655 : 234 : free(backup.workload_type);
2656 : : }
2657 : 483 : SPDK_RPC_REGISTER("perform_tests", rpc_perform_tests, SPDK_RPC_RUNTIME)
2658 : :
2659 : : static void
2660 : 27 : _bdevperf_job_drain(void *ctx)
2661 : : {
2662 : 27 : bdevperf_job_drain(ctx);
2663 : 27 : }
2664 : :
2665 : : static void
2666 : 286 : spdk_bdevperf_shutdown_cb(void)
2667 : : {
2668 : 286 : g_shutdown = true;
2669 : : struct bdevperf_job *job, *tmp;
2670 : :
2671 [ + + ]: 286 : if (g_bdevperf.running_jobs == 0) {
2672 : 277 : bdevperf_test_done(NULL);
2673 : 277 : return;
2674 : : }
2675 : :
2676 : : /* Iterate jobs to stop all I/O */
2677 [ + + ]: 36 : TAILQ_FOREACH_SAFE(job, &g_bdevperf.jobs, link, tmp) {
2678 : 27 : spdk_thread_send_msg(job->thread, _bdevperf_job_drain, job);
2679 : : }
2680 : : }
2681 : :
2682 : : static int
2683 : 2721 : bdevperf_parse_arg(int ch, char *arg)
2684 : : {
2685 : : long long tmp;
2686 : :
2687 [ + + ]: 2721 : if (ch == 'w') {
2688 [ - + ]: 462 : g_workload_type = strdup(arg);
2689 [ + + ]: 2259 : } else if (ch == 'T') {
2690 : 146 : g_job_bdev_name = arg;
2691 [ + + ]: 2113 : } else if (ch == 'z') {
2692 : 287 : g_wait_for_tests = true;
2693 [ - + ]: 1826 : } else if (ch == 'Z') {
2694 : 0 : g_zcopy = true;
2695 [ - + ]: 1826 : } else if (ch == 'X') {
2696 : 0 : g_abort = true;
2697 [ + + ]: 1826 : } else if (ch == 'C') {
2698 : 69 : g_multithread_mode = true;
2699 [ + + ]: 1757 : } else if (ch == 'f') {
2700 : 114 : g_continue_on_failure = true;
2701 [ + + ]: 1643 : } else if (ch == 'j') {
2702 : 20 : g_bdevperf_conf_file = arg;
2703 [ - + ]: 1623 : } else if (ch == 'F') {
2704 : 0 : char *endptr;
2705 : :
2706 : 0 : errno = 0;
2707 [ # # ]: 0 : g_zipf_theta = strtod(arg, &endptr);
2708 [ # # # # : 0 : if (errno || arg == endptr || g_zipf_theta < 0) {
# # ]
2709 [ # # ]: 0 : fprintf(stderr, "Illegal zipf theta value %s\n", arg);
2710 : 0 : return -EINVAL;
2711 : : }
2712 [ - + ]: 1623 : } else if (ch == 'l') {
2713 : 0 : g_latency_display_level++;
2714 [ - + ]: 1623 : } else if (ch == 'D') {
2715 : 0 : g_random_map = true;
2716 [ - + ]: 1623 : } else if (ch == 'E') {
2717 : 0 : g_one_thread_per_lcore = true;
2718 [ - + ]: 1623 : } else if (ch == 'J') {
2719 : 0 : g_rpc_log_file_name = arg;
2720 [ + + ]: 1623 : } else if (ch == 'o') {
2721 : 208 : uint64_t size;
2722 : :
2723 [ - + ]: 465 : if (spdk_parse_capacity(arg, &size, NULL) != 0) {
2724 [ # # ]: 0 : fprintf(stderr, "Invalid IO size: %s\n", arg);
2725 : 0 : return -EINVAL;
2726 : : }
2727 : 465 : g_io_size = (int)size;
2728 [ + + ]: 1158 : } else if (ch == 'U') {
2729 : 53 : g_unique_writes = true;
2730 : : } else {
2731 : 1105 : tmp = spdk_strtoll(arg, 10);
2732 [ - + ]: 1105 : if (tmp < 0) {
2733 [ # # ]: 0 : fprintf(stderr, "Parse failed for the option %c.\n", ch);
2734 : 0 : return tmp;
2735 [ - + ]: 1105 : } else if (tmp >= INT_MAX) {
2736 [ # # ]: 0 : fprintf(stderr, "Parsed option was too large %c.\n", ch);
2737 : 0 : return -ERANGE;
2738 : : }
2739 : :
2740 [ + + - + : 1105 : switch (ch) {
- + - ]
2741 : 462 : case 'q':
2742 : 462 : g_queue_depth = tmp;
2743 : 462 : break;
2744 : 482 : case 't':
2745 : 482 : g_time_in_sec = tmp;
2746 : 482 : break;
2747 : 0 : case 'k':
2748 : 0 : g_timeout_in_sec = tmp;
2749 : 0 : break;
2750 : 153 : case 'M':
2751 : 153 : g_rw_percentage = tmp;
2752 : 153 : g_mix_specified = true;
2753 : 153 : break;
2754 : 0 : case 'P':
2755 : 0 : g_show_performance_ema_period = tmp;
2756 : 0 : break;
2757 : 8 : case 'S':
2758 : 8 : g_show_performance_real_time = 1;
2759 : 8 : g_show_performance_period_in_usec = tmp * SPDK_SEC_TO_USEC;
2760 : 8 : break;
2761 : 0 : default:
2762 : 0 : return -EINVAL;
2763 : : }
2764 : : }
2765 : 2721 : return 0;
2766 : : }
2767 : :
2768 : : static void
2769 : 0 : bdevperf_usage(void)
2770 : : {
2771 [ # # ]: 0 : printf(" -q <depth> io depth\n");
2772 [ # # ]: 0 : printf(" -o <size> io size in bytes\n");
2773 [ # # ]: 0 : printf(" -w <type> io pattern type, must be one of " PATTERN_TYPES_STR "\n");
2774 [ # # ]: 0 : printf(" -t <time> time in seconds\n");
2775 [ # # ]: 0 : printf(" -k <timeout> timeout in seconds to detect starved I/O (default is 0 and disabled)\n");
2776 [ # # ]: 0 : printf(" -M <percent> rwmixread (100 for reads, 0 for writes)\n");
2777 [ # # ]: 0 : printf(" -P <num> number of moving average period\n");
2778 [ # # ]: 0 : printf("\t\t(If set to n, show weighted mean of the previous n IO/s in real time)\n");
2779 [ # # ]: 0 : printf("\t\t(Formula: M = 2 / (n + 1), EMA[i+1] = IO/s * M + (1 - M) * EMA[i])\n");
2780 [ # # ]: 0 : printf("\t\t(only valid with -S)\n");
2781 [ # # ]: 0 : printf(" -S <period> show performance result in real time every <period> seconds\n");
2782 [ # # ]: 0 : printf(" -T <bdev> bdev to run against. Default: all available bdevs.\n");
2783 [ # # ]: 0 : printf(" -f continue processing I/O even after failures\n");
2784 [ # # ]: 0 : printf(" -F <zipf theta> use zipf distribution for random I/O\n");
2785 [ # # ]: 0 : printf(" -Z enable using zcopy bdev API for read or write I/O\n");
2786 [ # # ]: 0 : printf(" -z start bdevperf, but wait for perform_tests RPC to start tests\n");
2787 [ # # ]: 0 : printf(" (See examples/bdev/bdevperf/bdevperf.py)\n");
2788 [ # # ]: 0 : printf(" -X abort timed out I/O\n");
2789 [ # # ]: 0 : printf(" -C enable every core to send I/Os to each bdev\n");
2790 [ # # ]: 0 : printf(" -j <filename> use job config file\n");
2791 [ # # ]: 0 : printf(" -l display latency histogram, default: disable. -l display summary, -ll display details\n");
2792 [ # # ]: 0 : printf(" -D use a random map for picking offsets not previously read or written (for all jobs)\n");
2793 [ # # ]: 0 : printf(" -E share per lcore thread among jobs. Available only if -j is not used.\n");
2794 [ # # ]: 0 : printf(" -J File name to open with append mode and log JSON RPC calls.\n");
2795 [ # # ]: 0 : printf(" -U generate unique data for each write I/O, has no effect on non-write I/O\n");
2796 : 0 : }
2797 : :
2798 : : static void
2799 : 483 : bdevperf_fini(void)
2800 : : {
2801 : 483 : free_job_config();
2802 : 483 : free(g_workload_type);
2803 : :
2804 [ - + ]: 483 : if (g_rpc_log_file != NULL) {
2805 : 0 : fclose(g_rpc_log_file);
2806 : 0 : g_rpc_log_file = NULL;
2807 : : }
2808 : 483 : }
2809 : :
2810 : : static int
2811 : 717 : verify_test_params(void)
2812 : : {
2813 [ + + + + ]: 717 : if (!g_bdevperf_conf_file && g_queue_depth <= 0) {
2814 : 1 : goto out;
2815 : : }
2816 [ + + - + ]: 716 : if (!g_bdevperf_conf_file && g_io_size <= 0) {
2817 : 0 : goto out;
2818 : : }
2819 [ + + - + ]: 716 : if (!g_bdevperf_conf_file && !g_workload_type) {
2820 : 0 : goto out;
2821 : : }
2822 [ + + - + : 716 : if (g_bdevperf_conf_file && g_one_thread_per_lcore) {
- + ]
2823 : 0 : printf("If bdevperf's config file is used, per lcore thread cannot be used\n");
2824 : 0 : goto out;
2825 : : }
2826 [ - + ]: 716 : if (g_time_in_sec <= 0) {
2827 : 0 : goto out;
2828 : : }
2829 : 716 : g_time_in_usec = g_time_in_sec * SPDK_SEC_TO_USEC;
2830 : :
2831 [ - + ]: 716 : if (g_timeout_in_sec < 0) {
2832 : 0 : goto out;
2833 : : }
2834 : :
2835 [ - + - + : 716 : if (g_abort && !g_timeout_in_sec) {
- - ]
2836 : 0 : printf("Timeout must be set for abort option, Ignoring g_abort\n");
2837 : : }
2838 : :
2839 [ - + ]: 716 : if (g_show_performance_ema_period > 0 &&
2840 [ # # ]: 0 : g_show_performance_real_time == 0) {
2841 [ # # ]: 0 : fprintf(stderr, "-P option must be specified with -S option\n");
2842 : 0 : return 1;
2843 : : }
2844 : :
2845 [ + + ]: 716 : if (g_io_size > SPDK_BDEV_LARGE_BUF_MAX_SIZE) {
2846 : 274 : printf("I/O size of %d is greater than zero copy threshold (%d).\n",
2847 : : g_io_size, SPDK_BDEV_LARGE_BUF_MAX_SIZE);
2848 : 274 : printf("Zero copy mechanism will not be used.\n");
2849 : 274 : g_zcopy = false;
2850 : : }
2851 : :
2852 [ + + ]: 716 : if (g_bdevperf_conf_file) {
2853 : : /* workload_type verification happens during config file parsing */
2854 : 20 : return 0;
2855 : : }
2856 : :
2857 [ + + + + ]: 696 : if (!strcmp(g_workload_type, "verify") ||
2858 [ - + + + ]: 501 : !strcmp(g_workload_type, "reset")) {
2859 : 196 : g_rw_percentage = 50;
2860 : 196 : g_verify = true;
2861 [ - + + + ]: 196 : if (!strcmp(g_workload_type, "reset")) {
2862 : 1 : g_reset = true;
2863 : : }
2864 : : }
2865 : :
2866 [ + + + + ]: 696 : if (!strcmp(g_workload_type, "read") ||
2867 [ + + + + ]: 688 : !strcmp(g_workload_type, "randread") ||
2868 [ + + + + ]: 602 : !strcmp(g_workload_type, "write") ||
2869 [ + + + + ]: 590 : !strcmp(g_workload_type, "randwrite") ||
2870 [ + + + + ]: 546 : !strcmp(g_workload_type, "verify") ||
2871 [ + + + + ]: 351 : !strcmp(g_workload_type, "reset") ||
2872 [ + + + + ]: 350 : !strcmp(g_workload_type, "unmap") ||
2873 [ + + + + ]: 342 : !strcmp(g_workload_type, "write_zeroes") ||
2874 [ - + + + ]: 270 : !strcmp(g_workload_type, "flush")) {
2875 [ - + - + ]: 433 : if (g_mix_specified) {
2876 [ # # ]: 0 : fprintf(stderr, "Ignoring -M option... Please use -M option"
2877 : : " only when using rw or randrw.\n");
2878 : : }
2879 : : }
2880 : :
2881 [ + + + - ]: 696 : if (!strcmp(g_workload_type, "rw") ||
2882 [ + + + + ]: 696 : !strcmp(g_workload_type, "randrw")) {
2883 [ + - - + ]: 263 : if (g_rw_percentage < 0 || g_rw_percentage > 100) {
2884 [ # # ]: 0 : fprintf(stderr,
2885 : : "-M must be specified to value from 0 to 100 "
2886 : : "for rw or randrw.\n");
2887 : 0 : return 1;
2888 : : }
2889 : : }
2890 : :
2891 [ + + + + ]: 696 : if (strcmp(g_workload_type, "randread") &&
2892 [ + + + + ]: 610 : strcmp(g_workload_type, "randwrite") &&
2893 [ + + + + ]: 566 : strcmp(g_workload_type, "randrw")) {
2894 [ - + - + ]: 303 : if (g_random_map) {
2895 [ # # ]: 0 : fprintf(stderr, "Ignoring -D option... Please use -D option"
2896 : : " only when using randread, randwrite or randrw.\n");
2897 : 0 : return 1;
2898 : : }
2899 : : }
2900 : :
2901 : 696 : return 0;
2902 : 1 : out:
2903 : 1 : return 1;
2904 : : }
2905 : :
2906 : : int
2907 : 483 : main(int argc, char **argv)
2908 : : {
2909 : 483 : struct spdk_app_opts opts = {};
2910 : : int rc;
2911 : :
2912 : : /* Use the runtime PID to set the random seed */
2913 : 483 : srand(getpid());
2914 : :
2915 : 483 : spdk_app_opts_init(&opts, sizeof(opts));
2916 : 483 : opts.name = "bdevperf";
2917 : 483 : opts.rpc_addr = NULL;
2918 : 483 : opts.shutdown_cb = spdk_bdevperf_shutdown_cb;
2919 : :
2920 [ - + ]: 483 : if ((rc = spdk_app_parse_args(argc, argv, &opts, "Zzfq:o:t:w:k:CEF:J:M:P:S:T:Xlj:DU", NULL,
2921 : : bdevperf_parse_arg, bdevperf_usage)) !=
2922 : : SPDK_APP_PARSE_ARGS_SUCCESS) {
2923 : 0 : return rc;
2924 : : }
2925 : :
2926 : : /* Set the default address if no rpc_addr was provided in args
2927 : : * and RPC is used for starting tests */
2928 [ + + + + : 483 : if (g_wait_for_tests && opts.rpc_addr == NULL) {
+ + ]
2929 : 35 : opts.rpc_addr = SPDK_DEFAULT_RPC_ADDR;
2930 : : }
2931 : :
2932 [ - + ]: 483 : if (read_job_config()) {
2933 : 0 : bdevperf_fini();
2934 : 0 : return 1;
2935 : : }
2936 : :
2937 [ - + ]: 483 : if (g_rpc_log_file != NULL) {
2938 : 0 : opts.rpc_log_file = g_rpc_log_file;
2939 : : }
2940 : :
2941 [ + + - + : 483 : if (verify_test_params() != 0 && !g_wait_for_tests) {
- + ]
2942 : 0 : spdk_app_usage();
2943 : 0 : bdevperf_usage();
2944 : 0 : bdevperf_fini();
2945 : 0 : exit(1);
2946 : : }
2947 : :
2948 : 483 : rc = spdk_app_start(&opts, bdevperf_run, NULL);
2949 : :
2950 : 483 : spdk_app_fini();
2951 : 483 : bdevperf_fini();
2952 : 483 : return rc;
2953 : : }
|