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