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