Spdk/examples/ioat/perf/perf.c
paul luse a6dbe3721e update Intel copyright notices
per Intel policy to include file commit date using git cmd
below.  The policy does not apply to non-Intel (C) notices.

git log --follow -C90% --format=%ad --date default <file> | tail -1

and then pull just the 4 digit year from the result.

Intel copyrights were not added to files where Intel either had
no contribution ot the contribution lacked substance (ie license
header updates, formatting changes, etc).  Contribution date used
"--follow -C95%" to get the most accurate date.

Note that several files in this patch didn't end the license/(c)
block with a blank comment line so these were added as the vast
majority of files do have this last blank line.  Simply there for
consistency.

Signed-off-by: paul luse <paul.e.luse@intel.com>
Change-Id: Id5b7ce4f658fe87132f14139ead58d6e285c04d4
Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/15192
Tested-by: SPDK CI Jenkins <sys_sgci@intel.com>
Reviewed-by: Jim Harris <james.r.harris@intel.com>
Reviewed-by: Ben Walker <benjamin.walker@intel.com>
Community-CI: Mellanox Build Bot
2022-11-10 08:28:53 +00:00

568 lines
13 KiB
C

/* SPDX-License-Identifier: BSD-3-Clause
* Copyright (C) 2015 Intel Corporation.
* All rights reserved.
*/
#include "spdk/stdinc.h"
#include "spdk/ioat.h"
#include "spdk/env.h"
#include "spdk/queue.h"
#include "spdk/string.h"
struct user_config {
int xfer_size_bytes;
int queue_depth;
int time_in_sec;
bool verify;
char *core_mask;
int ioat_chan_num;
};
struct ioat_device {
struct spdk_ioat_chan *ioat;
TAILQ_ENTRY(ioat_device) tailq;
};
static TAILQ_HEAD(, ioat_device) g_devices = TAILQ_HEAD_INITIALIZER(g_devices);
static struct ioat_device *g_next_device;
static struct user_config g_user_config;
struct ioat_chan_entry {
struct spdk_ioat_chan *chan;
int ioat_chan_id;
uint64_t xfer_completed;
uint64_t xfer_failed;
uint64_t current_queue_depth;
uint64_t waiting_for_flush;
uint64_t flush_threshold;
bool is_draining;
struct spdk_mempool *data_pool;
struct spdk_mempool *task_pool;
struct ioat_chan_entry *next;
};
struct worker_thread {
struct ioat_chan_entry *ctx;
struct worker_thread *next;
unsigned core;
};
struct ioat_task {
struct ioat_chan_entry *ioat_chan_entry;
void *src;
void *dst;
};
static struct worker_thread *g_workers = NULL;
static int g_num_workers = 0;
static int g_ioat_chan_num = 0;
static void submit_single_xfer(struct ioat_chan_entry *ioat_chan_entry, struct ioat_task *ioat_task,
void *dst, void *src);
static void
construct_user_config(struct user_config *self)
{
self->xfer_size_bytes = 4096;
self->ioat_chan_num = 1;
self->queue_depth = 256;
self->time_in_sec = 10;
self->verify = false;
self->core_mask = "0x1";
}
static void
dump_user_config(struct user_config *self)
{
printf("User configuration:\n");
printf("Number of channels: %u\n", self->ioat_chan_num);
printf("Transfer size: %u bytes\n", self->xfer_size_bytes);
printf("Queue depth: %u\n", self->queue_depth);
printf("Run time: %u seconds\n", self->time_in_sec);
printf("Core mask: %s\n", self->core_mask);
printf("Verify: %s\n\n", self->verify ? "Yes" : "No");
}
static void
ioat_exit(void)
{
struct ioat_device *dev;
while (!TAILQ_EMPTY(&g_devices)) {
dev = TAILQ_FIRST(&g_devices);
TAILQ_REMOVE(&g_devices, dev, tailq);
if (dev->ioat) {
spdk_ioat_detach(dev->ioat);
}
spdk_dma_free(dev);
}
}
static void
ioat_done(void *cb_arg)
{
struct ioat_task *ioat_task = (struct ioat_task *)cb_arg;
struct ioat_chan_entry *ioat_chan_entry = ioat_task->ioat_chan_entry;
if (g_user_config.verify && memcmp(ioat_task->src, ioat_task->dst, g_user_config.xfer_size_bytes)) {
ioat_chan_entry->xfer_failed++;
} else {
ioat_chan_entry->xfer_completed++;
}
ioat_chan_entry->current_queue_depth--;
if (ioat_chan_entry->is_draining) {
spdk_mempool_put(ioat_chan_entry->data_pool, ioat_task->src);
spdk_mempool_put(ioat_chan_entry->data_pool, ioat_task->dst);
spdk_mempool_put(ioat_chan_entry->task_pool, ioat_task);
} else {
submit_single_xfer(ioat_chan_entry, ioat_task, ioat_task->dst, ioat_task->src);
}
}
static int
register_workers(void)
{
uint32_t i;
struct worker_thread *worker;
g_workers = NULL;
g_num_workers = 0;
SPDK_ENV_FOREACH_CORE(i) {
worker = calloc(1, sizeof(*worker));
if (worker == NULL) {
fprintf(stderr, "Unable to allocate worker\n");
return 1;
}
worker->core = i;
worker->next = g_workers;
g_workers = worker;
g_num_workers++;
}
return 0;
}
static void
unregister_workers(void)
{
struct worker_thread *worker = g_workers;
struct ioat_chan_entry *entry, *entry1;
/* Free ioat_chan_entry and worker thread */
while (worker) {
struct worker_thread *next_worker = worker->next;
entry = worker->ctx;
while (entry) {
entry1 = entry->next;
spdk_mempool_free(entry->data_pool);
spdk_mempool_free(entry->task_pool);
free(entry);
entry = entry1;
}
free(worker);
worker = next_worker;
}
}
static bool
probe_cb(void *cb_ctx, struct spdk_pci_device *pci_dev)
{
printf(" Found matching device at %04x:%02x:%02x.%x "
"vendor:0x%04x device:0x%04x\n",
spdk_pci_device_get_domain(pci_dev),
spdk_pci_device_get_bus(pci_dev), spdk_pci_device_get_dev(pci_dev),
spdk_pci_device_get_func(pci_dev),
spdk_pci_device_get_vendor_id(pci_dev), spdk_pci_device_get_device_id(pci_dev));
return true;
}
static void
attach_cb(void *cb_ctx, struct spdk_pci_device *pci_dev, struct spdk_ioat_chan *ioat)
{
struct ioat_device *dev;
if (g_ioat_chan_num >= g_user_config.ioat_chan_num) {
return;
}
dev = spdk_dma_zmalloc(sizeof(*dev), 0, NULL);
if (dev == NULL) {
printf("Failed to allocate device struct\n");
return;
}
dev->ioat = ioat;
g_ioat_chan_num++;
TAILQ_INSERT_TAIL(&g_devices, dev, tailq);
}
static int
ioat_init(void)
{
if (spdk_ioat_probe(NULL, probe_cb, attach_cb) != 0) {
fprintf(stderr, "ioat_probe() failed\n");
return 1;
}
return 0;
}
static void
usage(char *program_name)
{
printf("%s options\n", program_name);
printf("\t[-h help message]\n");
printf("\t[-c core mask for distributing I/O submission/completion work]\n");
printf("\t[-q queue depth]\n");
printf("\t[-n number of channels]\n");
printf("\t[-o transfer size in bytes]\n");
printf("\t[-t time in seconds]\n");
printf("\t[-v verify copy result if this switch is on]\n");
}
static int
parse_args(int argc, char **argv)
{
int op;
construct_user_config(&g_user_config);
while ((op = getopt(argc, argv, "c:hn:o:q:t:v")) != -1) {
switch (op) {
case 'o':
g_user_config.xfer_size_bytes = spdk_strtol(optarg, 10);
break;
case 'n':
g_user_config.ioat_chan_num = spdk_strtol(optarg, 10);
break;
case 'q':
g_user_config.queue_depth = spdk_strtol(optarg, 10);
break;
case 't':
g_user_config.time_in_sec = spdk_strtol(optarg, 10);
break;
case 'c':
g_user_config.core_mask = optarg;
break;
case 'v':
g_user_config.verify = true;
break;
case 'h':
usage(argv[0]);
exit(0);
default:
usage(argv[0]);
return 1;
}
}
if (g_user_config.xfer_size_bytes <= 0 || g_user_config.queue_depth <= 0 ||
g_user_config.time_in_sec <= 0 || !g_user_config.core_mask ||
g_user_config.ioat_chan_num <= 0) {
usage(argv[0]);
return 1;
}
return 0;
}
static void
drain_io(struct ioat_chan_entry *ioat_chan_entry)
{
spdk_ioat_flush(ioat_chan_entry->chan);
while (ioat_chan_entry->current_queue_depth > 0) {
spdk_ioat_process_events(ioat_chan_entry->chan);
}
}
static void
submit_single_xfer(struct ioat_chan_entry *ioat_chan_entry, struct ioat_task *ioat_task, void *dst,
void *src)
{
ioat_task->ioat_chan_entry = ioat_chan_entry;
ioat_task->src = src;
ioat_task->dst = dst;
spdk_ioat_build_copy(ioat_chan_entry->chan, ioat_task, ioat_done, dst, src,
g_user_config.xfer_size_bytes);
ioat_chan_entry->waiting_for_flush++;
if (ioat_chan_entry->waiting_for_flush >= ioat_chan_entry->flush_threshold) {
spdk_ioat_flush(ioat_chan_entry->chan);
ioat_chan_entry->waiting_for_flush = 0;
}
ioat_chan_entry->current_queue_depth++;
}
static int
submit_xfers(struct ioat_chan_entry *ioat_chan_entry, uint64_t queue_depth)
{
while (queue_depth-- > 0) {
void *src = NULL, *dst = NULL;
struct ioat_task *ioat_task = NULL;
src = spdk_mempool_get(ioat_chan_entry->data_pool);
dst = spdk_mempool_get(ioat_chan_entry->data_pool);
ioat_task = spdk_mempool_get(ioat_chan_entry->task_pool);
if (!ioat_task) {
fprintf(stderr, "Unable to get ioat_task\n");
return 1;
}
submit_single_xfer(ioat_chan_entry, ioat_task, dst, src);
}
return 0;
}
static int
work_fn(void *arg)
{
uint64_t tsc_end;
struct worker_thread *worker = (struct worker_thread *)arg;
struct ioat_chan_entry *t = NULL;
printf("Starting thread on core %u\n", worker->core);
tsc_end = spdk_get_ticks() + g_user_config.time_in_sec * spdk_get_ticks_hz();
t = worker->ctx;
while (t != NULL) {
/* begin to submit transfers */
t->waiting_for_flush = 0;
t->flush_threshold = g_user_config.queue_depth / 2;
if (submit_xfers(t, g_user_config.queue_depth) != 0) {
return 1;
}
t = t->next;
}
while (1) {
t = worker->ctx;
while (t != NULL) {
spdk_ioat_process_events(t->chan);
t = t->next;
}
if (spdk_get_ticks() > tsc_end) {
break;
}
}
t = worker->ctx;
while (t != NULL) {
/* begin to drain io */
t->is_draining = true;
drain_io(t);
t = t->next;
}
return 0;
}
static int
init(void)
{
struct spdk_env_opts opts;
spdk_env_opts_init(&opts);
opts.name = "ioat_perf";
opts.core_mask = g_user_config.core_mask;
if (spdk_env_init(&opts) < 0) {
return 1;
}
return 0;
}
static int
dump_result(void)
{
uint64_t total_completed = 0;
uint64_t total_failed = 0;
uint64_t total_xfer_per_sec, total_bw_in_MiBps;
struct worker_thread *worker = g_workers;
printf("Channel_ID Core Transfers Bandwidth Failed\n");
printf("-----------------------------------------------------------\n");
while (worker != NULL) {
struct ioat_chan_entry *t = worker->ctx;
while (t) {
uint64_t xfer_per_sec = t->xfer_completed / g_user_config.time_in_sec;
uint64_t bw_in_MiBps = (t->xfer_completed * g_user_config.xfer_size_bytes) /
(g_user_config.time_in_sec * 1024 * 1024);
total_completed += t->xfer_completed;
total_failed += t->xfer_failed;
if (xfer_per_sec) {
printf("%10d%10d%12" PRIu64 "/s%8" PRIu64 " MiB/s%11" PRIu64 "\n",
t->ioat_chan_id, worker->core, xfer_per_sec,
bw_in_MiBps, t->xfer_failed);
}
t = t->next;
}
worker = worker->next;
}
total_xfer_per_sec = total_completed / g_user_config.time_in_sec;
total_bw_in_MiBps = (total_completed * g_user_config.xfer_size_bytes) /
(g_user_config.time_in_sec * 1024 * 1024);
printf("===========================================================\n");
printf("Total:%26" PRIu64 "/s%8" PRIu64 " MiB/s%11" PRIu64 "\n",
total_xfer_per_sec, total_bw_in_MiBps, total_failed);
return total_failed ? 1 : 0;
}
static struct spdk_ioat_chan *
get_next_chan(void)
{
struct spdk_ioat_chan *chan;
if (g_next_device == NULL) {
return NULL;
}
chan = g_next_device->ioat;
g_next_device = TAILQ_NEXT(g_next_device, tailq);
return chan;
}
static int
associate_workers_with_chan(void)
{
struct spdk_ioat_chan *chan = get_next_chan();
struct worker_thread *worker = g_workers;
struct ioat_chan_entry *t;
char buf_pool_name[30], task_pool_name[30];
int i = 0;
while (chan != NULL) {
t = calloc(1, sizeof(struct ioat_chan_entry));
if (!t) {
return 1;
}
t->ioat_chan_id = i;
snprintf(buf_pool_name, sizeof(buf_pool_name), "buf_pool_%d", i);
snprintf(task_pool_name, sizeof(task_pool_name), "task_pool_%d", i);
t->data_pool = spdk_mempool_create(buf_pool_name,
g_user_config.queue_depth * 2, /* src + dst */
g_user_config.xfer_size_bytes,
SPDK_MEMPOOL_DEFAULT_CACHE_SIZE,
SPDK_ENV_SOCKET_ID_ANY);
t->task_pool = spdk_mempool_create(task_pool_name,
g_user_config.queue_depth,
sizeof(struct ioat_task),
SPDK_MEMPOOL_DEFAULT_CACHE_SIZE,
SPDK_ENV_SOCKET_ID_ANY);
if (!t->data_pool || !t->task_pool) {
fprintf(stderr, "Could not allocate buffer pool.\n");
spdk_mempool_free(t->data_pool);
spdk_mempool_free(t->task_pool);
free(t);
return 1;
}
printf("Associating ioat_channel %d with core %d\n", i, worker->core);
t->chan = chan;
t->next = worker->ctx;
worker->ctx = t;
worker = worker->next;
if (worker == NULL) {
worker = g_workers;
}
chan = get_next_chan();
i++;
}
return 0;
}
int
main(int argc, char **argv)
{
int rc;
struct worker_thread *worker, *main_worker;
unsigned main_core;
if (parse_args(argc, argv) != 0) {
return 1;
}
if (init() != 0) {
return 1;
}
if (register_workers() != 0) {
rc = 1;
goto cleanup;
}
if (ioat_init() != 0) {
rc = 1;
goto cleanup;
}
if (g_ioat_chan_num == 0) {
printf("No channels found\n");
rc = 1;
goto cleanup;
}
if (g_user_config.ioat_chan_num > g_ioat_chan_num) {
printf("%d channels are requested, but only %d are found,"
"so only test %d channels\n", g_user_config.ioat_chan_num,
g_ioat_chan_num, g_ioat_chan_num);
g_user_config.ioat_chan_num = g_ioat_chan_num;
}
g_next_device = TAILQ_FIRST(&g_devices);
dump_user_config(&g_user_config);
if (associate_workers_with_chan() != 0) {
rc = 1;
goto cleanup;
}
/* Launch all of the secondary workers */
main_core = spdk_env_get_current_core();
main_worker = NULL;
worker = g_workers;
while (worker != NULL) {
if (worker->core != main_core) {
spdk_env_thread_launch_pinned(worker->core, work_fn, worker);
} else {
assert(main_worker == NULL);
main_worker = worker;
}
worker = worker->next;
}
assert(main_worker != NULL);
rc = work_fn(main_worker);
if (rc != 0) {
goto cleanup;
}
spdk_env_thread_wait_all();
rc = dump_result();
cleanup:
unregister_workers();
ioat_exit();
spdk_env_fini();
return rc;
}