xref: /dpdk/app/test-dma-perf/main.c (revision 3da59f30a23f2e795d2315f3d949e1b3e0ce0c3d)
1 /* SPDX-License-Identifier: BSD-3-Clause
2  * Copyright(c) 2023 Intel Corporation
3  */
4 
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <getopt.h>
8 #include <signal.h>
9 #include <stdbool.h>
10 #include <unistd.h>
11 #include <sys/wait.h>
12 #include <inttypes.h>
13 #include <libgen.h>
14 
15 #include <rte_eal.h>
16 #include <rte_cfgfile.h>
17 #include <rte_string_fns.h>
18 #include <rte_lcore.h>
19 
20 #include "main.h"
21 
22 #define CSV_HDR_FMT "Case %u : %s,lcore,DMA,DMA ring size,kick batch size,buffer size(B),number of buffers,memory(MB),average cycle,bandwidth(Gbps),MOps\n"
23 
24 #define MAX_EAL_PARAM_NB 100
25 #define MAX_EAL_PARAM_LEN 1024
26 
27 #define DMA_MEM_COPY "DMA_MEM_COPY"
28 #define CPU_MEM_COPY "CPU_MEM_COPY"
29 
30 #define CMDLINE_CONFIG_ARG "--config"
31 #define CMDLINE_RESULT_ARG "--result"
32 
33 #define MAX_PARAMS_PER_ENTRY 4
34 
35 #define MAX_LONG_OPT_SZ 64
36 
37 enum {
38 	TEST_TYPE_NONE = 0,
39 	TEST_TYPE_DMA_MEM_COPY,
40 	TEST_TYPE_CPU_MEM_COPY
41 };
42 
43 #define MAX_TEST_CASES 16
44 static struct test_configure test_cases[MAX_TEST_CASES];
45 
46 char output_str[MAX_WORKER_NB + 1][MAX_OUTPUT_STR_LEN];
47 
48 static FILE *fd;
49 
50 static void
51 output_csv(bool need_blankline)
52 {
53 	uint32_t i;
54 
55 	if (need_blankline) {
56 		fprintf(fd, ",,,,,,,,\n");
57 		fprintf(fd, ",,,,,,,,\n");
58 	}
59 
60 	for (i = 0; i < RTE_DIM(output_str); i++) {
61 		if (output_str[i][0]) {
62 			fprintf(fd, "%s", output_str[i]);
63 			output_str[i][0] = '\0';
64 		}
65 	}
66 
67 	fflush(fd);
68 }
69 
70 static void
71 output_env_info(void)
72 {
73 	snprintf(output_str[0], MAX_OUTPUT_STR_LEN, "Test Environment:\n");
74 	snprintf(output_str[1], MAX_OUTPUT_STR_LEN, "CPU frequency,%.3lf Ghz",
75 			rte_get_timer_hz() / 1000000000.0);
76 
77 	output_csv(true);
78 }
79 
80 static void
81 output_header(uint32_t case_id, struct test_configure *case_cfg)
82 {
83 	snprintf(output_str[0], MAX_OUTPUT_STR_LEN,
84 			CSV_HDR_FMT, case_id, case_cfg->test_type_str);
85 
86 	output_csv(true);
87 }
88 
89 static void
90 run_test_case(struct test_configure *case_cfg)
91 {
92 	switch (case_cfg->test_type) {
93 	case TEST_TYPE_DMA_MEM_COPY:
94 		mem_copy_benchmark(case_cfg, true);
95 		break;
96 	case TEST_TYPE_CPU_MEM_COPY:
97 		mem_copy_benchmark(case_cfg, false);
98 		break;
99 	default:
100 		printf("Unknown test type. %s\n", case_cfg->test_type_str);
101 		break;
102 	}
103 }
104 
105 static void
106 run_test(uint32_t case_id, struct test_configure *case_cfg)
107 {
108 	uint32_t i;
109 	uint32_t nb_lcores = rte_lcore_count();
110 	struct test_configure_entry *mem_size = &case_cfg->mem_size;
111 	struct test_configure_entry *buf_size = &case_cfg->buf_size;
112 	struct test_configure_entry *ring_size = &case_cfg->ring_size;
113 	struct test_configure_entry *kick_batch = &case_cfg->kick_batch;
114 	struct test_configure_entry dummy = { 0 };
115 	struct test_configure_entry *var_entry = &dummy;
116 
117 	for (i = 0; i < RTE_DIM(output_str); i++)
118 		memset(output_str[i], 0, MAX_OUTPUT_STR_LEN);
119 
120 	if (nb_lcores <= case_cfg->lcore_dma_map.cnt) {
121 		printf("Case %u: Not enough lcores.\n", case_id);
122 		return;
123 	}
124 
125 	printf("Number of used lcores: %u.\n", nb_lcores);
126 
127 	if (mem_size->incr != 0)
128 		var_entry = mem_size;
129 
130 	if (buf_size->incr != 0)
131 		var_entry = buf_size;
132 
133 	if (ring_size->incr != 0)
134 		var_entry = ring_size;
135 
136 	if (kick_batch->incr != 0)
137 		var_entry = kick_batch;
138 
139 	case_cfg->scenario_id = 0;
140 
141 	output_header(case_id, case_cfg);
142 
143 	for (var_entry->cur = var_entry->first; var_entry->cur <= var_entry->last;) {
144 		case_cfg->scenario_id++;
145 		printf("\nRunning scenario %d\n", case_cfg->scenario_id);
146 
147 		run_test_case(case_cfg);
148 		output_csv(false);
149 
150 		if (var_entry->op == OP_ADD)
151 			var_entry->cur += var_entry->incr;
152 		else if (var_entry->op == OP_MUL)
153 			var_entry->cur *= var_entry->incr;
154 		else {
155 			printf("No proper operation for variable entry.\n");
156 			break;
157 		}
158 	}
159 }
160 
161 static int
162 parse_lcore(struct test_configure *test_case, const char *value)
163 {
164 	uint16_t len;
165 	char *input;
166 	struct lcore_dma_map_t *lcore_dma_map;
167 
168 	if (test_case == NULL || value == NULL)
169 		return -1;
170 
171 	len = strlen(value);
172 	input = (char *)malloc((len + 1) * sizeof(char));
173 	strlcpy(input, value, len + 1);
174 	lcore_dma_map = &(test_case->lcore_dma_map);
175 
176 	memset(lcore_dma_map, 0, sizeof(struct lcore_dma_map_t));
177 
178 	char *token = strtok(input, ", ");
179 	while (token != NULL) {
180 		if (lcore_dma_map->cnt >= MAX_WORKER_NB) {
181 			free(input);
182 			return -1;
183 		}
184 
185 		uint16_t lcore_id = atoi(token);
186 		lcore_dma_map->lcores[lcore_dma_map->cnt++] = lcore_id;
187 
188 		token = strtok(NULL, ", ");
189 	}
190 
191 	free(input);
192 	return 0;
193 }
194 
195 static int
196 parse_lcore_dma(struct test_configure *test_case, const char *value)
197 {
198 	struct lcore_dma_map_t *lcore_dma_map;
199 	char *input, *addrs;
200 	char *ptrs[2];
201 	char *start, *end, *substr;
202 	uint16_t lcore_id;
203 	int ret = 0;
204 
205 	if (test_case == NULL || value == NULL)
206 		return -1;
207 
208 	input = strndup(value, strlen(value) + 1);
209 	if (input == NULL)
210 		return -1;
211 	addrs = input;
212 
213 	while (*addrs == '\0')
214 		addrs++;
215 	if (*addrs == '\0') {
216 		fprintf(stderr, "No input DMA addresses\n");
217 		ret = -1;
218 		goto out;
219 	}
220 
221 	substr = strtok(addrs, ",");
222 	if (substr == NULL) {
223 		fprintf(stderr, "No input DMA address\n");
224 		ret = -1;
225 		goto out;
226 	}
227 
228 	memset(&test_case->lcore_dma_map, 0, sizeof(struct lcore_dma_map_t));
229 
230 	do {
231 		if (rte_strsplit(substr, strlen(substr), ptrs, 2, '@') < 0) {
232 			fprintf(stderr, "Illegal DMA address\n");
233 			ret = -1;
234 			break;
235 		}
236 
237 		start = strstr(ptrs[0], "lcore");
238 		if (start == NULL) {
239 			fprintf(stderr, "Illegal lcore\n");
240 			ret = -1;
241 			break;
242 		}
243 
244 		start += 5;
245 		lcore_id = strtol(start, &end, 0);
246 		if (end == start) {
247 			fprintf(stderr, "No input lcore ID or ID %d is wrong\n", lcore_id);
248 			ret = -1;
249 			break;
250 		}
251 
252 		lcore_dma_map = &test_case->lcore_dma_map;
253 		if (lcore_dma_map->cnt >= MAX_WORKER_NB) {
254 			fprintf(stderr, "lcores count error\n");
255 			ret = -1;
256 			break;
257 		}
258 
259 		lcore_dma_map->lcores[lcore_dma_map->cnt] = lcore_id;
260 		strlcpy(lcore_dma_map->dma_names[lcore_dma_map->cnt], ptrs[1],
261 				RTE_DEV_NAME_MAX_LEN);
262 		lcore_dma_map->cnt++;
263 		substr = strtok(NULL, ",");
264 	} while (substr != NULL);
265 
266 out:
267 	free(input);
268 	return ret;
269 }
270 
271 static int
272 parse_entry(const char *value, struct test_configure_entry *entry)
273 {
274 	char input[255] = {0};
275 	char *args[MAX_PARAMS_PER_ENTRY];
276 	int args_nr = -1;
277 	int ret;
278 
279 	if (value == NULL || entry == NULL)
280 		goto out;
281 
282 	strncpy(input, value, 254);
283 	if (*input == '\0')
284 		goto out;
285 
286 	ret = rte_strsplit(input, strlen(input), args, MAX_PARAMS_PER_ENTRY, ',');
287 	if (ret != 1 && ret != 4)
288 		goto out;
289 
290 	entry->cur = entry->first = (uint32_t)atoi(args[0]);
291 
292 	if (ret == 4) {
293 		args_nr = 4;
294 		entry->last = (uint32_t)atoi(args[1]);
295 		entry->incr = (uint32_t)atoi(args[2]);
296 		if (!strcmp(args[3], "MUL"))
297 			entry->op = OP_MUL;
298 		else if (!strcmp(args[3], "ADD"))
299 			entry->op = OP_ADD;
300 		else {
301 			args_nr = -1;
302 			printf("Invalid op %s.\n", args[3]);
303 		}
304 
305 	} else {
306 		args_nr = 1;
307 		entry->op = OP_NONE;
308 		entry->last = 0;
309 		entry->incr = 0;
310 	}
311 out:
312 	return args_nr;
313 }
314 
315 static uint16_t
316 load_configs(const char *path)
317 {
318 	struct rte_cfgfile *cfgfile;
319 	int nb_sections, i;
320 	struct test_configure *test_case;
321 	char section_name[CFG_NAME_LEN];
322 	const char *case_type;
323 	const char *lcore_dma;
324 	const char *mem_size_str, *buf_size_str, *ring_size_str, *kick_batch_str;
325 	int args_nr, nb_vp;
326 	bool is_dma;
327 
328 	printf("config file parsing...\n");
329 	cfgfile = rte_cfgfile_load(path, 0);
330 	if (!cfgfile) {
331 		printf("Open configure file error.\n");
332 		exit(1);
333 	}
334 
335 	nb_sections = rte_cfgfile_num_sections(cfgfile, NULL, 0);
336 	if (nb_sections > MAX_TEST_CASES) {
337 		printf("Error: The maximum number of cases is %d.\n", MAX_TEST_CASES);
338 		exit(1);
339 	}
340 
341 	for (i = 0; i < nb_sections; i++) {
342 		snprintf(section_name, CFG_NAME_LEN, "case%d", i + 1);
343 		test_case = &test_cases[i];
344 		case_type = rte_cfgfile_get_entry(cfgfile, section_name, "type");
345 		if (case_type == NULL) {
346 			printf("Error: No case type in case %d, the test will be finished here.\n",
347 				i + 1);
348 			test_case->is_valid = false;
349 			continue;
350 		}
351 
352 		if (strcmp(case_type, DMA_MEM_COPY) == 0) {
353 			test_case->test_type = TEST_TYPE_DMA_MEM_COPY;
354 			test_case->test_type_str = DMA_MEM_COPY;
355 			is_dma = true;
356 		} else if (strcmp(case_type, CPU_MEM_COPY) == 0) {
357 			test_case->test_type = TEST_TYPE_CPU_MEM_COPY;
358 			test_case->test_type_str = CPU_MEM_COPY;
359 			is_dma = false;
360 		} else {
361 			printf("Error: Wrong test case type %s in case%d.\n", case_type, i + 1);
362 			test_case->is_valid = false;
363 			continue;
364 		}
365 
366 		test_case->src_numa_node = (int)atoi(rte_cfgfile_get_entry(cfgfile,
367 								section_name, "src_numa_node"));
368 		test_case->dst_numa_node = (int)atoi(rte_cfgfile_get_entry(cfgfile,
369 								section_name, "dst_numa_node"));
370 		nb_vp = 0;
371 		mem_size_str = rte_cfgfile_get_entry(cfgfile, section_name, "mem_size");
372 		args_nr = parse_entry(mem_size_str, &test_case->mem_size);
373 		if (args_nr < 0) {
374 			printf("parse error in case %d.\n", i + 1);
375 			test_case->is_valid = false;
376 			continue;
377 		} else if (args_nr == 4)
378 			nb_vp++;
379 
380 		buf_size_str = rte_cfgfile_get_entry(cfgfile, section_name, "buf_size");
381 		args_nr = parse_entry(buf_size_str, &test_case->buf_size);
382 		if (args_nr < 0) {
383 			printf("parse error in case %d.\n", i + 1);
384 			test_case->is_valid = false;
385 			continue;
386 		} else if (args_nr == 4)
387 			nb_vp++;
388 
389 		if (is_dma) {
390 			ring_size_str = rte_cfgfile_get_entry(cfgfile, section_name,
391 								"dma_ring_size");
392 			args_nr = parse_entry(ring_size_str, &test_case->ring_size);
393 			if (args_nr < 0) {
394 				printf("parse error in case %d.\n", i + 1);
395 				test_case->is_valid = false;
396 				continue;
397 			} else if (args_nr == 4)
398 				nb_vp++;
399 
400 			kick_batch_str = rte_cfgfile_get_entry(cfgfile, section_name, "kick_batch");
401 			args_nr = parse_entry(kick_batch_str, &test_case->kick_batch);
402 			if (args_nr < 0) {
403 				printf("parse error in case %d.\n", i + 1);
404 				test_case->is_valid = false;
405 				continue;
406 			} else if (args_nr == 4)
407 				nb_vp++;
408 
409 			lcore_dma = rte_cfgfile_get_entry(cfgfile, section_name, "lcore_dma");
410 			int lcore_ret = parse_lcore_dma(test_case, lcore_dma);
411 			if (lcore_ret < 0) {
412 				printf("parse lcore dma error in case %d.\n", i + 1);
413 				test_case->is_valid = false;
414 				continue;
415 			}
416 		} else {
417 			lcore_dma = rte_cfgfile_get_entry(cfgfile, section_name, "lcore");
418 			int lcore_ret = parse_lcore(test_case, lcore_dma);
419 			if (lcore_ret < 0) {
420 				printf("parse lcore error in case %d.\n", i + 1);
421 				test_case->is_valid = false;
422 				continue;
423 			}
424 		}
425 
426 		if (nb_vp > 1) {
427 			printf("Case %d error, each section can only have a single variable parameter.\n",
428 					i + 1);
429 			test_case->is_valid = false;
430 			continue;
431 		}
432 
433 		test_case->cache_flush =
434 			(uint8_t)atoi(rte_cfgfile_get_entry(cfgfile, section_name, "cache_flush"));
435 		test_case->test_secs = (uint16_t)atoi(rte_cfgfile_get_entry(cfgfile,
436 					section_name, "test_seconds"));
437 
438 		test_case->eal_args = rte_cfgfile_get_entry(cfgfile, section_name, "eal_args");
439 		test_case->is_valid = true;
440 	}
441 
442 	rte_cfgfile_close(cfgfile);
443 	printf("config file parsing complete.\n\n");
444 	return i;
445 }
446 
447 /* Parse the argument given in the command line of the application */
448 static int
449 append_eal_args(int argc, char **argv, const char *eal_args, char **new_argv)
450 {
451 	int i;
452 	char *tokens[MAX_EAL_PARAM_NB];
453 	char args[MAX_EAL_PARAM_LEN] = {0};
454 	int token_nb, new_argc = 0;
455 
456 	for (i = 0; i < argc; i++) {
457 		if ((strcmp(argv[i], CMDLINE_CONFIG_ARG) == 0) ||
458 				(strcmp(argv[i], CMDLINE_RESULT_ARG) == 0)) {
459 			i++;
460 			continue;
461 		}
462 		strlcpy(new_argv[new_argc], argv[i], MAX_EAL_PARAM_LEN);
463 		new_argc++;
464 	}
465 
466 	if (eal_args) {
467 		strlcpy(args, eal_args, MAX_EAL_PARAM_LEN);
468 		token_nb = rte_strsplit(args, strlen(args),
469 					tokens, MAX_EAL_PARAM_NB, ' ');
470 		for (i = 0; i < token_nb; i++)
471 			strlcpy(new_argv[new_argc++], tokens[i], MAX_EAL_PARAM_LEN);
472 	}
473 
474 	return new_argc;
475 }
476 
477 int
478 main(int argc, char *argv[])
479 {
480 	int ret;
481 	uint16_t case_nb;
482 	uint32_t i, nb_lcores;
483 	pid_t cpid, wpid;
484 	int wstatus;
485 	char args[MAX_EAL_PARAM_NB][MAX_EAL_PARAM_LEN];
486 	char *pargs[MAX_EAL_PARAM_NB];
487 	char *cfg_path_ptr = NULL;
488 	char *rst_path_ptr = NULL;
489 	char rst_path[PATH_MAX];
490 	int new_argc;
491 
492 	memset(args, 0, sizeof(args));
493 
494 	for (i = 0; i < RTE_DIM(pargs); i++)
495 		pargs[i] = args[i];
496 
497 	for (i = 0; i < (uint32_t)argc; i++) {
498 		if (strncmp(argv[i], CMDLINE_CONFIG_ARG, MAX_LONG_OPT_SZ) == 0)
499 			cfg_path_ptr = argv[i + 1];
500 		if (strncmp(argv[i], CMDLINE_RESULT_ARG, MAX_LONG_OPT_SZ) == 0)
501 			rst_path_ptr = argv[i + 1];
502 	}
503 	if (cfg_path_ptr == NULL) {
504 		printf("Config file not assigned.\n");
505 		return -1;
506 	}
507 	if (rst_path_ptr == NULL) {
508 		strlcpy(rst_path, cfg_path_ptr, PATH_MAX);
509 		char *token = strtok(basename(rst_path), ".");
510 		if (token == NULL) {
511 			printf("Config file error.\n");
512 			return -1;
513 		}
514 		strcat(token, "_result.csv");
515 		rst_path_ptr = rst_path;
516 	}
517 
518 	case_nb = load_configs(cfg_path_ptr);
519 	fd = fopen(rst_path_ptr, "w");
520 	if (fd == NULL) {
521 		printf("Open output CSV file error.\n");
522 		return -1;
523 	}
524 	fclose(fd);
525 
526 	printf("Running cases...\n");
527 	for (i = 0; i < case_nb; i++) {
528 		if (!test_cases[i].is_valid) {
529 			printf("Invalid test case %d.\n\n", i + 1);
530 			snprintf(output_str[0], MAX_OUTPUT_STR_LEN, "Invalid case %d\n", i + 1);
531 
532 			fd = fopen(rst_path_ptr, "a");
533 			if (!fd) {
534 				printf("Open output CSV file error.\n");
535 				return 0;
536 			}
537 			output_csv(true);
538 			fclose(fd);
539 			continue;
540 		}
541 
542 		if (test_cases[i].test_type == TEST_TYPE_NONE) {
543 			printf("No valid test type in test case %d.\n\n", i + 1);
544 			snprintf(output_str[0], MAX_OUTPUT_STR_LEN, "Invalid case %d\n", i + 1);
545 
546 			fd = fopen(rst_path_ptr, "a");
547 			if (!fd) {
548 				printf("Open output CSV file error.\n");
549 				return 0;
550 			}
551 			output_csv(true);
552 			fclose(fd);
553 			continue;
554 		}
555 
556 		cpid = fork();
557 		if (cpid < 0) {
558 			printf("Fork case %d failed.\n", i + 1);
559 			exit(EXIT_FAILURE);
560 		} else if (cpid == 0) {
561 			printf("\nRunning case %u\n\n", i + 1);
562 
563 			new_argc = append_eal_args(argc, argv, test_cases[i].eal_args, pargs);
564 			ret = rte_eal_init(new_argc, pargs);
565 			if (ret < 0)
566 				rte_exit(EXIT_FAILURE, "Invalid EAL arguments\n");
567 
568 			/* Check lcores. */
569 			nb_lcores = rte_lcore_count();
570 			if (nb_lcores < 2)
571 				rte_exit(EXIT_FAILURE,
572 					"There should be at least 2 worker lcores.\n");
573 
574 			fd = fopen(rst_path_ptr, "a");
575 			if (!fd) {
576 				printf("Open output CSV file error.\n");
577 				return 0;
578 			}
579 
580 			output_env_info();
581 
582 			run_test(i + 1, &test_cases[i]);
583 
584 			/* clean up the EAL */
585 			rte_eal_cleanup();
586 
587 			fclose(fd);
588 
589 			printf("\nCase %u completed.\n\n", i + 1);
590 
591 			exit(EXIT_SUCCESS);
592 		} else {
593 			wpid = waitpid(cpid, &wstatus, 0);
594 			if (wpid == -1) {
595 				printf("waitpid error.\n");
596 				exit(EXIT_FAILURE);
597 			}
598 
599 			if (WIFEXITED(wstatus))
600 				printf("Case process exited. status %d\n\n",
601 					WEXITSTATUS(wstatus));
602 			else if (WIFSIGNALED(wstatus))
603 				printf("Case process killed by signal %d\n\n",
604 					WTERMSIG(wstatus));
605 			else if (WIFSTOPPED(wstatus))
606 				printf("Case process stopped by signal %d\n\n",
607 					WSTOPSIG(wstatus));
608 			else if (WIFCONTINUED(wstatus))
609 				printf("Case process continued.\n\n");
610 			else
611 				printf("Case process unknown terminated.\n\n");
612 		}
613 	}
614 
615 	printf("Bye...\n");
616 	return 0;
617 }
618