1 /*- 2 * BSD LICENSE 3 * 4 * Copyright (c) Intel Corporation. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * * Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * * Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in 15 * the documentation and/or other materials provided with the 16 * distribution. 17 * * Neither the name of Intel Corporation nor the names of its 18 * contributors may be used to endorse or promote products derived 19 * from this software without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 */ 33 34 #include <stdio.h> 35 #include <unistd.h> 36 #include <stdlib.h> 37 #include <stdbool.h> 38 #include <string.h> 39 40 #include <rte_config.h> 41 #include <rte_lcore.h> 42 43 #include "spdk/ioat.h" 44 #include "spdk/env.h" 45 #include "spdk/queue.h" 46 #include "spdk/string.h" 47 48 struct user_config { 49 int xfer_size_bytes; 50 int queue_depth; 51 int time_in_sec; 52 bool verify; 53 char *core_mask; 54 int ioat_chan_num; 55 }; 56 57 struct ioat_device { 58 struct spdk_ioat_chan *ioat; 59 TAILQ_ENTRY(ioat_device) tailq; 60 }; 61 62 static TAILQ_HEAD(, ioat_device) g_devices; 63 static struct ioat_device *g_next_device; 64 65 static struct user_config g_user_config; 66 67 struct ioat_chan_entry { 68 struct spdk_ioat_chan *chan; 69 int ioat_chan_id; 70 uint64_t xfer_completed; 71 uint64_t xfer_failed; 72 uint64_t current_queue_depth; 73 bool is_draining; 74 struct spdk_mempool *data_pool; 75 struct spdk_mempool *task_pool; 76 struct ioat_chan_entry *next; 77 }; 78 79 struct worker_thread { 80 struct ioat_chan_entry *ctx; 81 struct worker_thread *next; 82 unsigned lcore; 83 }; 84 85 struct ioat_task { 86 struct ioat_chan_entry *ioat_chan_entry; 87 void *src; 88 void *dst; 89 }; 90 91 static struct worker_thread *g_workers = NULL; 92 static int g_num_workers = 0; 93 static int g_ioat_chan_num = 0; 94 95 static void submit_single_xfer(struct ioat_chan_entry *ioat_chan_entry, struct ioat_task *ioat_task, 96 void *dst, void *src); 97 98 static void 99 construct_user_config(struct user_config *self) 100 { 101 self->xfer_size_bytes = 4096; 102 self->ioat_chan_num = 1; 103 self->queue_depth = 256; 104 self->time_in_sec = 10; 105 self->verify = false; 106 self->core_mask = "0x1"; 107 } 108 109 static void 110 dump_user_config(struct user_config *self) 111 { 112 printf("User configuration:\n"); 113 printf("Number of channels: %u\n", self->ioat_chan_num); 114 printf("Transfer size: %u bytes\n", self->xfer_size_bytes); 115 printf("Queue depth: %u\n", self->queue_depth); 116 printf("Run time: %u seconds\n", self->time_in_sec); 117 printf("Core mask: %s\n", self->core_mask); 118 printf("Verify: %s\n\n", self->verify ? "Yes" : "No"); 119 } 120 121 static void 122 ioat_exit(void) 123 { 124 struct ioat_device *dev; 125 126 while (!TAILQ_EMPTY(&g_devices)) { 127 dev = TAILQ_FIRST(&g_devices); 128 TAILQ_REMOVE(&g_devices, dev, tailq); 129 if (dev->ioat) { 130 spdk_ioat_detach(dev->ioat); 131 } 132 spdk_free(dev); 133 } 134 } 135 136 static void 137 ioat_done(void *cb_arg) 138 { 139 struct ioat_task *ioat_task = (struct ioat_task *)cb_arg; 140 struct ioat_chan_entry *ioat_chan_entry = ioat_task->ioat_chan_entry; 141 142 if (g_user_config.verify && memcmp(ioat_task->src, ioat_task->dst, g_user_config.xfer_size_bytes)) { 143 ioat_chan_entry->xfer_failed++; 144 } else { 145 ioat_chan_entry->xfer_completed++; 146 } 147 148 ioat_chan_entry->current_queue_depth--; 149 150 if (ioat_chan_entry->is_draining) { 151 spdk_mempool_put(ioat_chan_entry->data_pool, ioat_task->src); 152 spdk_mempool_put(ioat_chan_entry->data_pool, ioat_task->dst); 153 spdk_mempool_put(ioat_chan_entry->task_pool, ioat_task); 154 } else { 155 submit_single_xfer(ioat_chan_entry, ioat_task, ioat_task->dst, ioat_task->src); 156 } 157 } 158 159 static int 160 register_workers(void) 161 { 162 unsigned lcore; 163 struct worker_thread *worker; 164 struct worker_thread *prev_worker; 165 166 worker = malloc(sizeof(struct worker_thread)); 167 if (worker == NULL) { 168 perror("worker_thread malloc"); 169 return -1; 170 } 171 172 memset(worker, 0, sizeof(struct worker_thread)); 173 worker->lcore = rte_get_master_lcore(); 174 175 g_workers = worker; 176 g_num_workers = 1; 177 178 RTE_LCORE_FOREACH_SLAVE(lcore) { 179 prev_worker = worker; 180 worker = malloc(sizeof(struct worker_thread)); 181 if (worker == NULL) { 182 perror("worker_thread malloc"); 183 return -1; 184 } 185 186 memset(worker, 0, sizeof(struct worker_thread)); 187 worker->lcore = lcore; 188 prev_worker->next = worker; 189 g_num_workers++; 190 } 191 192 return 0; 193 } 194 195 static void 196 unregister_workers(void) 197 { 198 struct worker_thread *worker = g_workers; 199 struct ioat_chan_entry *entry, *entry1; 200 201 /* Free ioat_chan_entry and worker thread */ 202 while (worker) { 203 struct worker_thread *next_worker = worker->next; 204 entry = worker->ctx; 205 while (entry) { 206 entry1 = entry->next; 207 spdk_mempool_free(entry->data_pool); 208 spdk_mempool_free(entry->task_pool); 209 free(entry); 210 entry = entry1; 211 } 212 free(worker); 213 worker = next_worker; 214 } 215 } 216 217 static bool 218 probe_cb(void *cb_ctx, struct spdk_pci_device *pci_dev) 219 { 220 printf(" Found matching device at %04x:%02x:%02x.%x " 221 "vendor:0x%04x device:0x%04x\n", 222 spdk_pci_device_get_domain(pci_dev), 223 spdk_pci_device_get_bus(pci_dev), spdk_pci_device_get_dev(pci_dev), 224 spdk_pci_device_get_func(pci_dev), 225 spdk_pci_device_get_vendor_id(pci_dev), spdk_pci_device_get_device_id(pci_dev)); 226 227 return true; 228 } 229 230 static void 231 attach_cb(void *cb_ctx, struct spdk_pci_device *pci_dev, struct spdk_ioat_chan *ioat) 232 { 233 struct ioat_device *dev; 234 235 if (g_ioat_chan_num >= g_user_config.ioat_chan_num) { 236 return; 237 } 238 239 dev = spdk_zmalloc(sizeof(*dev), 0, NULL); 240 if (dev == NULL) { 241 printf("Failed to allocate device struct\n"); 242 return; 243 } 244 245 dev->ioat = ioat; 246 g_ioat_chan_num++; 247 TAILQ_INSERT_TAIL(&g_devices, dev, tailq); 248 } 249 250 static int 251 ioat_init(void) 252 { 253 TAILQ_INIT(&g_devices); 254 255 if (spdk_ioat_probe(NULL, probe_cb, attach_cb) != 0) { 256 fprintf(stderr, "ioat_probe() failed\n"); 257 return 1; 258 } 259 260 return 0; 261 } 262 263 static void 264 usage(char *program_name) 265 { 266 printf("%s options\n", program_name); 267 printf("\t[-h help message]\n"); 268 printf("\t[-c core mask for distributing I/O submission/completion work]\n"); 269 printf("\t[-q queue depth]\n"); 270 printf("\t[-n number of channels]\n"); 271 printf("\t[-s transfer size in bytes]\n"); 272 printf("\t[-t time in seconds]\n"); 273 printf("\t[-v verify copy result if this switch is on]\n"); 274 } 275 276 static int 277 parse_args(int argc, char **argv) 278 { 279 int op; 280 281 construct_user_config(&g_user_config); 282 while ((op = getopt(argc, argv, "c:hn:q:s:t:v")) != -1) { 283 switch (op) { 284 case 's': 285 g_user_config.xfer_size_bytes = atoi(optarg); 286 break; 287 case 'n': 288 g_user_config.ioat_chan_num = atoi(optarg); 289 break; 290 case 'q': 291 g_user_config.queue_depth = atoi(optarg); 292 break; 293 case 't': 294 g_user_config.time_in_sec = atoi(optarg); 295 break; 296 case 'c': 297 g_user_config.core_mask = optarg; 298 break; 299 case 'v': 300 g_user_config.verify = true; 301 break; 302 case 'h': 303 usage(argv[0]); 304 exit(0); 305 default: 306 usage(argv[0]); 307 return 1; 308 } 309 } 310 if (!g_user_config.xfer_size_bytes || !g_user_config.queue_depth || 311 !g_user_config.time_in_sec || !g_user_config.core_mask || 312 !g_user_config.ioat_chan_num) { 313 usage(argv[0]); 314 return 1; 315 } 316 optind = 1; 317 return 0; 318 } 319 320 static void 321 drain_io(struct ioat_chan_entry *ioat_chan_entry) 322 { 323 while (ioat_chan_entry->current_queue_depth > 0) { 324 spdk_ioat_process_events(ioat_chan_entry->chan); 325 } 326 } 327 328 static void 329 submit_single_xfer(struct ioat_chan_entry *ioat_chan_entry, struct ioat_task *ioat_task, void *dst, 330 void *src) 331 { 332 ioat_task->ioat_chan_entry = ioat_chan_entry; 333 ioat_task->src = src; 334 ioat_task->dst = dst; 335 336 spdk_ioat_submit_copy(ioat_chan_entry->chan, ioat_task, ioat_done, dst, src, 337 g_user_config.xfer_size_bytes); 338 339 ioat_chan_entry->current_queue_depth++; 340 } 341 342 static void 343 submit_xfers(struct ioat_chan_entry *ioat_chan_entry, uint64_t queue_depth) 344 { 345 while (queue_depth-- > 0) { 346 void *src = NULL, *dst = NULL; 347 struct ioat_task *ioat_task = NULL; 348 349 src = spdk_mempool_get(ioat_chan_entry->data_pool); 350 dst = spdk_mempool_get(ioat_chan_entry->data_pool); 351 ioat_task = spdk_mempool_get(ioat_chan_entry->task_pool); 352 353 submit_single_xfer(ioat_chan_entry, ioat_task, dst, src); 354 } 355 } 356 357 static int 358 work_fn(void *arg) 359 { 360 uint64_t tsc_end; 361 struct worker_thread *worker = (struct worker_thread *)arg; 362 struct ioat_chan_entry *t = NULL; 363 364 printf("Starting thread on core %u\n", worker->lcore); 365 366 tsc_end = spdk_get_ticks() + g_user_config.time_in_sec * spdk_get_ticks_hz(); 367 368 t = worker->ctx; 369 while (t != NULL) { 370 // begin to submit transfers 371 submit_xfers(t, g_user_config.queue_depth); 372 t = t->next; 373 } 374 375 while (1) { 376 t = worker->ctx; 377 while (t != NULL) { 378 spdk_ioat_process_events(t->chan); 379 t = t->next; 380 } 381 382 if (spdk_get_ticks() > tsc_end) { 383 break; 384 } 385 } 386 387 t = worker->ctx; 388 while (t != NULL) { 389 // begin to drain io 390 t->is_draining = true; 391 drain_io(t); 392 t = t->next; 393 } 394 395 return 0; 396 } 397 398 static int 399 init(void) 400 { 401 struct spdk_env_opts opts; 402 403 spdk_env_opts_init(&opts); 404 opts.name = "perf"; 405 opts.core_mask = g_user_config.core_mask; 406 spdk_env_init(&opts); 407 408 return 0; 409 } 410 411 static int 412 dump_result(void) 413 { 414 uint64_t total_completed = 0; 415 uint64_t total_failed = 0; 416 uint64_t total_xfer_per_sec, total_bw_in_MiBps; 417 struct worker_thread *worker = g_workers; 418 419 printf("Channel_ID Lcore Transfers Bandwidth Failed\n"); 420 printf("-----------------------------------------------------------\n"); 421 while (worker != NULL) { 422 struct ioat_chan_entry *t = worker->ctx; 423 while (t) { 424 uint64_t xfer_per_sec = t->xfer_completed / g_user_config.time_in_sec; 425 uint64_t bw_in_MiBps = (t->xfer_completed * g_user_config.xfer_size_bytes) / 426 (g_user_config.time_in_sec * 1024 * 1024); 427 428 total_completed += t->xfer_completed; 429 total_failed += t->xfer_failed; 430 431 if (xfer_per_sec) { 432 printf("%10d%10d%12" PRIu64 "/s%8" PRIu64 " MiB/s%11" PRIu64 "\n", 433 t->ioat_chan_id, worker->lcore, xfer_per_sec, 434 bw_in_MiBps, t->xfer_failed); 435 } 436 t = t->next; 437 } 438 worker = worker->next; 439 } 440 441 total_xfer_per_sec = total_completed / g_user_config.time_in_sec; 442 total_bw_in_MiBps = (total_completed * g_user_config.xfer_size_bytes) / 443 (g_user_config.time_in_sec * 1024 * 1024); 444 445 printf("===========================================================\n"); 446 printf("Total:%26" PRIu64 "/s%8" PRIu64 " MiB/s%11" PRIu64 "\n", 447 total_xfer_per_sec, total_bw_in_MiBps, total_failed); 448 449 return total_failed ? 1 : 0; 450 } 451 452 static struct spdk_ioat_chan * 453 get_next_chan(void) 454 { 455 struct spdk_ioat_chan *chan; 456 457 if (g_next_device == NULL) { 458 return NULL; 459 } 460 461 chan = g_next_device->ioat; 462 463 g_next_device = TAILQ_NEXT(g_next_device, tailq); 464 465 return chan; 466 } 467 468 static int 469 associate_workers_with_chan(void) 470 { 471 struct spdk_ioat_chan *chan = get_next_chan(); 472 struct worker_thread *worker = g_workers; 473 struct ioat_chan_entry *t; 474 char buf_pool_name[20], task_pool_name[20]; 475 int i = 0; 476 477 while (chan != NULL) { 478 t = calloc(1, sizeof(struct ioat_chan_entry)); 479 if (!t) { 480 return -1; 481 } 482 483 t->ioat_chan_id = i; 484 snprintf(buf_pool_name, sizeof(buf_pool_name), "buf_pool_%d", i); 485 snprintf(task_pool_name, sizeof(task_pool_name), "task_pool_%d", i); 486 t->data_pool = spdk_mempool_create(buf_pool_name, 512, g_user_config.xfer_size_bytes, -1, 487 SPDK_ENV_SOCKET_ID_ANY); 488 t->task_pool = spdk_mempool_create(task_pool_name, 512, sizeof(struct ioat_task), -1, 489 SPDK_ENV_SOCKET_ID_ANY); 490 if (!t->data_pool || !t->task_pool) { 491 fprintf(stderr, "Could not allocate buffer pool.\n"); 492 spdk_mempool_free(t->data_pool); 493 spdk_mempool_free(t->task_pool); 494 free(t); 495 return 1; 496 } 497 printf("Associating ioat_channel %d with lcore %d\n", i, worker->lcore); 498 t->chan = chan; 499 t->next = worker->ctx; 500 worker->ctx = t; 501 502 worker = worker->next; 503 if (worker == NULL) { 504 worker = g_workers; 505 } 506 507 chan = get_next_chan(); 508 i++; 509 } 510 511 return 0; 512 } 513 514 int 515 main(int argc, char **argv) 516 { 517 int rc; 518 struct worker_thread *worker; 519 520 if (parse_args(argc, argv) != 0) { 521 return 1; 522 } 523 524 if (init() != 0) { 525 return 1; 526 } 527 528 if (register_workers() != 0) { 529 rc = -1; 530 goto cleanup; 531 } 532 533 if (ioat_init() != 0) { 534 rc = -1; 535 goto cleanup; 536 } 537 538 if (g_ioat_chan_num == 0) { 539 printf("No channels found\n"); 540 rc = 0; 541 goto cleanup; 542 } 543 544 if (g_user_config.ioat_chan_num > g_ioat_chan_num) { 545 printf("%d channels are requested, but only %d are found," 546 "so only test %d channels\n", g_user_config.ioat_chan_num, 547 g_ioat_chan_num, g_ioat_chan_num); 548 g_user_config.ioat_chan_num = g_ioat_chan_num; 549 } 550 551 g_next_device = TAILQ_FIRST(&g_devices); 552 dump_user_config(&g_user_config); 553 554 if (associate_workers_with_chan() != 0) { 555 rc = -1; 556 goto cleanup; 557 } 558 559 /* Launch all of the slave workers */ 560 worker = g_workers->next; 561 while (worker != NULL) { 562 rte_eal_remote_launch(work_fn, worker, worker->lcore); 563 worker = worker->next; 564 } 565 566 rc = work_fn(g_workers); 567 if (rc < 0) { 568 goto cleanup; 569 } 570 571 worker = g_workers->next; 572 while (worker != NULL) { 573 if (rte_eal_wait_lcore(worker->lcore) < 0) { 574 rc = -1; 575 goto cleanup; 576 } 577 worker = worker->next; 578 } 579 580 rc = dump_result(); 581 582 cleanup: 583 unregister_workers(); 584 ioat_exit(); 585 586 return rc; 587 } 588