1 /* SPDX-License-Identifier: BSD-3-Clause 2 * Copyright 2020 Mellanox Technologies, Ltd 3 */ 4 5 #include <stdio.h> 6 #include <stdlib.h> 7 #include <string.h> 8 #include <stdint.h> 9 #include <stdbool.h> 10 #include <stdarg.h> 11 #include <ctype.h> 12 #include <errno.h> 13 #include <getopt.h> 14 #include <signal.h> 15 16 #include <rte_eal.h> 17 #include <rte_common.h> 18 #include <rte_malloc.h> 19 #include <rte_mempool.h> 20 #include <rte_mbuf.h> 21 #include <rte_cycles.h> 22 #include <rte_regexdev.h> 23 24 #define MAX_FILE_NAME 255 25 #define MBUF_CACHE_SIZE 256 26 #define MBUF_SIZE (1 << 8) 27 #define START_BURST_SIZE 32u 28 29 enum app_args { 30 ARG_HELP, 31 ARG_RULES_FILE_NAME, 32 ARG_DATA_FILE_NAME, 33 ARG_NUM_OF_JOBS, 34 ARG_PERF_MODE, 35 ARG_NUM_OF_ITERATIONS, 36 ARG_NUM_OF_QPS, 37 ARG_NUM_OF_LCORES, 38 }; 39 40 struct job_ctx { 41 struct rte_mbuf *mbuf; 42 }; 43 44 struct qp_params { 45 uint32_t total_enqueue; 46 uint32_t total_dequeue; 47 uint32_t total_matches; 48 struct rte_regex_ops **ops; 49 struct job_ctx *jobs_ctx; 50 char *buf; 51 uint64_t start; 52 uint64_t cycles; 53 }; 54 55 struct qps_per_lcore { 56 unsigned int lcore_id; 57 int socket; 58 uint16_t qp_id_base; 59 uint16_t nb_qps; 60 }; 61 62 struct regex_conf { 63 uint32_t nb_jobs; 64 bool perf_mode; 65 uint32_t nb_iterations; 66 char *data_file; 67 uint8_t nb_max_matches; 68 uint32_t nb_qps; 69 uint16_t qp_id_base; 70 char *data_buf; 71 long data_len; 72 long job_len; 73 }; 74 75 static void 76 usage(const char *prog_name) 77 { 78 printf("%s [EAL options] --\n" 79 " --rules NAME: precompiled rules file\n" 80 " --data NAME: data file to use\n" 81 " --nb_jobs: number of jobs to use\n" 82 " --perf N: only outputs the performance data\n" 83 " --nb_iter N: number of iteration to run\n" 84 " --nb_qps N: number of queues to use\n" 85 " --nb_lcores N: number of lcores to use\n", 86 prog_name); 87 } 88 89 static void 90 args_parse(int argc, char **argv, char *rules_file, char *data_file, 91 uint32_t *nb_jobs, bool *perf_mode, uint32_t *nb_iterations, 92 uint32_t *nb_qps, uint32_t *nb_lcores) 93 { 94 char **argvopt; 95 int opt; 96 int opt_idx; 97 size_t len; 98 static struct option lgopts[] = { 99 { "help", 0, 0, ARG_HELP}, 100 /* Rules database file to load. */ 101 { "rules", 1, 0, ARG_RULES_FILE_NAME}, 102 /* Data file to load. */ 103 { "data", 1, 0, ARG_DATA_FILE_NAME}, 104 /* Number of jobs to create. */ 105 { "nb_jobs", 1, 0, ARG_NUM_OF_JOBS}, 106 /* Perf test only */ 107 { "perf", 0, 0, ARG_PERF_MODE}, 108 /* Number of iterations to run with perf test */ 109 { "nb_iter", 1, 0, ARG_NUM_OF_ITERATIONS}, 110 /* Number of QPs. */ 111 { "nb_qps", 1, 0, ARG_NUM_OF_QPS}, 112 /* Number of lcores. */ 113 { "nb_lcores", 1, 0, ARG_NUM_OF_LCORES}, 114 /* End of options */ 115 { 0, 0, 0, 0 } 116 }; 117 118 argvopt = argv; 119 while ((opt = getopt_long(argc, argvopt, "", 120 lgopts, &opt_idx)) != EOF) { 121 switch (opt) { 122 case ARG_RULES_FILE_NAME: 123 len = strnlen(optarg, MAX_FILE_NAME - 1); 124 if (len == MAX_FILE_NAME) 125 rte_exit(EXIT_FAILURE, 126 "Rule file name to long max %d\n", 127 MAX_FILE_NAME - 1); 128 strncpy(rules_file, optarg, MAX_FILE_NAME - 1); 129 break; 130 case ARG_DATA_FILE_NAME: 131 len = strnlen(optarg, MAX_FILE_NAME - 1); 132 if (len == MAX_FILE_NAME) 133 rte_exit(EXIT_FAILURE, 134 "Data file name to long max %d\n", 135 MAX_FILE_NAME - 1); 136 strncpy(data_file, optarg, MAX_FILE_NAME - 1); 137 break; 138 case ARG_NUM_OF_JOBS: 139 *nb_jobs = atoi(optarg); 140 break; 141 case ARG_PERF_MODE: 142 *perf_mode = true; 143 break; 144 case ARG_NUM_OF_ITERATIONS: 145 *nb_iterations = atoi(optarg); 146 break; 147 case ARG_NUM_OF_QPS: 148 *nb_qps = atoi(optarg); 149 break; 150 case ARG_NUM_OF_LCORES: 151 *nb_lcores = atoi(optarg); 152 break; 153 case ARG_HELP: 154 usage("RegEx test app"); 155 break; 156 default: 157 fprintf(stderr, "Invalid option: %s\n", argv[optind]); 158 usage("RegEx test app"); 159 rte_exit(EXIT_FAILURE, "Invalid option\n"); 160 break; 161 } 162 } 163 164 if (!perf_mode) 165 *nb_iterations = 1; 166 } 167 168 static long 169 read_file(char *file, char **buf) 170 { 171 FILE *fp; 172 long buf_len = 0; 173 size_t read_len; 174 int res = 0; 175 176 fp = fopen(file, "r"); 177 if (!fp) 178 return -EIO; 179 if (fseek(fp, 0L, SEEK_END) == 0) { 180 buf_len = ftell(fp); 181 if (buf_len == -1) { 182 res = EIO; 183 goto error; 184 } 185 *buf = rte_malloc(NULL, sizeof(char) * (buf_len + 1), 4096); 186 if (!*buf) { 187 res = ENOMEM; 188 goto error; 189 } 190 if (fseek(fp, 0L, SEEK_SET) != 0) { 191 res = EIO; 192 goto error; 193 } 194 read_len = fread(*buf, sizeof(char), buf_len, fp); 195 if (read_len != (unsigned long)buf_len) { 196 res = EIO; 197 goto error; 198 } 199 } 200 fclose(fp); 201 return buf_len; 202 error: 203 printf("Error, can't open file %s\n, err = %d", file, res); 204 if (fp) 205 fclose(fp); 206 if (*buf) 207 rte_free(*buf); 208 return -res; 209 } 210 211 static int 212 clone_buf(char *data_buf, char **buf, long data_len) 213 { 214 char *dest_buf; 215 dest_buf = 216 rte_malloc(NULL, sizeof(char) * (data_len + 1), 4096); 217 if (!dest_buf) 218 return -ENOMEM; 219 memcpy(dest_buf, data_buf, data_len + 1); 220 *buf = dest_buf; 221 return 0; 222 } 223 224 static int 225 init_port(uint16_t *nb_max_payload, char *rules_file, uint8_t *nb_max_matches, 226 uint32_t nb_qps) 227 { 228 uint16_t id; 229 uint16_t qp_id; 230 uint16_t num_devs; 231 char *rules = NULL; 232 long rules_len; 233 struct rte_regexdev_info info; 234 struct rte_regexdev_config dev_conf = { 235 .nb_queue_pairs = nb_qps, 236 .nb_groups = 1, 237 }; 238 struct rte_regexdev_qp_conf qp_conf = { 239 .nb_desc = 1024, 240 .qp_conf_flags = 0, 241 }; 242 int res = 0; 243 244 num_devs = rte_regexdev_count(); 245 if (num_devs == 0) { 246 printf("Error, no devices detected.\n"); 247 return -EINVAL; 248 } 249 250 rules_len = read_file(rules_file, &rules); 251 if (rules_len < 0) { 252 printf("Error, can't read rules files.\n"); 253 res = -EIO; 254 goto error; 255 } 256 257 for (id = 0; id < num_devs; id++) { 258 res = rte_regexdev_info_get(id, &info); 259 if (res != 0) { 260 printf("Error, can't get device info.\n"); 261 goto error; 262 } 263 printf(":: initializing dev: %d\n", id); 264 *nb_max_matches = info.max_matches; 265 *nb_max_payload = info.max_payload_size; 266 if (info.regexdev_capa & RTE_REGEXDEV_SUPP_MATCH_AS_END_F) 267 dev_conf.dev_cfg_flags |= 268 RTE_REGEXDEV_CFG_MATCH_AS_END_F; 269 dev_conf.nb_max_matches = info.max_matches; 270 dev_conf.nb_rules_per_group = info.max_rules_per_group; 271 dev_conf.rule_db_len = rules_len; 272 dev_conf.rule_db = rules; 273 res = rte_regexdev_configure(id, &dev_conf); 274 if (res < 0) { 275 printf("Error, can't configure device %d.\n", id); 276 goto error; 277 } 278 if (info.regexdev_capa & RTE_REGEXDEV_CAPA_QUEUE_PAIR_OOS_F) 279 qp_conf.qp_conf_flags |= 280 RTE_REGEX_QUEUE_PAIR_CFG_OOS_F; 281 for (qp_id = 0; qp_id < nb_qps; qp_id++) { 282 res = rte_regexdev_queue_pair_setup(id, qp_id, 283 &qp_conf); 284 if (res < 0) { 285 printf("Error, can't setup queue pair %u for " 286 "device %d.\n", qp_id, id); 287 goto error; 288 } 289 } 290 printf(":: initializing device: %d done\n", id); 291 } 292 rte_free(rules); 293 return 0; 294 error: 295 if (rules) 296 rte_free(rules); 297 return res; 298 } 299 300 static void 301 extbuf_free_cb(void *addr __rte_unused, void *fcb_opaque __rte_unused) 302 { 303 } 304 305 static int 306 run_regex(void *args) 307 { 308 struct regex_conf *rgxc = args; 309 uint32_t nb_jobs = rgxc->nb_jobs; 310 uint32_t nb_iterations = rgxc->nb_iterations; 311 uint8_t nb_max_matches = rgxc->nb_max_matches; 312 uint32_t nb_qps = rgxc->nb_qps; 313 uint16_t qp_id_base = rgxc->qp_id_base; 314 char *data_buf = rgxc->data_buf; 315 long data_len = rgxc->data_len; 316 long job_len = rgxc->job_len; 317 318 char *buf = NULL; 319 uint32_t actual_jobs = 0; 320 uint32_t i; 321 uint16_t qp_id; 322 uint16_t dev_id = 0; 323 uint8_t nb_matches; 324 struct rte_regexdev_match *match; 325 long pos; 326 unsigned long d_ind = 0; 327 struct rte_mbuf_ext_shared_info shinfo; 328 int res = 0; 329 long double time; 330 struct rte_mempool *mbuf_mp; 331 struct qp_params *qp; 332 struct qp_params *qps = NULL; 333 bool update; 334 uint16_t qps_used = 0; 335 char mbuf_pool[16]; 336 337 shinfo.free_cb = extbuf_free_cb; 338 snprintf(mbuf_pool, 339 sizeof(mbuf_pool), 340 "mbuf_pool_%2u", qp_id_base); 341 mbuf_mp = rte_pktmbuf_pool_create(mbuf_pool, nb_jobs * nb_qps, 0, 342 0, MBUF_SIZE, rte_socket_id()); 343 if (mbuf_mp == NULL) { 344 printf("Error, can't create memory pool\n"); 345 return -ENOMEM; 346 } 347 348 qps = rte_malloc(NULL, sizeof(*qps) * nb_qps, 0); 349 if (!qps) { 350 printf("Error, can't allocate memory for QPs\n"); 351 res = -ENOMEM; 352 goto end; 353 } 354 355 for (qp_id = 0; qp_id < nb_qps; qp_id++) { 356 struct rte_regex_ops **ops; 357 struct job_ctx *jobs_ctx; 358 359 qps_used++; 360 qp = &qps[qp_id]; 361 qp->jobs_ctx = NULL; 362 qp->buf = NULL; 363 qp->ops = ops = rte_malloc(NULL, sizeof(*ops) * nb_jobs, 0); 364 if (!ops) { 365 printf("Error, can't allocate memory for ops.\n"); 366 res = -ENOMEM; 367 goto end; 368 } 369 370 qp->jobs_ctx = jobs_ctx = 371 rte_malloc(NULL, sizeof(*jobs_ctx) * nb_jobs, 0); 372 if (!jobs_ctx) { 373 printf("Error, can't allocate memory for jobs_ctx.\n"); 374 res = -ENOMEM; 375 goto end; 376 } 377 378 /* Allocate the jobs and assign each job with an mbuf. */ 379 for (i = 0; i < nb_jobs; i++) { 380 ops[i] = rte_malloc(NULL, sizeof(*ops[0]) + 381 nb_max_matches * 382 sizeof(struct rte_regexdev_match), 0); 383 if (!ops[i]) { 384 printf("Error, can't allocate " 385 "memory for op.\n"); 386 res = -ENOMEM; 387 goto end; 388 } 389 ops[i]->mbuf = rte_pktmbuf_alloc(mbuf_mp); 390 if (!ops[i]->mbuf) { 391 printf("Error, can't attach mbuf.\n"); 392 res = -ENOMEM; 393 goto end; 394 } 395 } 396 397 if (clone_buf(data_buf, &buf, data_len)) { 398 printf("Error, can't clone buf.\n"); 399 res = -EXIT_FAILURE; 400 goto end; 401 } 402 403 /* Assign each mbuf with the data to handle. */ 404 actual_jobs = 0; 405 pos = 0; 406 for (i = 0; (pos < data_len) && (i < nb_jobs) ; i++) { 407 long act_job_len = RTE_MIN(job_len, data_len - pos); 408 rte_pktmbuf_attach_extbuf(ops[i]->mbuf, &buf[pos], 0, 409 act_job_len, &shinfo); 410 jobs_ctx[i].mbuf = ops[i]->mbuf; 411 ops[i]->mbuf->data_len = job_len; 412 ops[i]->mbuf->pkt_len = act_job_len; 413 ops[i]->user_id = i; 414 ops[i]->group_id0 = 1; 415 pos += act_job_len; 416 actual_jobs++; 417 } 418 419 qp->buf = buf; 420 qp->total_matches = 0; 421 qp->start = 0; 422 qp->cycles = 0; 423 } 424 425 for (i = 0; i < nb_iterations; i++) { 426 for (qp_id = 0; qp_id < nb_qps; qp_id++) { 427 qp = &qps[qp_id]; 428 qp->total_enqueue = 0; 429 qp->total_dequeue = 0; 430 } 431 do { 432 update = false; 433 for (qp_id = 0; qp_id < nb_qps; qp_id++) { 434 qp = &qps[qp_id]; 435 if (qp->total_dequeue < actual_jobs) { 436 qp->start = rte_rdtsc_precise(); 437 struct rte_regex_ops ** 438 cur_ops_to_enqueue = qp->ops + 439 qp->total_enqueue; 440 441 if (actual_jobs - qp->total_enqueue) 442 qp->total_enqueue += 443 rte_regexdev_enqueue_burst 444 (dev_id, 445 qp_id_base + qp_id, 446 cur_ops_to_enqueue, 447 actual_jobs - 448 qp->total_enqueue); 449 } 450 } 451 for (qp_id = 0; qp_id < nb_qps; qp_id++) { 452 qp = &qps[qp_id]; 453 if (qp->total_dequeue < actual_jobs) { 454 struct rte_regex_ops ** 455 cur_ops_to_dequeue = qp->ops + 456 qp->total_dequeue; 457 458 qp->total_dequeue += 459 rte_regexdev_dequeue_burst 460 (dev_id, 461 qp_id_base + qp_id, 462 cur_ops_to_dequeue, 463 qp->total_enqueue - 464 qp->total_dequeue); 465 qp->cycles += 466 (rte_rdtsc_precise() - qp->start); 467 update = true; 468 } 469 } 470 } while (update); 471 } 472 for (qp_id = 0; qp_id < nb_qps; qp_id++) { 473 qp = &qps[qp_id]; 474 time = (long double)qp->cycles / rte_get_timer_hz(); 475 printf("Core=%u QP=%u Job=%ld Bytes Time=%Lf sec Perf=%Lf " 476 "Gbps\n", rte_lcore_id(), qp_id + qp_id_base, 477 job_len, time, 478 (((double)actual_jobs * job_len * nb_iterations * 8) 479 / time) / 1000000000.0); 480 } 481 482 if (rgxc->perf_mode) 483 goto end; 484 for (qp_id = 0; qp_id < nb_qps; qp_id++) { 485 printf("\n############ Core=%u QP=%u ############\n", 486 rte_lcore_id(), qp_id + qp_id_base); 487 qp = &qps[qp_id]; 488 /* Log results per job. */ 489 for (d_ind = 0; d_ind < qp->total_dequeue; d_ind++) { 490 nb_matches = qp->ops[d_ind % actual_jobs]->nb_matches; 491 printf("Job id %"PRIu64" number of matches = %d\n", 492 qp->ops[d_ind]->user_id, nb_matches); 493 qp->total_matches += nb_matches; 494 match = qp->ops[d_ind % actual_jobs]->matches; 495 for (i = 0; i < nb_matches; i++) { 496 printf("match %d, rule = %d, " 497 "start = %d,len = %d\n", 498 i, match->rule_id, match->start_offset, 499 match->len); 500 match++; 501 } 502 } 503 printf("Total matches = %d\n", qp->total_matches); 504 printf("All Matches:\n"); 505 /* Log absolute results. */ 506 for (d_ind = 0; d_ind < qp->total_dequeue; d_ind++) { 507 nb_matches = qp->ops[d_ind % actual_jobs]->nb_matches; 508 qp->total_matches += nb_matches; 509 match = qp->ops[d_ind % actual_jobs]->matches; 510 for (i = 0; i < nb_matches; i++) { 511 printf("start = %ld, len = %d, rule = %d\n", 512 match->start_offset + 513 d_ind * job_len, 514 match->len, match->rule_id); 515 match++; 516 } 517 } 518 } 519 end: 520 for (qp_id = 0; qp_id < qps_used; qp_id++) { 521 qp = &qps[qp_id]; 522 for (i = 0; i < actual_jobs && qp->ops; i++) 523 rte_free(qp->ops[i]); 524 rte_free(qp->ops); 525 qp->ops = NULL; 526 for (i = 0; i < actual_jobs && qp->jobs_ctx; i++) 527 rte_pktmbuf_free(qp->jobs_ctx[i].mbuf); 528 rte_free(qp->jobs_ctx); 529 qp->jobs_ctx = NULL; 530 rte_free(qp->buf); 531 qp->buf = NULL; 532 } 533 if (mbuf_mp) 534 rte_mempool_free(mbuf_mp); 535 rte_free(qps); 536 return res; 537 } 538 539 static int 540 distribute_qps_to_lcores(uint32_t nb_cores, uint32_t nb_qps, 541 struct qps_per_lcore **qpl) 542 { 543 int socket; 544 unsigned lcore_id; 545 uint32_t i; 546 uint16_t min_qp_id; 547 uint16_t max_qp_id; 548 struct qps_per_lcore *qps_per_lcore; 549 uint32_t detected_lcores; 550 551 if (nb_qps < nb_cores) { 552 nb_cores = nb_qps; 553 printf("Reducing number of cores to number of QPs (%u)\n", 554 nb_cores); 555 } 556 /* Allocate qps_per_lcore array */ 557 qps_per_lcore = 558 rte_malloc(NULL, sizeof(*qps_per_lcore) * nb_cores, 0); 559 if (!qps_per_lcore) 560 rte_exit(EXIT_FAILURE, "Failed to create qps_per_lcore array\n"); 561 *qpl = qps_per_lcore; 562 detected_lcores = 0; 563 min_qp_id = 0; 564 565 RTE_LCORE_FOREACH_WORKER(lcore_id) { 566 if (detected_lcores >= nb_cores) 567 break; 568 qps_per_lcore[detected_lcores].lcore_id = lcore_id; 569 socket = rte_lcore_to_socket_id(lcore_id); 570 if (socket == SOCKET_ID_ANY) 571 socket = 0; 572 qps_per_lcore[detected_lcores].socket = socket; 573 qps_per_lcore[detected_lcores].qp_id_base = min_qp_id; 574 max_qp_id = min_qp_id + nb_qps / nb_cores - 1; 575 if (nb_qps % nb_cores > detected_lcores) 576 max_qp_id++; 577 qps_per_lcore[detected_lcores].nb_qps = max_qp_id - 578 min_qp_id + 1; 579 min_qp_id = max_qp_id + 1; 580 detected_lcores++; 581 } 582 if (detected_lcores != nb_cores) 583 return -1; 584 585 for (i = 0; i < detected_lcores; i++) { 586 printf("===> Core %d: allocated queues: ", 587 qps_per_lcore[i].lcore_id); 588 min_qp_id = qps_per_lcore[i].qp_id_base; 589 max_qp_id = 590 qps_per_lcore[i].qp_id_base + qps_per_lcore[i].nb_qps; 591 while (min_qp_id < max_qp_id) { 592 printf("%u ", min_qp_id); 593 min_qp_id++; 594 } 595 printf("\n"); 596 } 597 return 0; 598 } 599 600 int 601 main(int argc, char **argv) 602 { 603 char rules_file[MAX_FILE_NAME]; 604 char data_file[MAX_FILE_NAME]; 605 uint32_t nb_jobs = 0; 606 bool perf_mode = 0; 607 uint32_t nb_iterations = 0; 608 int ret; 609 uint16_t nb_max_payload = 0; 610 uint8_t nb_max_matches = 0; 611 uint32_t nb_qps = 1; 612 char *data_buf; 613 long data_len; 614 long job_len; 615 uint32_t nb_lcores = 1; 616 struct regex_conf *rgxc; 617 uint32_t i; 618 struct qps_per_lcore *qps_per_lcore; 619 620 /* Init EAL. */ 621 ret = rte_eal_init(argc, argv); 622 if (ret < 0) 623 rte_exit(EXIT_FAILURE, "EAL init failed\n"); 624 argc -= ret; 625 argv += ret; 626 if (argc > 1) 627 args_parse(argc, argv, rules_file, data_file, &nb_jobs, 628 &perf_mode, &nb_iterations, &nb_qps, 629 &nb_lcores); 630 631 if (nb_qps == 0) 632 rte_exit(EXIT_FAILURE, "Number of QPs must be greater than 0\n"); 633 if (nb_lcores == 0) 634 rte_exit(EXIT_FAILURE, "Number of lcores must be greater than 0\n"); 635 if (distribute_qps_to_lcores(nb_lcores, nb_qps, &qps_per_lcore) < 0) 636 rte_exit(EXIT_FAILURE, "Failed to distribute queues to lcores!\n"); 637 ret = init_port(&nb_max_payload, rules_file, 638 &nb_max_matches, nb_qps); 639 if (ret < 0) 640 rte_exit(EXIT_FAILURE, "init port failed\n"); 641 642 data_len = read_file(data_file, &data_buf); 643 if (data_len <= 0) 644 rte_exit(EXIT_FAILURE, "Error, can't read file, or file is empty.\n"); 645 646 job_len = data_len / nb_jobs; 647 if (job_len == 0) 648 rte_exit(EXIT_FAILURE, "Error, To many jobs, for the given input.\n"); 649 650 if (job_len > nb_max_payload) 651 rte_exit(EXIT_FAILURE, "Error, not enough jobs to cover input.\n"); 652 653 rgxc = rte_malloc(NULL, sizeof(*rgxc) * nb_lcores, 0); 654 if (!rgxc) 655 rte_exit(EXIT_FAILURE, "Failed to create Regex Conf\n"); 656 for (i = 0; i < nb_lcores; i++) { 657 rgxc[i] = (struct regex_conf){ 658 .nb_jobs = nb_jobs, 659 .perf_mode = perf_mode, 660 .nb_iterations = nb_iterations, 661 .nb_max_matches = nb_max_matches, 662 .nb_qps = qps_per_lcore[i].nb_qps, 663 .qp_id_base = qps_per_lcore[i].qp_id_base, 664 .data_buf = data_buf, 665 .data_len = data_len, 666 .job_len = job_len, 667 }; 668 rte_eal_remote_launch(run_regex, &rgxc[i], 669 qps_per_lcore[i].lcore_id); 670 } 671 rte_eal_mp_wait_lcore(); 672 rte_free(data_buf); 673 rte_free(rgxc); 674 rte_free(qps_per_lcore); 675 return EXIT_SUCCESS; 676 } 677