xref: /spdk/examples/accel/perf/accel_perf.c (revision 0cecfcb19bab8f89f838f7ea1011fe121d4edacf)
19f51cf32Spaul luse /*-
29f51cf32Spaul luse  *   BSD LICENSE
39f51cf32Spaul luse  *
49f51cf32Spaul luse  *   Copyright (c) Intel Corporation.
59f51cf32Spaul luse  *   All rights reserved.
69f51cf32Spaul luse  *
79f51cf32Spaul luse  *   Redistribution and use in source and binary forms, with or without
89f51cf32Spaul luse  *   modification, are permitted provided that the following conditions
99f51cf32Spaul luse  *   are met:
109f51cf32Spaul luse  *
119f51cf32Spaul luse  *     * Redistributions of source code must retain the above copyright
129f51cf32Spaul luse  *       notice, this list of conditions and the following disclaimer.
139f51cf32Spaul luse  *     * Redistributions in binary form must reproduce the above copyright
149f51cf32Spaul luse  *       notice, this list of conditions and the following disclaimer in
159f51cf32Spaul luse  *       the documentation and/or other materials provided with the
169f51cf32Spaul luse  *       distribution.
179f51cf32Spaul luse  *     * Neither the name of Intel Corporation nor the names of its
189f51cf32Spaul luse  *       contributors may be used to endorse or promote products derived
199f51cf32Spaul luse  *       from this software without specific prior written permission.
209f51cf32Spaul luse  *
219f51cf32Spaul luse  *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
229f51cf32Spaul luse  *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
239f51cf32Spaul luse  *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
249f51cf32Spaul luse  *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
259f51cf32Spaul luse  *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
269f51cf32Spaul luse  *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
279f51cf32Spaul luse  *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
289f51cf32Spaul luse  *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
299f51cf32Spaul luse  *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
309f51cf32Spaul luse  *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
319f51cf32Spaul luse  *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
329f51cf32Spaul luse  */
339f51cf32Spaul luse 
349f51cf32Spaul luse #include "spdk/stdinc.h"
359f51cf32Spaul luse #include "spdk/thread.h"
369f51cf32Spaul luse #include "spdk/env.h"
379f51cf32Spaul luse #include "spdk/event.h"
389f51cf32Spaul luse #include "spdk/log.h"
399f51cf32Spaul luse #include "spdk/string.h"
409f51cf32Spaul luse #include "spdk/accel_engine.h"
41e69375bfSpaul luse #include "spdk/crc32.h"
42*0cecfcb1Spaul luse #include "spdk/util.h"
439f51cf32Spaul luse 
44b9218b7aSpaul luse #define DATA_PATTERN 0x5a
450ef079c6Spaul luse #define ALIGN_4K 0x1000
46b9218b7aSpaul luse 
479f51cf32Spaul luse static uint64_t	g_tsc_rate;
489f51cf32Spaul luse static uint64_t g_tsc_us_rate;
499f51cf32Spaul luse static uint64_t g_tsc_end;
509f51cf32Spaul luse static int g_xfer_size_bytes = 4096;
519f51cf32Spaul luse static int g_queue_depth = 32;
529f51cf32Spaul luse static int g_time_in_sec = 5;
53e69375bfSpaul luse static uint32_t g_crc32c_seed = 0;
54b9218b7aSpaul luse static int g_fail_percent_goal = 0;
5589495464Spaul luse static uint8_t g_fill_pattern = 255;
569f51cf32Spaul luse static bool g_verify = false;
572a0c66d0Spaul luse static const char *g_workload_type = NULL;
58514be889Spaul luse static enum accel_capability g_workload_selection;
599f51cf32Spaul luse static struct worker_thread *g_workers = NULL;
609f51cf32Spaul luse static int g_num_workers = 0;
619f51cf32Spaul luse static pthread_mutex_t g_workers_lock = PTHREAD_MUTEX_INITIALIZER;
62a34fc12bSpaul luse uint64_t g_capabilites;
63*0cecfcb1Spaul luse struct ap_task;
649f51cf32Spaul luse 
659f51cf32Spaul luse struct worker_thread {
669f51cf32Spaul luse 	struct spdk_io_channel		*ch;
679f51cf32Spaul luse 	uint64_t			xfer_completed;
689f51cf32Spaul luse 	uint64_t			xfer_failed;
69b9218b7aSpaul luse 	uint64_t			injected_miscompares;
709f51cf32Spaul luse 	uint64_t			current_queue_depth;
71*0cecfcb1Spaul luse 	TAILQ_HEAD(, ap_task)		tasks;
729f51cf32Spaul luse 	struct worker_thread		*next;
739f51cf32Spaul luse 	unsigned			core;
749f51cf32Spaul luse 	struct spdk_thread		*thread;
759f51cf32Spaul luse 	bool				is_draining;
769f51cf32Spaul luse 	struct spdk_poller		*is_draining_poller;
779f51cf32Spaul luse 	struct spdk_poller		*stop_poller;
789f51cf32Spaul luse };
799f51cf32Spaul luse 
809f51cf32Spaul luse struct ap_task {
819f51cf32Spaul luse 	void			*src;
829f51cf32Spaul luse 	void			*dst;
830ef079c6Spaul luse 	void			*dst2;
849f51cf32Spaul luse 	struct worker_thread	*worker;
85b9218b7aSpaul luse 	int			status;
86b9218b7aSpaul luse 	int			expected_status; /* used for compare */
87*0cecfcb1Spaul luse 	TAILQ_ENTRY(ap_task)	link;
889f51cf32Spaul luse };
899f51cf32Spaul luse 
909f51cf32Spaul luse static void
919f51cf32Spaul luse dump_user_config(struct spdk_app_opts *opts)
929f51cf32Spaul luse {
939f51cf32Spaul luse 	printf("SPDK Configuration:\n");
949f51cf32Spaul luse 	printf("Core mask:      %s\n\n", opts->reactor_mask);
959f51cf32Spaul luse 	printf("Accel Perf Configuration:\n");
962a0c66d0Spaul luse 	printf("Workload Type:  %s\n", g_workload_type);
97b9218b7aSpaul luse 	if (g_workload_selection == ACCEL_CRC32C) {
98b9218b7aSpaul luse 		printf("CRC-32C seed:   %u\n", g_crc32c_seed);
9989495464Spaul luse 	} else if (g_workload_selection == ACCEL_FILL) {
10089495464Spaul luse 		printf("Fill pattern:   0x%x\n", g_fill_pattern);
101b9218b7aSpaul luse 	} else if ((g_workload_selection == ACCEL_COMPARE) && g_fail_percent_goal > 0) {
10289495464Spaul luse 		printf("Failure inject: %u percent\n", g_fail_percent_goal);
103e69375bfSpaul luse 	}
1049f51cf32Spaul luse 	printf("Transfer size:  %u bytes\n", g_xfer_size_bytes);
1059f51cf32Spaul luse 	printf("Queue depth:    %u\n", g_queue_depth);
1069f51cf32Spaul luse 	printf("Run time:       %u seconds\n", g_time_in_sec);
1079f51cf32Spaul luse 	printf("Verify:         %s\n\n", g_verify ? "Yes" : "No");
1089f51cf32Spaul luse }
1099f51cf32Spaul luse 
1109f51cf32Spaul luse static void
1119f51cf32Spaul luse usage(void)
1129f51cf32Spaul luse {
1139f51cf32Spaul luse 	printf("accel_perf options:\n");
1149f51cf32Spaul luse 	printf("\t[-h help message]\n");
1159f51cf32Spaul luse 	printf("\t[-q queue depth]\n");
1169f51cf32Spaul luse 	printf("\t[-n number of channels]\n");
1179f51cf32Spaul luse 	printf("\t[-o transfer size in bytes]\n");
1189f51cf32Spaul luse 	printf("\t[-t time in seconds]\n");
1190ef079c6Spaul luse 	printf("\t[-w workload type must be one of these: copy, fill, crc32c, compare, dualcast\n");
120e69375bfSpaul luse 	printf("\t[-s for crc32c workload, use this seed value (default 0)\n");
121b9218b7aSpaul luse 	printf("\t[-P for compare workload, percentage of operations that should miscompare (percent, default 0)\n");
12289495464Spaul luse 	printf("\t[-f for fill workload, use this BYTE value (default 255)\n");
1232a0c66d0Spaul luse 	printf("\t[-y verify result if this switch is on]\n");
1249f51cf32Spaul luse }
1259f51cf32Spaul luse 
1269f51cf32Spaul luse static int
1279f51cf32Spaul luse parse_args(int argc, char *argv)
1289f51cf32Spaul luse {
1299f51cf32Spaul luse 	switch (argc) {
13089495464Spaul luse 	case 'f':
13189495464Spaul luse 		g_fill_pattern = (uint8_t)spdk_strtol(optarg, 10);
13289495464Spaul luse 		break;
1339f51cf32Spaul luse 	case 'o':
1349f51cf32Spaul luse 		g_xfer_size_bytes = spdk_strtol(optarg, 10);
1359f51cf32Spaul luse 		break;
136b9218b7aSpaul luse 	case 'P':
137b9218b7aSpaul luse 		g_fail_percent_goal = spdk_strtol(optarg, 10);
138b9218b7aSpaul luse 		break;
1399f51cf32Spaul luse 	case 'q':
1409f51cf32Spaul luse 		g_queue_depth = spdk_strtol(optarg, 10);
1419f51cf32Spaul luse 		break;
142e69375bfSpaul luse 	case 's':
143e69375bfSpaul luse 		g_crc32c_seed = spdk_strtol(optarg, 10);
144e69375bfSpaul luse 		break;
1459f51cf32Spaul luse 	case 't':
1469f51cf32Spaul luse 		g_time_in_sec = spdk_strtol(optarg, 10);
1479f51cf32Spaul luse 		break;
1489f51cf32Spaul luse 	case 'y':
1499f51cf32Spaul luse 		g_verify = true;
1509f51cf32Spaul luse 		break;
1512a0c66d0Spaul luse 	case 'w':
1522a0c66d0Spaul luse 		g_workload_type = optarg;
153514be889Spaul luse 		if (!strcmp(g_workload_type, "copy")) {
154514be889Spaul luse 			g_workload_selection = ACCEL_COPY;
155514be889Spaul luse 		} else if (!strcmp(g_workload_type, "fill")) {
156514be889Spaul luse 			g_workload_selection = ACCEL_FILL;
157e69375bfSpaul luse 		} else if (!strcmp(g_workload_type, "crc32c")) {
158e69375bfSpaul luse 			g_workload_selection = ACCEL_CRC32C;
159b9218b7aSpaul luse 		} else if (!strcmp(g_workload_type, "compare")) {
160b9218b7aSpaul luse 			g_workload_selection = ACCEL_COMPARE;
1610ef079c6Spaul luse 		} else if (!strcmp(g_workload_type, "dualcast")) {
1620ef079c6Spaul luse 			g_workload_selection = ACCEL_DUALCAST;
163514be889Spaul luse 		}
1642a0c66d0Spaul luse 		break;
1659f51cf32Spaul luse 	default:
1669f51cf32Spaul luse 		usage();
1679f51cf32Spaul luse 		return 1;
1689f51cf32Spaul luse 	}
1699f51cf32Spaul luse 	return 0;
1709f51cf32Spaul luse }
1719f51cf32Spaul luse 
1729f51cf32Spaul luse static void
1739f51cf32Spaul luse unregister_worker(void *arg1)
1749f51cf32Spaul luse {
1759f51cf32Spaul luse 	struct worker_thread *worker = arg1;
176*0cecfcb1Spaul luse 	struct ap_task *task;
1779f51cf32Spaul luse 
178*0cecfcb1Spaul luse 	while (!TAILQ_EMPTY(&worker->tasks)) {
179*0cecfcb1Spaul luse 		task = TAILQ_FIRST(&worker->tasks);
180*0cecfcb1Spaul luse 		TAILQ_REMOVE(&worker->tasks, task, link);
181*0cecfcb1Spaul luse 		free(task);
182*0cecfcb1Spaul luse 	}
1839f51cf32Spaul luse 	spdk_put_io_channel(worker->ch);
1849f51cf32Spaul luse 	pthread_mutex_lock(&g_workers_lock);
1859f51cf32Spaul luse 	assert(g_num_workers >= 1);
1869f51cf32Spaul luse 	if (--g_num_workers == 0) {
1879f51cf32Spaul luse 		pthread_mutex_unlock(&g_workers_lock);
1889f51cf32Spaul luse 		spdk_app_stop(0);
1899f51cf32Spaul luse 	}
1909f51cf32Spaul luse 	pthread_mutex_unlock(&g_workers_lock);
1919f51cf32Spaul luse }
1929f51cf32Spaul luse 
1939f51cf32Spaul luse static void accel_done(void *ref, int status);
1949f51cf32Spaul luse 
1959f51cf32Spaul luse static void
1969f51cf32Spaul luse _submit_single(void *arg1, void *arg2)
1979f51cf32Spaul luse {
1989f51cf32Spaul luse 	struct worker_thread *worker = arg1;
1999f51cf32Spaul luse 	struct ap_task *task = arg2;
200b9218b7aSpaul luse 	int random_num;
20140ec8e97Spaul luse 	int rc = 0;
2029f51cf32Spaul luse 
2039f51cf32Spaul luse 	assert(worker);
2049f51cf32Spaul luse 
2059f51cf32Spaul luse 	task->worker = worker;
2069f51cf32Spaul luse 	task->worker->current_queue_depth++;
207e69375bfSpaul luse 	switch (g_workload_selection) {
208e69375bfSpaul luse 	case ACCEL_COPY:
209e8463f87Spaul luse 		rc = spdk_accel_submit_copy(worker->ch, task->dst, task->src,
210e8463f87Spaul luse 					    g_xfer_size_bytes, accel_done, task);
211e69375bfSpaul luse 		break;
212e69375bfSpaul luse 	case ACCEL_FILL:
2132a0c66d0Spaul luse 		/* For fill use the first byte of the task->dst buffer */
214ee7e31f9Spaul luse 		rc = spdk_accel_submit_fill(worker->ch, task->dst, *(uint8_t *)task->src,
215e8463f87Spaul luse 					    g_xfer_size_bytes, accel_done, task);
216e69375bfSpaul luse 		break;
217e69375bfSpaul luse 	case ACCEL_CRC32C:
218e8463f87Spaul luse 		rc = spdk_accel_submit_crc32c(worker->ch, (uint32_t *)task->dst,
219e8463f87Spaul luse 					      task->src, g_crc32c_seed,
220e8463f87Spaul luse 					      g_xfer_size_bytes, accel_done, task);
221e69375bfSpaul luse 		break;
222b9218b7aSpaul luse 	case ACCEL_COMPARE:
223b9218b7aSpaul luse 		random_num = rand() % 100;
224b9218b7aSpaul luse 		if (random_num < g_fail_percent_goal) {
225b9218b7aSpaul luse 			task->expected_status = -EILSEQ;
226b9218b7aSpaul luse 			*(uint8_t *)task->dst = ~DATA_PATTERN;
227b9218b7aSpaul luse 		} else {
228b9218b7aSpaul luse 			task->expected_status = 0;
229b9218b7aSpaul luse 			*(uint8_t *)task->dst = DATA_PATTERN;
230b9218b7aSpaul luse 		}
231ee7e31f9Spaul luse 		rc = spdk_accel_submit_compare(worker->ch, task->dst, task->src,
232e8463f87Spaul luse 					       g_xfer_size_bytes, accel_done, task);
233b9218b7aSpaul luse 		break;
2340ef079c6Spaul luse 	case ACCEL_DUALCAST:
235ee7e31f9Spaul luse 		rc = spdk_accel_submit_dualcast(worker->ch, task->dst, task->dst2,
236e8463f87Spaul luse 						task->src, g_xfer_size_bytes, accel_done, task);
2370ef079c6Spaul luse 		break;
238e69375bfSpaul luse 	default:
2392a0c66d0Spaul luse 		assert(false);
240e69375bfSpaul luse 		break;
241e69375bfSpaul luse 
2422a0c66d0Spaul luse 	}
24340ec8e97Spaul luse 
24440ec8e97Spaul luse 	if (rc) {
245e8463f87Spaul luse 		accel_done(task, rc);
24640ec8e97Spaul luse 	}
2479f51cf32Spaul luse }
2489f51cf32Spaul luse 
2499f51cf32Spaul luse static void
2509f51cf32Spaul luse _accel_done(void *arg1)
2519f51cf32Spaul luse {
2529f51cf32Spaul luse 	struct ap_task *task = arg1;
2539f51cf32Spaul luse 	struct worker_thread *worker = task->worker;
254e69375bfSpaul luse 	uint32_t sw_crc32c;
2559f51cf32Spaul luse 
2569f51cf32Spaul luse 	assert(worker);
2579f51cf32Spaul luse 	assert(worker->current_queue_depth > 0);
2589f51cf32Spaul luse 
259b9218b7aSpaul luse 	if (g_verify && task->status == 0) {
260b9218b7aSpaul luse 		switch (g_workload_selection) {
261b9218b7aSpaul luse 		case ACCEL_CRC32C:
262e69375bfSpaul luse 			/* calculate sw CRC-32C and compare to sw aceel result. */
263e69375bfSpaul luse 			sw_crc32c = spdk_crc32c_update(task->src, g_xfer_size_bytes, ~g_crc32c_seed);
264e69375bfSpaul luse 			if (*(uint32_t *)task->dst != sw_crc32c) {
265e69375bfSpaul luse 				SPDK_NOTICELOG("CRC-32C miscompare\n");
266e69375bfSpaul luse 				worker->xfer_failed++;
267e69375bfSpaul luse 			}
268b9218b7aSpaul luse 			break;
269b9218b7aSpaul luse 		case ACCEL_COPY:
270b9218b7aSpaul luse 			if (memcmp(task->src, task->dst, g_xfer_size_bytes)) {
2719f51cf32Spaul luse 				SPDK_NOTICELOG("Data miscompare\n");
2729f51cf32Spaul luse 				worker->xfer_failed++;
273b9218b7aSpaul luse 			}
274b9218b7aSpaul luse 			break;
2750ef079c6Spaul luse 		case ACCEL_DUALCAST:
2760ef079c6Spaul luse 			if (memcmp(task->src, task->dst, g_xfer_size_bytes)) {
2770ef079c6Spaul luse 				SPDK_NOTICELOG("Data miscompare, first destination\n");
2780ef079c6Spaul luse 				worker->xfer_failed++;
2790ef079c6Spaul luse 			}
2800ef079c6Spaul luse 			if (memcmp(task->src, task->dst2, g_xfer_size_bytes)) {
2810ef079c6Spaul luse 				SPDK_NOTICELOG("Data miscompare, second destination\n");
2820ef079c6Spaul luse 				worker->xfer_failed++;
2830ef079c6Spaul luse 			}
2840ef079c6Spaul luse 			break;
285d207237fSpaul luse 		case ACCEL_FILL:
286d207237fSpaul luse 			if (memcmp(task->dst, task->src, g_xfer_size_bytes)) {
287d207237fSpaul luse 				SPDK_NOTICELOG("Data miscompare\n");
288d207237fSpaul luse 				worker->xfer_failed++;
289d207237fSpaul luse 			}
290d207237fSpaul luse 			break;
2918cee297cSpaul luse 		case ACCEL_COMPARE:
2928cee297cSpaul luse 			break;
293b9218b7aSpaul luse 		default:
294b9218b7aSpaul luse 			assert(false);
295b9218b7aSpaul luse 			break;
2969f51cf32Spaul luse 		}
2979f51cf32Spaul luse 	}
298b9218b7aSpaul luse 
299b9218b7aSpaul luse 	if (task->expected_status == -EILSEQ) {
300b9218b7aSpaul luse 		assert(task->status != 0);
301b9218b7aSpaul luse 		worker->injected_miscompares++;
302b9218b7aSpaul luse 	} else if (task->status) {
303b9218b7aSpaul luse 		/* Expected to pass but API reported error. */
304b9218b7aSpaul luse 		worker->xfer_failed++;
305b9218b7aSpaul luse 	}
306b9218b7aSpaul luse 
3079f51cf32Spaul luse 	worker->xfer_completed++;
3089f51cf32Spaul luse 	worker->current_queue_depth--;
3099f51cf32Spaul luse 
31040ec8e97Spaul luse 	if (!worker->is_draining) {
3119f51cf32Spaul luse 		_submit_single(worker, task);
3129f51cf32Spaul luse 	} else {
313b9218b7aSpaul luse 		spdk_free(task->src);
314b9218b7aSpaul luse 		spdk_free(task->dst);
3150ef079c6Spaul luse 		if (g_workload_selection == ACCEL_DUALCAST) {
3160ef079c6Spaul luse 			spdk_free(task->dst2);
3170ef079c6Spaul luse 		}
318*0cecfcb1Spaul luse 		TAILQ_INSERT_TAIL(&worker->tasks, task, link);
3199f51cf32Spaul luse 	}
3209f51cf32Spaul luse }
3219f51cf32Spaul luse 
322a34fc12bSpaul luse static void
323e8463f87Spaul luse batch_done(void *cb_arg, int status)
324a34fc12bSpaul luse {
325e8463f87Spaul luse 	struct ap_task *task = (struct ap_task *)cb_arg;
326a34fc12bSpaul luse 	struct worker_thread *worker = task->worker;
327a34fc12bSpaul luse 
328a34fc12bSpaul luse 	worker->current_queue_depth--;
329*0cecfcb1Spaul luse 	TAILQ_INSERT_TAIL(&worker->tasks, task, link);
330a34fc12bSpaul luse }
331a34fc12bSpaul luse 
3329f51cf32Spaul luse static int
3339f51cf32Spaul luse dump_result(void)
3349f51cf32Spaul luse {
3359f51cf32Spaul luse 	uint64_t total_completed = 0;
3369f51cf32Spaul luse 	uint64_t total_failed = 0;
337b9218b7aSpaul luse 	uint64_t total_miscompared = 0;
3389f51cf32Spaul luse 	uint64_t total_xfer_per_sec, total_bw_in_MiBps;
3399f51cf32Spaul luse 	struct worker_thread *worker = g_workers;
3409f51cf32Spaul luse 
341b9218b7aSpaul luse 	printf("\nCore           Transfers     Bandwidth     Failed     Miscompares\n");
342b9218b7aSpaul luse 	printf("-----------------------------------------------------------------\n");
3439f51cf32Spaul luse 	while (worker != NULL) {
3449f51cf32Spaul luse 
3459f51cf32Spaul luse 		uint64_t xfer_per_sec = worker->xfer_completed / g_time_in_sec;
3469f51cf32Spaul luse 		uint64_t bw_in_MiBps = (worker->xfer_completed * g_xfer_size_bytes) /
3479f51cf32Spaul luse 				       (g_time_in_sec * 1024 * 1024);
3489f51cf32Spaul luse 
3499f51cf32Spaul luse 		total_completed += worker->xfer_completed;
3509f51cf32Spaul luse 		total_failed += worker->xfer_failed;
351b9218b7aSpaul luse 		total_miscompared += worker->injected_miscompares;
3529f51cf32Spaul luse 
3539f51cf32Spaul luse 		if (xfer_per_sec) {
354b9218b7aSpaul luse 			printf("%10d%12" PRIu64 "/s%8" PRIu64 " MiB/s%11" PRIu64 " %11" PRIu64 "\n",
3559f51cf32Spaul luse 			       worker->core, xfer_per_sec,
356b9218b7aSpaul luse 			       bw_in_MiBps, worker->xfer_failed, worker->injected_miscompares);
3579f51cf32Spaul luse 		}
3589f51cf32Spaul luse 
3599f51cf32Spaul luse 		worker = worker->next;
3609f51cf32Spaul luse 	}
3619f51cf32Spaul luse 
3629f51cf32Spaul luse 	total_xfer_per_sec = total_completed / g_time_in_sec;
3639f51cf32Spaul luse 	total_bw_in_MiBps = (total_completed * g_xfer_size_bytes) /
3649f51cf32Spaul luse 			    (g_time_in_sec * 1024 * 1024);
3659f51cf32Spaul luse 
366b9218b7aSpaul luse 	printf("==================================================================\n");
367b9218b7aSpaul luse 	printf("Total:%16" PRIu64 "/s%8" PRIu64 " MiB/s%11" PRIu64 " %11" PRIu64"\n\n",
368b9218b7aSpaul luse 	       total_xfer_per_sec, total_bw_in_MiBps, total_failed, total_miscompared);
3699f51cf32Spaul luse 
3709f51cf32Spaul luse 	return total_failed ? 1 : 0;
3719f51cf32Spaul luse }
3729f51cf32Spaul luse 
3739f51cf32Spaul luse static int
3749f51cf32Spaul luse _check_draining(void *arg)
3759f51cf32Spaul luse {
3769f51cf32Spaul luse 	struct worker_thread *worker = arg;
3779f51cf32Spaul luse 
3789f51cf32Spaul luse 	assert(worker);
3799f51cf32Spaul luse 
3809f51cf32Spaul luse 	if (worker->current_queue_depth == 0) {
3819f51cf32Spaul luse 		spdk_poller_unregister(&worker->is_draining_poller);
3829f51cf32Spaul luse 		unregister_worker(worker);
3839f51cf32Spaul luse 	}
3849f51cf32Spaul luse 
3859f51cf32Spaul luse 	return -1;
3869f51cf32Spaul luse }
3879f51cf32Spaul luse 
3889f51cf32Spaul luse static int
3899f51cf32Spaul luse _worker_stop(void *arg)
3909f51cf32Spaul luse {
3919f51cf32Spaul luse 	struct worker_thread *worker = arg;
3929f51cf32Spaul luse 
3939f51cf32Spaul luse 	assert(worker);
3949f51cf32Spaul luse 
3959f51cf32Spaul luse 	spdk_poller_unregister(&worker->stop_poller);
3969f51cf32Spaul luse 
3979f51cf32Spaul luse 	/* now let the worker drain and check it's outstanding IO with a poller */
3989f51cf32Spaul luse 	worker->is_draining = true;
399ab0bc5c2SShuhei Matsumoto 	worker->is_draining_poller = SPDK_POLLER_REGISTER(_check_draining, worker, 0);
4009f51cf32Spaul luse 
4019f51cf32Spaul luse 	return 0;
4029f51cf32Spaul luse }
4039f51cf32Spaul luse 
4049f51cf32Spaul luse static void
4059f51cf32Spaul luse _init_thread_done(void *ctx)
4069f51cf32Spaul luse {
4079f51cf32Spaul luse }
4089f51cf32Spaul luse 
409a34fc12bSpaul luse static int
410a34fc12bSpaul luse _get_task_data_bufs(struct ap_task *task)
4119f51cf32Spaul luse {
4120ef079c6Spaul luse 	uint32_t align = 0;
4139f51cf32Spaul luse 
4140ef079c6Spaul luse 	/* For dualcast, the DSA HW requires 4K alignment on destination addresses but
4150ef079c6Spaul luse 	 * we do this for all engines to keep it simple.
4160ef079c6Spaul luse 	 */
4170ef079c6Spaul luse 	if (g_workload_selection == ACCEL_DUALCAST) {
4180ef079c6Spaul luse 		align = ALIGN_4K;
4190ef079c6Spaul luse 	}
4200ef079c6Spaul luse 
421a34fc12bSpaul luse 	task->src = spdk_dma_zmalloc(g_xfer_size_bytes, 0, NULL);
422a34fc12bSpaul luse 	if (task->src == NULL) {
423a34fc12bSpaul luse 		fprintf(stderr, "Unable to alloc src buffer\n");
424a34fc12bSpaul luse 		return -ENOMEM;
425a34fc12bSpaul luse 	}
426a34fc12bSpaul luse 	memset(task->src, DATA_PATTERN, g_xfer_size_bytes);
427a34fc12bSpaul luse 
428a34fc12bSpaul luse 	task->dst = spdk_dma_zmalloc(g_xfer_size_bytes, align, NULL);
429a34fc12bSpaul luse 	if (task->dst == NULL) {
430a34fc12bSpaul luse 		fprintf(stderr, "Unable to alloc dst buffer\n");
431a34fc12bSpaul luse 		return -ENOMEM;
432a34fc12bSpaul luse 	}
433a34fc12bSpaul luse 
434a34fc12bSpaul luse 	/* For compare we want the buffers to match, otherwise not. */
435a34fc12bSpaul luse 	if (g_workload_selection == ACCEL_COMPARE) {
436a34fc12bSpaul luse 		memset(task->dst, DATA_PATTERN, g_xfer_size_bytes);
437a34fc12bSpaul luse 	} else {
438a34fc12bSpaul luse 		memset(task->dst, ~DATA_PATTERN, g_xfer_size_bytes);
439a34fc12bSpaul luse 	}
440a34fc12bSpaul luse 
441d207237fSpaul luse 	/* For fill, set the entire src buffer so we can check if verify is enabled. */
442d207237fSpaul luse 	if (g_workload_selection == ACCEL_FILL) {
443d207237fSpaul luse 		memset(task->src, g_fill_pattern, g_xfer_size_bytes);
444d207237fSpaul luse 	}
445d207237fSpaul luse 
446a34fc12bSpaul luse 	if (g_workload_selection == ACCEL_DUALCAST) {
447a34fc12bSpaul luse 		task->dst2 = spdk_dma_zmalloc(g_xfer_size_bytes, align, NULL);
448a34fc12bSpaul luse 		if (task->dst2 == NULL) {
449a34fc12bSpaul luse 			fprintf(stderr, "Unable to alloc dst buffer\n");
450a34fc12bSpaul luse 			return -ENOMEM;
451a34fc12bSpaul luse 		}
452a34fc12bSpaul luse 		memset(task->dst2, ~DATA_PATTERN, g_xfer_size_bytes);
453a34fc12bSpaul luse 	}
454a34fc12bSpaul luse 
455a34fc12bSpaul luse 	return 0;
456a34fc12bSpaul luse }
457a34fc12bSpaul luse 
458a34fc12bSpaul luse static int
459a34fc12bSpaul luse _batch_prep_cmd(struct worker_thread *worker, struct ap_task *task, struct spdk_accel_batch *batch)
460a34fc12bSpaul luse {
461a34fc12bSpaul luse 	int rc = 0;
462a34fc12bSpaul luse 
463a34fc12bSpaul luse 	switch (g_workload_selection) {
464a34fc12bSpaul luse 	case ACCEL_COPY:
465ee7e31f9Spaul luse 		rc = spdk_accel_batch_prep_copy(worker->ch, batch, task->dst,
466e8463f87Spaul luse 						task->src, g_xfer_size_bytes, accel_done, task);
467a34fc12bSpaul luse 		break;
468ec086e6fSpaul luse 	case ACCEL_DUALCAST:
469ee7e31f9Spaul luse 		rc = spdk_accel_batch_prep_dualcast(worker->ch, batch, task->dst, task->dst2,
470e8463f87Spaul luse 						    task->src, g_xfer_size_bytes, accel_done, task);
471ec086e6fSpaul luse 		break;
472d137ba30Spaul luse 	case ACCEL_COMPARE:
473ee7e31f9Spaul luse 		rc = spdk_accel_batch_prep_compare(worker->ch, batch, task->dst, task->src,
474e8463f87Spaul luse 						   g_xfer_size_bytes, accel_done, task);
475d137ba30Spaul luse 		break;
476d207237fSpaul luse 	case ACCEL_FILL:
477e8463f87Spaul luse 		rc = spdk_accel_batch_prep_fill(worker->ch, batch, task->dst,
478e8463f87Spaul luse 						*(uint8_t *)task->src,
479e8463f87Spaul luse 						g_xfer_size_bytes, accel_done, task);
480d207237fSpaul luse 		break;
481e54f14a5Spaul luse 	case ACCEL_CRC32C:
482e8463f87Spaul luse 		rc = spdk_accel_batch_prep_crc32c(worker->ch, batch, (uint32_t *)task->dst,
483e8463f87Spaul luse 						  task->src, g_crc32c_seed, g_xfer_size_bytes, accel_done, task);
484e54f14a5Spaul luse 		break;
485a34fc12bSpaul luse 	default:
486a34fc12bSpaul luse 		assert(false);
487a34fc12bSpaul luse 		break;
488a34fc12bSpaul luse 	}
489a34fc12bSpaul luse 
490a34fc12bSpaul luse 	return rc;
491a34fc12bSpaul luse }
492a34fc12bSpaul luse 
493a34fc12bSpaul luse static void
494a34fc12bSpaul luse _init_thread(void *arg1)
495a34fc12bSpaul luse {
496a34fc12bSpaul luse 	struct worker_thread *worker;
497a34fc12bSpaul luse 	struct ap_task *task;
498*0cecfcb1Spaul luse 	int i, rc, max_per_batch, batch_count, num_tasks;
499a34fc12bSpaul luse 	int remaining = g_queue_depth;
500a34fc12bSpaul luse 	struct spdk_accel_batch *batch, *new_batch;
501a34fc12bSpaul luse 
502a34fc12bSpaul luse 	worker = calloc(1, sizeof(*worker));
503a34fc12bSpaul luse 	if (worker == NULL) {
504a34fc12bSpaul luse 		fprintf(stderr, "Unable to allocate worker\n");
505a34fc12bSpaul luse 		return;
506a34fc12bSpaul luse 	}
507a34fc12bSpaul luse 
5089f51cf32Spaul luse 	worker->core = spdk_env_get_current_core();
5099f51cf32Spaul luse 	worker->thread = spdk_get_thread();
5109f51cf32Spaul luse 	worker->next = g_workers;
5119f51cf32Spaul luse 	worker->ch = spdk_accel_engine_get_io_channel();
512b9218b7aSpaul luse 
513*0cecfcb1Spaul luse 	max_per_batch = spdk_accel_batch_get_max(worker->ch);
514*0cecfcb1Spaul luse 	assert(max_per_batch > 0);
515*0cecfcb1Spaul luse 	num_tasks = g_queue_depth + spdk_divide_round_up(g_queue_depth, max_per_batch);
516*0cecfcb1Spaul luse 
517*0cecfcb1Spaul luse 	TAILQ_INIT(&worker->tasks);
518*0cecfcb1Spaul luse 	for (i = 0; i < num_tasks; i++) {
519*0cecfcb1Spaul luse 		task = calloc(1, sizeof(struct ap_task));
520*0cecfcb1Spaul luse 		if (task == NULL) {
521*0cecfcb1Spaul luse 			fprintf(stderr, "Could not allocate task.\n");
5229f51cf32Spaul luse 			return;
523*0cecfcb1Spaul luse 			/* TODO cleanup */
524*0cecfcb1Spaul luse 		}
525*0cecfcb1Spaul luse 		TAILQ_INSERT_TAIL(&worker->tasks, task, link);
5269f51cf32Spaul luse 	}
5279f51cf32Spaul luse 
5289f51cf32Spaul luse 	/* Register a poller that will stop the worker at time elapsed */
529ab0bc5c2SShuhei Matsumoto 	worker->stop_poller = SPDK_POLLER_REGISTER(_worker_stop, worker,
5309f51cf32Spaul luse 			      g_time_in_sec * 1000000ULL);
5319f51cf32Spaul luse 
5329f51cf32Spaul luse 	g_workers = worker;
5339f51cf32Spaul luse 	pthread_mutex_lock(&g_workers_lock);
5349f51cf32Spaul luse 	g_num_workers++;
5359f51cf32Spaul luse 	pthread_mutex_unlock(&g_workers_lock);
5369f51cf32Spaul luse 
537f295f5b3Spaul luse 	/* Batching is only possible if there is at least 2 operations. */
538f295f5b3Spaul luse 	if (g_queue_depth > 1) {
539a34fc12bSpaul luse 
540a34fc12bSpaul luse 		/* Outter loop sets up each batch command, inner loop populates the
541a34fc12bSpaul luse 		 * batch descriptors.
542a34fc12bSpaul luse 		 */
543a34fc12bSpaul luse 		do {
544a34fc12bSpaul luse 			new_batch = spdk_accel_batch_create(worker->ch);
545a34fc12bSpaul luse 			if (new_batch == NULL) {
546a34fc12bSpaul luse 				break;
547a34fc12bSpaul luse 			}
548a34fc12bSpaul luse 
549a34fc12bSpaul luse 			batch = new_batch;
550a34fc12bSpaul luse 			batch_count = 0;
551a34fc12bSpaul luse 
552a34fc12bSpaul luse 			do {
553*0cecfcb1Spaul luse 				if (!TAILQ_EMPTY(&worker->tasks)) {
554*0cecfcb1Spaul luse 					task = TAILQ_FIRST(&worker->tasks);
555*0cecfcb1Spaul luse 					TAILQ_REMOVE(&worker->tasks, task, link);
556*0cecfcb1Spaul luse 				} else {
5579f51cf32Spaul luse 					fprintf(stderr, "Unable to get accel_task\n");
558a34fc12bSpaul luse 					goto error;
559a34fc12bSpaul luse 				}
560a34fc12bSpaul luse 				task->worker = worker;
561a34fc12bSpaul luse 				task->worker->current_queue_depth++;
562a34fc12bSpaul luse 
563a34fc12bSpaul luse 				if (_get_task_data_bufs(task)) {
564a34fc12bSpaul luse 					fprintf(stderr, "Unable to get data bufs\n");
565a34fc12bSpaul luse 					goto error;
5669f51cf32Spaul luse 				}
567b9218b7aSpaul luse 
568a34fc12bSpaul luse 				rc = _batch_prep_cmd(worker, task, batch);
569a34fc12bSpaul luse 				if (rc) {
570a34fc12bSpaul luse 					fprintf(stderr, "error preping command\n");
571a34fc12bSpaul luse 					goto error;
572a34fc12bSpaul luse 				}
573a34fc12bSpaul luse 				remaining--;
574a34fc12bSpaul luse 				batch_count++;
575a34fc12bSpaul luse 			} while (batch_count < max_per_batch && remaining > 0);
576a34fc12bSpaul luse 
577a34fc12bSpaul luse 			/* Now send the batch command. */
578*0cecfcb1Spaul luse 			if (!TAILQ_EMPTY(&worker->tasks)) {
579*0cecfcb1Spaul luse 				task = TAILQ_FIRST(&worker->tasks);
580*0cecfcb1Spaul luse 				TAILQ_REMOVE(&worker->tasks, task, link);
581*0cecfcb1Spaul luse 			} else {
582a34fc12bSpaul luse 				fprintf(stderr, "Unable to get accel_task\n");
583a34fc12bSpaul luse 				goto error;
584a34fc12bSpaul luse 			}
585a34fc12bSpaul luse 			task->worker = worker;
586a34fc12bSpaul luse 			task->worker->current_queue_depth++;
587a34fc12bSpaul luse 
588e8463f87Spaul luse 			rc = spdk_accel_batch_submit(worker->ch, batch, batch_done, task);
589a34fc12bSpaul luse 			if (rc) {
590a34fc12bSpaul luse 				fprintf(stderr, "error ending batch %d\n", rc);
591a34fc12bSpaul luse 				goto error;
592a34fc12bSpaul luse 			}
593a34fc12bSpaul luse 			/* We can't build a batch unless it has 2 descriptors (per spec). */
594a34fc12bSpaul luse 		} while (remaining > 1);
595a34fc12bSpaul luse 
596a34fc12bSpaul luse 		/* If there are no more left, we're done. */
597a34fc12bSpaul luse 		if (remaining == 0) {
598b9218b7aSpaul luse 			return;
599b9218b7aSpaul luse 		}
600b9218b7aSpaul luse 	}
6010ef079c6Spaul luse 
602a34fc12bSpaul luse 	/* For engines that don't support batch or for the odd event that
603a34fc12bSpaul luse 	 * a batch ends with only one descriptor left.
604a34fc12bSpaul luse 	 */
605a34fc12bSpaul luse 	for (i = 0; i < remaining; i++) {
606a34fc12bSpaul luse 
607*0cecfcb1Spaul luse 		if (!TAILQ_EMPTY(&worker->tasks)) {
608*0cecfcb1Spaul luse 			task = TAILQ_FIRST(&worker->tasks);
609*0cecfcb1Spaul luse 			TAILQ_REMOVE(&worker->tasks, task, link);
610*0cecfcb1Spaul luse 		} else {
611a34fc12bSpaul luse 			fprintf(stderr, "Unable to get accel_task\n");
612a34fc12bSpaul luse 			goto error;
6130ef079c6Spaul luse 		}
6140ef079c6Spaul luse 
615a34fc12bSpaul luse 		if (_get_task_data_bufs(task)) {
616a34fc12bSpaul luse 			fprintf(stderr, "Unable to get data bufs\n");
617a34fc12bSpaul luse 			goto error;
618b9218b7aSpaul luse 		}
619b9218b7aSpaul luse 
6209f51cf32Spaul luse 		_submit_single(worker, task);
6219f51cf32Spaul luse 	}
622a34fc12bSpaul luse 	return;
623a34fc12bSpaul luse error:
624a34fc12bSpaul luse 	/* TODO clean exit */
625a34fc12bSpaul luse 	raise(SIGINT);
626*0cecfcb1Spaul luse 	while (!TAILQ_EMPTY(&worker->tasks)) {
627*0cecfcb1Spaul luse 		task = TAILQ_FIRST(&worker->tasks);
628*0cecfcb1Spaul luse 		TAILQ_REMOVE(&worker->tasks, task, link);
629*0cecfcb1Spaul luse 		free(task);
630*0cecfcb1Spaul luse 	}
631a34fc12bSpaul luse 	free(worker);
632a34fc12bSpaul luse 	spdk_app_stop(-1);
6339f51cf32Spaul luse }
6349f51cf32Spaul luse 
6359f51cf32Spaul luse static void
636e8463f87Spaul luse accel_done(void *cb_arg, int status)
6379f51cf32Spaul luse {
638e8463f87Spaul luse 	struct ap_task *task = (struct ap_task *)cb_arg;
6399f51cf32Spaul luse 	struct worker_thread *worker = task->worker;
6409f51cf32Spaul luse 
6419f51cf32Spaul luse 	assert(worker);
6429f51cf32Spaul luse 
643b9218b7aSpaul luse 	task->status = status;
6449f51cf32Spaul luse 	spdk_thread_send_msg(worker->thread, _accel_done, task);
6459f51cf32Spaul luse }
6469f51cf32Spaul luse 
6479f51cf32Spaul luse static void
6489f51cf32Spaul luse accel_perf_start(void *arg1)
6499f51cf32Spaul luse {
650514be889Spaul luse 	struct spdk_io_channel *accel_ch;
651514be889Spaul luse 
652514be889Spaul luse 	accel_ch = spdk_accel_engine_get_io_channel();
653a34fc12bSpaul luse 	g_capabilites = spdk_accel_get_capabilities(accel_ch);
654514be889Spaul luse 	spdk_put_io_channel(accel_ch);
655514be889Spaul luse 
656a34fc12bSpaul luse 	if ((g_capabilites & g_workload_selection) != g_workload_selection) {
657a7dfca5bSpaul luse 		SPDK_WARNLOG("The selected workload is not natively supported by the current engine\n");
658a7dfca5bSpaul luse 		SPDK_WARNLOG("The software engine will be used instead.\n\n");
659514be889Spaul luse 	}
660514be889Spaul luse 
6619f51cf32Spaul luse 	g_tsc_rate = spdk_get_ticks_hz();
6629f51cf32Spaul luse 	g_tsc_us_rate = g_tsc_rate / (1000 * 1000);
6639f51cf32Spaul luse 	g_tsc_end = spdk_get_ticks() + g_time_in_sec * g_tsc_rate;
6649f51cf32Spaul luse 
6659f51cf32Spaul luse 	printf("Running for %d seconds...\n", g_time_in_sec);
6669f51cf32Spaul luse 	fflush(stdout);
6679f51cf32Spaul luse 
6689f51cf32Spaul luse 	spdk_for_each_thread(_init_thread, NULL, _init_thread_done);
6699f51cf32Spaul luse }
6709f51cf32Spaul luse 
6719f51cf32Spaul luse int
6729f51cf32Spaul luse main(int argc, char **argv)
6739f51cf32Spaul luse {
6749f51cf32Spaul luse 	struct spdk_app_opts opts = {};
6759f51cf32Spaul luse 	struct worker_thread *worker, *tmp;
6769f51cf32Spaul luse 	int rc = 0;
6779f51cf32Spaul luse 
6789f51cf32Spaul luse 	pthread_mutex_init(&g_workers_lock, NULL);
6799f51cf32Spaul luse 	spdk_app_opts_init(&opts);
6809f51cf32Spaul luse 	opts.reactor_mask = "0x1";
68189495464Spaul luse 	if ((rc = spdk_app_parse_args(argc, argv, &opts, "o:q:t:yw:P:f:", NULL, parse_args,
6829f51cf32Spaul luse 				      usage)) != SPDK_APP_PARSE_ARGS_SUCCESS) {
6839f51cf32Spaul luse 		rc = -1;
6849f51cf32Spaul luse 		goto cleanup;
6859f51cf32Spaul luse 	}
6869f51cf32Spaul luse 
687b9218b7aSpaul luse 	if ((g_workload_selection != ACCEL_COPY) &&
688b9218b7aSpaul luse 	    (g_workload_selection != ACCEL_FILL) &&
689b9218b7aSpaul luse 	    (g_workload_selection != ACCEL_CRC32C) &&
6900ef079c6Spaul luse 	    (g_workload_selection != ACCEL_COMPARE) &&
6910ef079c6Spaul luse 	    (g_workload_selection != ACCEL_DUALCAST)) {
6922a0c66d0Spaul luse 		usage();
6932a0c66d0Spaul luse 		rc = -1;
6942a0c66d0Spaul luse 		goto cleanup;
6952a0c66d0Spaul luse 	}
6962a0c66d0Spaul luse 
6979f51cf32Spaul luse 	dump_user_config(&opts);
6989f51cf32Spaul luse 	rc = spdk_app_start(&opts, accel_perf_start, NULL);
6999f51cf32Spaul luse 	if (rc) {
7009f51cf32Spaul luse 		SPDK_ERRLOG("ERROR starting application\n");
7019f51cf32Spaul luse 	} else {
7029f51cf32Spaul luse 		dump_result();
7039f51cf32Spaul luse 	}
7049f51cf32Spaul luse 
7059f51cf32Spaul luse 	pthread_mutex_destroy(&g_workers_lock);
7069f51cf32Spaul luse 
7079f51cf32Spaul luse 	worker = g_workers;
7089f51cf32Spaul luse 	while (worker) {
7099f51cf32Spaul luse 		tmp = worker->next;
7109f51cf32Spaul luse 		free(worker);
7119f51cf32Spaul luse 		worker = tmp;
7129f51cf32Spaul luse 	}
7139f51cf32Spaul luse cleanup:
7149f51cf32Spaul luse 	spdk_app_fini();
7159f51cf32Spaul luse 	return rc;
7169f51cf32Spaul luse }
717