1 /* SPDX-License-Identifier: BSD-3-Clause 2 * Copyright 2020 Mellanox Technologies, Ltd 3 * 4 * This file contain the application main file 5 * This application provides the user the ability to test the 6 * insertion rate for specific rte_flow rule under stress state ~4M rule/ 7 * 8 * Then it will also provide packet per second measurement after installing 9 * all rules, the user may send traffic to test the PPS that match the rules 10 * after all rules are installed, to check performance or functionality after 11 * the stress. 12 * 13 * The flows insertion will go for all ports first, then it will print the 14 * results, after that the application will go into forwarding packets mode 15 * it will start receiving traffic if any and then forwarding it back and 16 * gives packet per second measurement. 17 */ 18 19 #include <stdio.h> 20 #include <stdlib.h> 21 #include <string.h> 22 #include <stdint.h> 23 #include <inttypes.h> 24 #include <stdarg.h> 25 #include <errno.h> 26 #include <getopt.h> 27 #include <stdbool.h> 28 #include <sys/time.h> 29 #include <signal.h> 30 #include <unistd.h> 31 32 #include <rte_malloc.h> 33 #include <rte_mempool.h> 34 #include <rte_mbuf.h> 35 #include <rte_ethdev.h> 36 #include <rte_flow.h> 37 #include <rte_mtr.h> 38 39 #include "config.h" 40 #include "flow_gen.h" 41 42 #define MAX_BATCHES_COUNT 100 43 #define DEFAULT_RULES_COUNT 4000000 44 #define DEFAULT_RULES_BATCH 100000 45 #define DEFAULT_GROUP 0 46 47 struct rte_flow *flow; 48 static uint8_t flow_group; 49 50 static uint64_t encap_data; 51 static uint64_t decap_data; 52 53 static uint64_t flow_items[MAX_ITEMS_NUM]; 54 static uint64_t flow_actions[MAX_ACTIONS_NUM]; 55 static uint64_t flow_attrs[MAX_ATTRS_NUM]; 56 static uint8_t items_idx, actions_idx, attrs_idx; 57 58 static uint64_t ports_mask; 59 static volatile bool force_quit; 60 static bool dump_iterations; 61 static bool delete_flag; 62 static bool dump_socket_mem_flag; 63 static bool enable_fwd; 64 65 static struct rte_mempool *mbuf_mp; 66 static uint32_t nb_lcores; 67 static uint32_t rules_count; 68 static uint32_t rules_batch; 69 static uint32_t hairpin_queues_num; /* total hairpin q number - default: 0 */ 70 static uint32_t nb_lcores; 71 72 #define MAX_PKT_BURST 32 73 #define LCORE_MODE_PKT 1 74 #define LCORE_MODE_STATS 2 75 #define MAX_STREAMS 64 76 #define METER_CREATE 1 77 #define METER_DELETE 2 78 79 struct stream { 80 int tx_port; 81 int tx_queue; 82 int rx_port; 83 int rx_queue; 84 }; 85 86 struct lcore_info { 87 int mode; 88 int streams_nb; 89 struct stream streams[MAX_STREAMS]; 90 /* stats */ 91 uint64_t tx_pkts; 92 uint64_t tx_drops; 93 uint64_t rx_pkts; 94 struct rte_mbuf *pkts[MAX_PKT_BURST]; 95 } __rte_cache_aligned; 96 97 static struct lcore_info lcore_infos[RTE_MAX_LCORE]; 98 99 struct used_cpu_time { 100 double insertion[MAX_PORTS][RTE_MAX_LCORE]; 101 double deletion[MAX_PORTS][RTE_MAX_LCORE]; 102 }; 103 104 struct multi_cores_pool { 105 uint32_t cores_count; 106 uint32_t rules_count; 107 struct used_cpu_time create_meter; 108 struct used_cpu_time create_flow; 109 int64_t last_alloc[RTE_MAX_LCORE]; 110 int64_t current_alloc[RTE_MAX_LCORE]; 111 } __rte_cache_aligned; 112 113 static struct multi_cores_pool mc_pool = { 114 .cores_count = 1, 115 }; 116 117 static void 118 usage(char *progname) 119 { 120 printf("\nusage: %s\n", progname); 121 printf("\nControl configurations:\n"); 122 printf(" --rules-count=N: to set the number of needed" 123 " rules to insert, default is %d\n", DEFAULT_RULES_COUNT); 124 printf(" --rules-batch=N: set number of batched rules," 125 " default is %d\n", DEFAULT_RULES_BATCH); 126 printf(" --dump-iterations: To print rates for each" 127 " iteration\n"); 128 printf(" --deletion-rate: Enable deletion rate" 129 " calculations\n"); 130 printf(" --dump-socket-mem: To dump all socket memory\n"); 131 printf(" --enable-fwd: To enable packets forwarding" 132 " after insertion\n"); 133 printf(" --portmask=N: hexadecimal bitmask of ports used\n"); 134 135 printf("To set flow attributes:\n"); 136 printf(" --ingress: set ingress attribute in flows\n"); 137 printf(" --egress: set egress attribute in flows\n"); 138 printf(" --transfer: set transfer attribute in flows\n"); 139 printf(" --group=N: set group for all flows," 140 " default is %d\n", DEFAULT_GROUP); 141 printf(" --cores=N: to set the number of needed " 142 "cores to insert rte_flow rules, default is 1\n"); 143 144 printf("To set flow items:\n"); 145 printf(" --ether: add ether layer in flow items\n"); 146 printf(" --vlan: add vlan layer in flow items\n"); 147 printf(" --ipv4: add ipv4 layer in flow items\n"); 148 printf(" --ipv6: add ipv6 layer in flow items\n"); 149 printf(" --tcp: add tcp layer in flow items\n"); 150 printf(" --udp: add udp layer in flow items\n"); 151 printf(" --vxlan: add vxlan layer in flow items\n"); 152 printf(" --vxlan-gpe: add vxlan-gpe layer in flow items\n"); 153 printf(" --gre: add gre layer in flow items\n"); 154 printf(" --geneve: add geneve layer in flow items\n"); 155 printf(" --gtp: add gtp layer in flow items\n"); 156 printf(" --meta: add meta layer in flow items\n"); 157 printf(" --tag: add tag layer in flow items\n"); 158 printf(" --icmpv4: add icmpv4 layer in flow items\n"); 159 printf(" --icmpv6: add icmpv6 layer in flow items\n"); 160 161 printf("To set flow actions:\n"); 162 printf(" --port-id: add port-id action in flow actions\n"); 163 printf(" --rss: add rss action in flow actions\n"); 164 printf(" --queue: add queue action in flow actions\n"); 165 printf(" --jump: add jump action in flow actions\n"); 166 printf(" --mark: add mark action in flow actions\n"); 167 printf(" --count: add count action in flow actions\n"); 168 printf(" --set-meta: add set meta action in flow actions\n"); 169 printf(" --set-tag: add set tag action in flow actions\n"); 170 printf(" --drop: add drop action in flow actions\n"); 171 printf(" --hairpin-queue=N: add hairpin-queue action in flow actions\n"); 172 printf(" --hairpin-rss=N: add hairpin-rss action in flow actions\n"); 173 printf(" --set-src-mac: add set src mac action to flow actions\n" 174 "Src mac to be set is random each flow\n"); 175 printf(" --set-dst-mac: add set dst mac action to flow actions\n" 176 "Dst mac to be set is random each flow\n"); 177 printf(" --set-src-ipv4: add set src ipv4 action to flow actions\n" 178 "Src ipv4 to be set is random each flow\n"); 179 printf(" --set-dst-ipv4 add set dst ipv4 action to flow actions\n" 180 "Dst ipv4 to be set is random each flow\n"); 181 printf(" --set-src-ipv6: add set src ipv6 action to flow actions\n" 182 "Src ipv6 to be set is random each flow\n"); 183 printf(" --set-dst-ipv6: add set dst ipv6 action to flow actions\n" 184 "Dst ipv6 to be set is random each flow\n"); 185 printf(" --set-src-tp: add set src tp action to flow actions\n" 186 "Src tp to be set is random each flow\n"); 187 printf(" --set-dst-tp: add set dst tp action to flow actions\n" 188 "Dst tp to be set is random each flow\n"); 189 printf(" --inc-tcp-ack: add inc tcp ack action to flow actions\n" 190 "tcp ack will be increments by 1\n"); 191 printf(" --dec-tcp-ack: add dec tcp ack action to flow actions\n" 192 "tcp ack will be decrements by 1\n"); 193 printf(" --inc-tcp-seq: add inc tcp seq action to flow actions\n" 194 "tcp seq will be increments by 1\n"); 195 printf(" --dec-tcp-seq: add dec tcp seq action to flow actions\n" 196 "tcp seq will be decrements by 1\n"); 197 printf(" --set-ttl: add set ttl action to flow actions\n" 198 "L3 ttl to be set is random each flow\n"); 199 printf(" --dec-ttl: add dec ttl action to flow actions\n" 200 "L3 ttl will be decrements by 1\n"); 201 printf(" --set-ipv4-dscp: add set ipv4 dscp action to flow actions\n" 202 "ipv4 dscp value to be set is random each flow\n"); 203 printf(" --set-ipv6-dscp: add set ipv6 dscp action to flow actions\n" 204 "ipv6 dscp value to be set is random each flow\n"); 205 printf(" --flag: add flag action to flow actions\n"); 206 printf(" --meter: add meter action to flow actions\n"); 207 printf(" --raw-encap=<data>: add raw encap action to flow actions\n" 208 "Data is the data needed to be encaped\n" 209 "Example: raw-encap=ether,ipv4,udp,vxlan\n"); 210 printf(" --raw-decap=<data>: add raw decap action to flow actions\n" 211 "Data is the data needed to be decaped\n" 212 "Example: raw-decap=ether,ipv4,udp,vxlan\n"); 213 printf(" --vxlan-encap: add vxlan-encap action to flow actions\n" 214 "Encapped data is fixed with pattern: ether,ipv4,udp,vxlan\n" 215 "With fixed values\n"); 216 printf(" --vxlan-decap: add vxlan_decap action to flow actions\n"); 217 } 218 219 static void 220 args_parse(int argc, char **argv) 221 { 222 uint64_t pm; 223 char **argvopt; 224 char *token; 225 char *end; 226 int n, opt; 227 int opt_idx; 228 size_t i; 229 230 static const struct option_dict { 231 const char *str; 232 const uint64_t mask; 233 uint64_t *map; 234 uint8_t *map_idx; 235 236 } flow_options[] = { 237 { 238 .str = "ether", 239 .mask = FLOW_ITEM_MASK(RTE_FLOW_ITEM_TYPE_ETH), 240 .map = &flow_items[0], 241 .map_idx = &items_idx 242 }, 243 { 244 .str = "ipv4", 245 .mask = FLOW_ITEM_MASK(RTE_FLOW_ITEM_TYPE_IPV4), 246 .map = &flow_items[0], 247 .map_idx = &items_idx 248 }, 249 { 250 .str = "ipv6", 251 .mask = FLOW_ITEM_MASK(RTE_FLOW_ITEM_TYPE_IPV6), 252 .map = &flow_items[0], 253 .map_idx = &items_idx 254 }, 255 { 256 .str = "vlan", 257 .mask = FLOW_ITEM_MASK(RTE_FLOW_ITEM_TYPE_VLAN), 258 .map = &flow_items[0], 259 .map_idx = &items_idx 260 }, 261 { 262 .str = "tcp", 263 .mask = FLOW_ITEM_MASK(RTE_FLOW_ITEM_TYPE_TCP), 264 .map = &flow_items[0], 265 .map_idx = &items_idx 266 }, 267 { 268 .str = "udp", 269 .mask = FLOW_ITEM_MASK(RTE_FLOW_ITEM_TYPE_UDP), 270 .map = &flow_items[0], 271 .map_idx = &items_idx 272 }, 273 { 274 .str = "vxlan", 275 .mask = FLOW_ITEM_MASK(RTE_FLOW_ITEM_TYPE_VXLAN), 276 .map = &flow_items[0], 277 .map_idx = &items_idx 278 }, 279 { 280 .str = "vxlan-gpe", 281 .mask = FLOW_ITEM_MASK(RTE_FLOW_ITEM_TYPE_VXLAN_GPE), 282 .map = &flow_items[0], 283 .map_idx = &items_idx 284 }, 285 { 286 .str = "gre", 287 .mask = FLOW_ITEM_MASK(RTE_FLOW_ITEM_TYPE_GRE), 288 .map = &flow_items[0], 289 .map_idx = &items_idx 290 }, 291 { 292 .str = "geneve", 293 .mask = FLOW_ITEM_MASK(RTE_FLOW_ITEM_TYPE_GENEVE), 294 .map = &flow_items[0], 295 .map_idx = &items_idx 296 }, 297 { 298 .str = "gtp", 299 .mask = FLOW_ITEM_MASK(RTE_FLOW_ITEM_TYPE_GTP), 300 .map = &flow_items[0], 301 .map_idx = &items_idx 302 }, 303 { 304 .str = "meta", 305 .mask = FLOW_ITEM_MASK(RTE_FLOW_ITEM_TYPE_META), 306 .map = &flow_items[0], 307 .map_idx = &items_idx 308 }, 309 { 310 .str = "tag", 311 .mask = FLOW_ITEM_MASK(RTE_FLOW_ITEM_TYPE_TAG), 312 .map = &flow_items[0], 313 .map_idx = &items_idx 314 }, 315 { 316 .str = "icmpv4", 317 .mask = FLOW_ITEM_MASK(RTE_FLOW_ITEM_TYPE_ICMP), 318 .map = &flow_items[0], 319 .map_idx = &items_idx 320 }, 321 { 322 .str = "icmpv6", 323 .mask = FLOW_ITEM_MASK(RTE_FLOW_ITEM_TYPE_ICMP6), 324 .map = &flow_items[0], 325 .map_idx = &items_idx 326 }, 327 { 328 .str = "ingress", 329 .mask = INGRESS, 330 .map = &flow_attrs[0], 331 .map_idx = &attrs_idx 332 }, 333 { 334 .str = "egress", 335 .mask = EGRESS, 336 .map = &flow_attrs[0], 337 .map_idx = &attrs_idx 338 }, 339 { 340 .str = "transfer", 341 .mask = TRANSFER, 342 .map = &flow_attrs[0], 343 .map_idx = &attrs_idx 344 }, 345 { 346 .str = "port-id", 347 .mask = FLOW_ACTION_MASK(RTE_FLOW_ACTION_TYPE_PORT_ID), 348 .map = &flow_actions[0], 349 .map_idx = &actions_idx 350 }, 351 { 352 .str = "rss", 353 .mask = FLOW_ACTION_MASK(RTE_FLOW_ACTION_TYPE_RSS), 354 .map = &flow_actions[0], 355 .map_idx = &actions_idx 356 }, 357 { 358 .str = "queue", 359 .mask = FLOW_ACTION_MASK(RTE_FLOW_ACTION_TYPE_QUEUE), 360 .map = &flow_actions[0], 361 .map_idx = &actions_idx 362 }, 363 { 364 .str = "jump", 365 .mask = FLOW_ACTION_MASK(RTE_FLOW_ACTION_TYPE_JUMP), 366 .map = &flow_actions[0], 367 .map_idx = &actions_idx 368 }, 369 { 370 .str = "mark", 371 .mask = FLOW_ACTION_MASK(RTE_FLOW_ACTION_TYPE_MARK), 372 .map = &flow_actions[0], 373 .map_idx = &actions_idx 374 }, 375 { 376 .str = "count", 377 .mask = FLOW_ACTION_MASK(RTE_FLOW_ACTION_TYPE_COUNT), 378 .map = &flow_actions[0], 379 .map_idx = &actions_idx 380 }, 381 { 382 .str = "set-meta", 383 .mask = FLOW_ACTION_MASK(RTE_FLOW_ACTION_TYPE_SET_META), 384 .map = &flow_actions[0], 385 .map_idx = &actions_idx 386 }, 387 { 388 .str = "set-tag", 389 .mask = FLOW_ACTION_MASK(RTE_FLOW_ACTION_TYPE_SET_TAG), 390 .map = &flow_actions[0], 391 .map_idx = &actions_idx 392 }, 393 { 394 .str = "drop", 395 .mask = FLOW_ACTION_MASK(RTE_FLOW_ACTION_TYPE_DROP), 396 .map = &flow_actions[0], 397 .map_idx = &actions_idx 398 }, 399 { 400 .str = "set-src-mac", 401 .mask = FLOW_ACTION_MASK( 402 RTE_FLOW_ACTION_TYPE_SET_MAC_SRC 403 ), 404 .map = &flow_actions[0], 405 .map_idx = &actions_idx 406 }, 407 { 408 .str = "set-dst-mac", 409 .mask = FLOW_ACTION_MASK( 410 RTE_FLOW_ACTION_TYPE_SET_MAC_DST 411 ), 412 .map = &flow_actions[0], 413 .map_idx = &actions_idx 414 }, 415 { 416 .str = "set-src-ipv4", 417 .mask = FLOW_ACTION_MASK( 418 RTE_FLOW_ACTION_TYPE_SET_IPV4_SRC 419 ), 420 .map = &flow_actions[0], 421 .map_idx = &actions_idx 422 }, 423 { 424 .str = "set-dst-ipv4", 425 .mask = FLOW_ACTION_MASK( 426 RTE_FLOW_ACTION_TYPE_SET_IPV4_DST 427 ), 428 .map = &flow_actions[0], 429 .map_idx = &actions_idx 430 }, 431 { 432 .str = "set-src-ipv6", 433 .mask = FLOW_ACTION_MASK( 434 RTE_FLOW_ACTION_TYPE_SET_IPV6_SRC 435 ), 436 .map = &flow_actions[0], 437 .map_idx = &actions_idx 438 }, 439 { 440 .str = "set-dst-ipv6", 441 .mask = FLOW_ACTION_MASK( 442 RTE_FLOW_ACTION_TYPE_SET_IPV6_DST 443 ), 444 .map = &flow_actions[0], 445 .map_idx = &actions_idx 446 }, 447 { 448 .str = "set-src-tp", 449 .mask = FLOW_ACTION_MASK( 450 RTE_FLOW_ACTION_TYPE_SET_TP_SRC 451 ), 452 .map = &flow_actions[0], 453 .map_idx = &actions_idx 454 }, 455 { 456 .str = "set-dst-tp", 457 .mask = FLOW_ACTION_MASK( 458 RTE_FLOW_ACTION_TYPE_SET_TP_DST 459 ), 460 .map = &flow_actions[0], 461 .map_idx = &actions_idx 462 }, 463 { 464 .str = "inc-tcp-ack", 465 .mask = FLOW_ACTION_MASK( 466 RTE_FLOW_ACTION_TYPE_INC_TCP_ACK 467 ), 468 .map = &flow_actions[0], 469 .map_idx = &actions_idx 470 }, 471 { 472 .str = "dec-tcp-ack", 473 .mask = FLOW_ACTION_MASK( 474 RTE_FLOW_ACTION_TYPE_DEC_TCP_ACK 475 ), 476 .map = &flow_actions[0], 477 .map_idx = &actions_idx 478 }, 479 { 480 .str = "inc-tcp-seq", 481 .mask = FLOW_ACTION_MASK( 482 RTE_FLOW_ACTION_TYPE_INC_TCP_SEQ 483 ), 484 .map = &flow_actions[0], 485 .map_idx = &actions_idx 486 }, 487 { 488 .str = "dec-tcp-seq", 489 .mask = FLOW_ACTION_MASK( 490 RTE_FLOW_ACTION_TYPE_DEC_TCP_SEQ 491 ), 492 .map = &flow_actions[0], 493 .map_idx = &actions_idx 494 }, 495 { 496 .str = "set-ttl", 497 .mask = FLOW_ACTION_MASK( 498 RTE_FLOW_ACTION_TYPE_SET_TTL 499 ), 500 .map = &flow_actions[0], 501 .map_idx = &actions_idx 502 }, 503 { 504 .str = "dec-ttl", 505 .mask = FLOW_ACTION_MASK( 506 RTE_FLOW_ACTION_TYPE_DEC_TTL 507 ), 508 .map = &flow_actions[0], 509 .map_idx = &actions_idx 510 }, 511 { 512 .str = "set-ipv4-dscp", 513 .mask = FLOW_ACTION_MASK( 514 RTE_FLOW_ACTION_TYPE_SET_IPV4_DSCP 515 ), 516 .map = &flow_actions[0], 517 .map_idx = &actions_idx 518 }, 519 { 520 .str = "set-ipv6-dscp", 521 .mask = FLOW_ACTION_MASK( 522 RTE_FLOW_ACTION_TYPE_SET_IPV6_DSCP 523 ), 524 .map = &flow_actions[0], 525 .map_idx = &actions_idx 526 }, 527 { 528 .str = "flag", 529 .mask = FLOW_ACTION_MASK( 530 RTE_FLOW_ACTION_TYPE_FLAG 531 ), 532 .map = &flow_actions[0], 533 .map_idx = &actions_idx 534 }, 535 { 536 .str = "meter", 537 .mask = FLOW_ACTION_MASK( 538 RTE_FLOW_ACTION_TYPE_METER 539 ), 540 .map = &flow_actions[0], 541 .map_idx = &actions_idx 542 }, 543 { 544 .str = "vxlan-encap", 545 .mask = FLOW_ACTION_MASK( 546 RTE_FLOW_ACTION_TYPE_VXLAN_ENCAP 547 ), 548 .map = &flow_actions[0], 549 .map_idx = &actions_idx 550 }, 551 { 552 .str = "vxlan-decap", 553 .mask = FLOW_ACTION_MASK( 554 RTE_FLOW_ACTION_TYPE_VXLAN_DECAP 555 ), 556 .map = &flow_actions[0], 557 .map_idx = &actions_idx 558 }, 559 }; 560 561 static const struct option lgopts[] = { 562 /* Control */ 563 { "help", 0, 0, 0 }, 564 { "rules-count", 1, 0, 0 }, 565 { "rules-batch", 1, 0, 0 }, 566 { "dump-iterations", 0, 0, 0 }, 567 { "deletion-rate", 0, 0, 0 }, 568 { "dump-socket-mem", 0, 0, 0 }, 569 { "enable-fwd", 0, 0, 0 }, 570 { "portmask", 1, 0, 0 }, 571 { "cores", 1, 0, 0 }, 572 /* Attributes */ 573 { "ingress", 0, 0, 0 }, 574 { "egress", 0, 0, 0 }, 575 { "transfer", 0, 0, 0 }, 576 { "group", 1, 0, 0 }, 577 /* Items */ 578 { "ether", 0, 0, 0 }, 579 { "vlan", 0, 0, 0 }, 580 { "ipv4", 0, 0, 0 }, 581 { "ipv6", 0, 0, 0 }, 582 { "tcp", 0, 0, 0 }, 583 { "udp", 0, 0, 0 }, 584 { "vxlan", 0, 0, 0 }, 585 { "vxlan-gpe", 0, 0, 0 }, 586 { "gre", 0, 0, 0 }, 587 { "geneve", 0, 0, 0 }, 588 { "gtp", 0, 0, 0 }, 589 { "meta", 0, 0, 0 }, 590 { "tag", 0, 0, 0 }, 591 { "icmpv4", 0, 0, 0 }, 592 { "icmpv6", 0, 0, 0 }, 593 /* Actions */ 594 { "port-id", 0, 0, 0 }, 595 { "rss", 0, 0, 0 }, 596 { "queue", 0, 0, 0 }, 597 { "jump", 0, 0, 0 }, 598 { "mark", 0, 0, 0 }, 599 { "count", 0, 0, 0 }, 600 { "set-meta", 0, 0, 0 }, 601 { "set-tag", 0, 0, 0 }, 602 { "drop", 0, 0, 0 }, 603 { "hairpin-queue", 1, 0, 0 }, 604 { "hairpin-rss", 1, 0, 0 }, 605 { "set-src-mac", 0, 0, 0 }, 606 { "set-dst-mac", 0, 0, 0 }, 607 { "set-src-ipv4", 0, 0, 0 }, 608 { "set-dst-ipv4", 0, 0, 0 }, 609 { "set-src-ipv6", 0, 0, 0 }, 610 { "set-dst-ipv6", 0, 0, 0 }, 611 { "set-src-tp", 0, 0, 0 }, 612 { "set-dst-tp", 0, 0, 0 }, 613 { "inc-tcp-ack", 0, 0, 0 }, 614 { "dec-tcp-ack", 0, 0, 0 }, 615 { "inc-tcp-seq", 0, 0, 0 }, 616 { "dec-tcp-seq", 0, 0, 0 }, 617 { "set-ttl", 0, 0, 0 }, 618 { "dec-ttl", 0, 0, 0 }, 619 { "set-ipv4-dscp", 0, 0, 0 }, 620 { "set-ipv6-dscp", 0, 0, 0 }, 621 { "flag", 0, 0, 0 }, 622 { "meter", 0, 0, 0 }, 623 { "raw-encap", 1, 0, 0 }, 624 { "raw-decap", 1, 0, 0 }, 625 { "vxlan-encap", 0, 0, 0 }, 626 { "vxlan-decap", 0, 0, 0 }, 627 }; 628 629 RTE_ETH_FOREACH_DEV(i) 630 ports_mask |= 1 << i; 631 632 hairpin_queues_num = 0; 633 argvopt = argv; 634 635 printf(":: Flow -> "); 636 while ((opt = getopt_long(argc, argvopt, "", 637 lgopts, &opt_idx)) != EOF) { 638 switch (opt) { 639 case 0: 640 if (strcmp(lgopts[opt_idx].name, "help") == 0) { 641 usage(argv[0]); 642 rte_exit(EXIT_SUCCESS, "Displayed help\n"); 643 } 644 645 if (strcmp(lgopts[opt_idx].name, "group") == 0) { 646 n = atoi(optarg); 647 if (n >= 0) 648 flow_group = n; 649 else 650 rte_exit(EXIT_SUCCESS, 651 "flow group should be >= 0\n"); 652 printf("group %d / ", flow_group); 653 } 654 655 for (i = 0; i < RTE_DIM(flow_options); i++) 656 if (strcmp(lgopts[opt_idx].name, 657 flow_options[i].str) == 0) { 658 flow_options[i].map[ 659 (*flow_options[i].map_idx)++] = 660 flow_options[i].mask; 661 printf("%s / ", flow_options[i].str); 662 } 663 664 if (strcmp(lgopts[opt_idx].name, 665 "hairpin-rss") == 0) { 666 n = atoi(optarg); 667 if (n > 0) 668 hairpin_queues_num = n; 669 else 670 rte_exit(EXIT_SUCCESS, 671 "Hairpin queues should be > 0\n"); 672 673 flow_actions[actions_idx++] = 674 HAIRPIN_RSS_ACTION; 675 printf("hairpin-rss / "); 676 } 677 if (strcmp(lgopts[opt_idx].name, 678 "hairpin-queue") == 0) { 679 n = atoi(optarg); 680 if (n > 0) 681 hairpin_queues_num = n; 682 else 683 rte_exit(EXIT_SUCCESS, 684 "Hairpin queues should be > 0\n"); 685 686 flow_actions[actions_idx++] = 687 HAIRPIN_QUEUE_ACTION; 688 printf("hairpin-queue / "); 689 } 690 691 if (strcmp(lgopts[opt_idx].name, "raw-encap") == 0) { 692 printf("raw-encap "); 693 flow_actions[actions_idx++] = 694 FLOW_ITEM_MASK( 695 RTE_FLOW_ACTION_TYPE_RAW_ENCAP 696 ); 697 698 token = strtok(optarg, ","); 699 while (token != NULL) { 700 for (i = 0; i < RTE_DIM(flow_options); i++) { 701 if (strcmp(flow_options[i].str, token) == 0) { 702 printf("%s,", token); 703 encap_data |= flow_options[i].mask; 704 break; 705 } 706 /* Reached last item with no match */ 707 if (i == (RTE_DIM(flow_options) - 1)) { 708 fprintf(stderr, "Invalid encap item: %s\n", token); 709 usage(argv[0]); 710 rte_exit(EXIT_SUCCESS, "Invalid encap item\n"); 711 } 712 } 713 token = strtok(NULL, ","); 714 } 715 printf(" / "); 716 } 717 if (strcmp(lgopts[opt_idx].name, "raw-decap") == 0) { 718 printf("raw-decap "); 719 flow_actions[actions_idx++] = 720 FLOW_ITEM_MASK( 721 RTE_FLOW_ACTION_TYPE_RAW_DECAP 722 ); 723 724 token = strtok(optarg, ","); 725 while (token != NULL) { 726 for (i = 0; i < RTE_DIM(flow_options); i++) { 727 if (strcmp(flow_options[i].str, token) == 0) { 728 printf("%s,", token); 729 encap_data |= flow_options[i].mask; 730 break; 731 } 732 /* Reached last item with no match */ 733 if (i == (RTE_DIM(flow_options) - 1)) { 734 fprintf(stderr, "Invalid decap item: %s\n", token); 735 usage(argv[0]); 736 rte_exit(EXIT_SUCCESS, "Invalid decap item\n"); 737 } 738 } 739 token = strtok(NULL, ","); 740 } 741 printf(" / "); 742 } 743 /* Control */ 744 if (strcmp(lgopts[opt_idx].name, 745 "rules-batch") == 0) { 746 n = atoi(optarg); 747 if (n >= DEFAULT_RULES_BATCH) 748 rules_batch = n; 749 else { 750 printf("\n\nrules_batch should be >= %d\n", 751 DEFAULT_RULES_BATCH); 752 rte_exit(EXIT_SUCCESS, " "); 753 } 754 } 755 if (strcmp(lgopts[opt_idx].name, 756 "rules-count") == 0) { 757 n = atoi(optarg); 758 if (n >= (int) rules_batch) 759 rules_count = n; 760 else { 761 printf("\n\nrules_count should be >= %d\n", 762 rules_batch); 763 } 764 } 765 if (strcmp(lgopts[opt_idx].name, 766 "dump-iterations") == 0) 767 dump_iterations = true; 768 if (strcmp(lgopts[opt_idx].name, 769 "deletion-rate") == 0) 770 delete_flag = true; 771 if (strcmp(lgopts[opt_idx].name, 772 "dump-socket-mem") == 0) 773 dump_socket_mem_flag = true; 774 if (strcmp(lgopts[opt_idx].name, 775 "enable-fwd") == 0) 776 enable_fwd = true; 777 if (strcmp(lgopts[opt_idx].name, 778 "portmask") == 0) { 779 /* parse hexadecimal string */ 780 end = NULL; 781 pm = strtoull(optarg, &end, 16); 782 if ((optarg[0] == '\0') || (end == NULL) || (*end != '\0')) 783 rte_exit(EXIT_FAILURE, "Invalid fwd port mask\n"); 784 ports_mask = pm; 785 } 786 if (strcmp(lgopts[opt_idx].name, "cores") == 0) { 787 n = atoi(optarg); 788 if ((int) rte_lcore_count() <= n) { 789 printf("\nError: you need %d cores to run on multi-cores\n" 790 "Existing cores are: %d\n", n, rte_lcore_count()); 791 rte_exit(EXIT_FAILURE, " "); 792 } 793 if (n <= RTE_MAX_LCORE && n > 0) 794 mc_pool.cores_count = n; 795 else { 796 printf("Error: cores count must be > 0 " 797 " and < %d\n", RTE_MAX_LCORE); 798 rte_exit(EXIT_FAILURE, " "); 799 } 800 } 801 break; 802 default: 803 fprintf(stderr, "Invalid option: %s\n", argv[optind]); 804 usage(argv[0]); 805 rte_exit(EXIT_SUCCESS, "Invalid option\n"); 806 break; 807 } 808 } 809 printf("end_flow\n"); 810 } 811 812 /* Dump the socket memory statistics on console */ 813 static size_t 814 dump_socket_mem(FILE *f) 815 { 816 struct rte_malloc_socket_stats socket_stats; 817 unsigned int i = 0; 818 size_t total = 0; 819 size_t alloc = 0; 820 size_t free = 0; 821 unsigned int n_alloc = 0; 822 unsigned int n_free = 0; 823 bool active_nodes = false; 824 825 826 for (i = 0; i < RTE_MAX_NUMA_NODES; i++) { 827 if (rte_malloc_get_socket_stats(i, &socket_stats) || 828 !socket_stats.heap_totalsz_bytes) 829 continue; 830 active_nodes = true; 831 total += socket_stats.heap_totalsz_bytes; 832 alloc += socket_stats.heap_allocsz_bytes; 833 free += socket_stats.heap_freesz_bytes; 834 n_alloc += socket_stats.alloc_count; 835 n_free += socket_stats.free_count; 836 if (dump_socket_mem_flag) { 837 fprintf(f, "::::::::::::::::::::::::::::::::::::::::"); 838 fprintf(f, 839 "\nSocket %u:\nsize(M) total: %.6lf\nalloc:" 840 " %.6lf(%.3lf%%)\nfree: %.6lf" 841 "\nmax: %.6lf" 842 "\ncount alloc: %u\nfree: %u\n", 843 i, 844 socket_stats.heap_totalsz_bytes / 1.0e6, 845 socket_stats.heap_allocsz_bytes / 1.0e6, 846 (double)socket_stats.heap_allocsz_bytes * 100 / 847 (double)socket_stats.heap_totalsz_bytes, 848 socket_stats.heap_freesz_bytes / 1.0e6, 849 socket_stats.greatest_free_size / 1.0e6, 850 socket_stats.alloc_count, 851 socket_stats.free_count); 852 fprintf(f, "::::::::::::::::::::::::::::::::::::::::"); 853 } 854 } 855 if (dump_socket_mem_flag && active_nodes) { 856 fprintf(f, 857 "\nTotal: size(M)\ntotal: %.6lf" 858 "\nalloc: %.6lf(%.3lf%%)\nfree: %.6lf" 859 "\ncount alloc: %u\nfree: %u\n", 860 total / 1.0e6, alloc / 1.0e6, 861 (double)alloc * 100 / (double)total, free / 1.0e6, 862 n_alloc, n_free); 863 fprintf(f, "::::::::::::::::::::::::::::::::::::::::\n"); 864 } 865 return alloc; 866 } 867 868 static void 869 print_flow_error(struct rte_flow_error error) 870 { 871 printf("Flow can't be created %d message: %s\n", 872 error.type, 873 error.message ? error.message : "(no stated reason)"); 874 } 875 876 static inline void 877 print_rules_batches(double *cpu_time_per_batch) 878 { 879 uint8_t idx; 880 double delta; 881 double rate; 882 883 for (idx = 0; idx < MAX_BATCHES_COUNT; idx++) { 884 if (!cpu_time_per_batch[idx]) 885 break; 886 delta = (double)(rules_batch / cpu_time_per_batch[idx]); 887 rate = delta / 1000; /* Save rate in K unit. */ 888 printf(":: Rules batch #%d: %d rules " 889 "in %f sec[ Rate = %f K Rule/Sec ]\n", 890 idx, rules_batch, 891 cpu_time_per_batch[idx], rate); 892 } 893 } 894 895 896 static inline int 897 has_meter(void) 898 { 899 int i; 900 901 for (i = 0; i < MAX_ACTIONS_NUM; i++) { 902 if (flow_actions[i] == 0) 903 break; 904 if (flow_actions[i] 905 & FLOW_ACTION_MASK(RTE_FLOW_ACTION_TYPE_METER)) 906 return 1; 907 } 908 return 0; 909 } 910 911 static void 912 create_meter_rule(int port_id, uint32_t counter) 913 { 914 int ret; 915 struct rte_mtr_params params; 916 uint32_t default_prof_id = 100; 917 struct rte_mtr_error error; 918 919 memset(¶ms, 0, sizeof(struct rte_mtr_params)); 920 params.meter_enable = 1; 921 params.stats_mask = 0xffff; 922 params.use_prev_mtr_color = 0; 923 params.dscp_table = NULL; 924 925 /*create meter*/ 926 params.meter_profile_id = default_prof_id; 927 params.action[RTE_COLOR_GREEN] = 928 MTR_POLICER_ACTION_COLOR_GREEN; 929 params.action[RTE_COLOR_YELLOW] = 930 MTR_POLICER_ACTION_COLOR_YELLOW; 931 params.action[RTE_COLOR_RED] = 932 MTR_POLICER_ACTION_DROP; 933 934 ret = rte_mtr_create(port_id, counter, ¶ms, 1, &error); 935 if (ret != 0) { 936 printf("Port %u create meter idx(%d) error(%d) message: %s\n", 937 port_id, counter, error.type, 938 error.message ? error.message : "(no stated reason)"); 939 rte_exit(EXIT_FAILURE, "error in creating meter"); 940 } 941 } 942 943 static void 944 destroy_meter_rule(int port_id, uint32_t counter) 945 { 946 struct rte_mtr_error error; 947 948 if (rte_mtr_destroy(port_id, counter, &error)) { 949 printf("Port %u destroy meter(%d) error(%d) message: %s\n", 950 port_id, counter, error.type, 951 error.message ? error.message : "(no stated reason)"); 952 rte_exit(EXIT_FAILURE, "Error in deleting meter rule"); 953 } 954 } 955 956 static void 957 meters_handler(int port_id, uint8_t core_id, uint8_t ops) 958 { 959 uint64_t start_batch; 960 double cpu_time_used, insertion_rate; 961 int rules_count_per_core, rules_batch_idx; 962 uint32_t counter, start_counter = 0, end_counter; 963 double cpu_time_per_batch[MAX_BATCHES_COUNT] = { 0 }; 964 965 rules_count_per_core = rules_count / mc_pool.cores_count; 966 967 if (core_id) 968 start_counter = core_id * rules_count_per_core; 969 end_counter = (core_id + 1) * rules_count_per_core; 970 971 cpu_time_used = 0; 972 start_batch = rte_rdtsc(); 973 for (counter = start_counter; counter < end_counter; counter++) { 974 if (ops == METER_CREATE) 975 create_meter_rule(port_id, counter); 976 else 977 destroy_meter_rule(port_id, counter); 978 /* 979 * Save the insertion rate for rules batch. 980 * Check if the insertion reached the rules 981 * patch counter, then save the insertion rate 982 * for this batch. 983 */ 984 if (!((counter + 1) % rules_batch)) { 985 rules_batch_idx = ((counter + 1) / rules_batch) - 1; 986 cpu_time_per_batch[rules_batch_idx] = 987 ((double)(rte_rdtsc() - start_batch)) 988 / rte_get_tsc_hz(); 989 cpu_time_used += cpu_time_per_batch[rules_batch_idx]; 990 start_batch = rte_rdtsc(); 991 } 992 } 993 994 /* Print insertion rates for all batches */ 995 if (dump_iterations) 996 print_rules_batches(cpu_time_per_batch); 997 998 insertion_rate = 999 ((double) (rules_count_per_core / cpu_time_used) / 1000); 1000 1001 /* Insertion rate for all rules in one core */ 1002 printf(":: Port %d :: Core %d Meter %s :: start @[%d] - end @[%d]," 1003 " use:%.02fs, rate:%.02fk Rule/Sec\n", 1004 port_id, core_id, ops == METER_CREATE ? "create" : "delete", 1005 start_counter, end_counter - 1, 1006 cpu_time_used, insertion_rate); 1007 1008 if (ops == METER_CREATE) 1009 mc_pool.create_meter.insertion[port_id][core_id] 1010 = cpu_time_used; 1011 else 1012 mc_pool.create_meter.deletion[port_id][core_id] 1013 = cpu_time_used; 1014 } 1015 1016 static void 1017 destroy_meter_profile(void) 1018 { 1019 struct rte_mtr_error error; 1020 uint16_t nr_ports; 1021 int port_id; 1022 1023 nr_ports = rte_eth_dev_count_avail(); 1024 for (port_id = 0; port_id < nr_ports; port_id++) { 1025 /* If port outside portmask */ 1026 if (!((ports_mask >> port_id) & 0x1)) 1027 continue; 1028 1029 if (rte_mtr_meter_profile_delete 1030 (port_id, DEFAULT_METER_PROF_ID, &error)) { 1031 printf("Port %u del profile error(%d) message: %s\n", 1032 port_id, error.type, 1033 error.message ? error.message : "(no stated reason)"); 1034 rte_exit(EXIT_FAILURE, "Error: Destroy meter profile Failed!\n"); 1035 } 1036 } 1037 } 1038 1039 static void 1040 create_meter_profile(void) 1041 { 1042 uint16_t nr_ports; 1043 int ret, port_id; 1044 struct rte_mtr_meter_profile mp; 1045 struct rte_mtr_error error; 1046 1047 /* 1048 *currently , only create one meter file for one port 1049 *1 meter profile -> N meter rules -> N rte flows 1050 */ 1051 memset(&mp, 0, sizeof(struct rte_mtr_meter_profile)); 1052 nr_ports = rte_eth_dev_count_avail(); 1053 for (port_id = 0; port_id < nr_ports; port_id++) { 1054 /* If port outside portmask */ 1055 if (!((ports_mask >> port_id) & 0x1)) 1056 continue; 1057 1058 mp.alg = RTE_MTR_SRTCM_RFC2697; 1059 mp.srtcm_rfc2697.cir = METER_CIR; 1060 mp.srtcm_rfc2697.cbs = METER_CIR / 8; 1061 mp.srtcm_rfc2697.ebs = 0; 1062 1063 ret = rte_mtr_meter_profile_add 1064 (port_id, DEFAULT_METER_PROF_ID, &mp, &error); 1065 if (ret != 0) { 1066 printf("Port %u create Profile error(%d) message: %s\n", 1067 port_id, error.type, 1068 error.message ? error.message : "(no stated reason)"); 1069 rte_exit(EXIT_FAILURE, "Error: Creation meter profile Failed!\n"); 1070 } 1071 } 1072 } 1073 1074 static inline void 1075 destroy_flows(int port_id, uint8_t core_id, struct rte_flow **flows_list) 1076 { 1077 struct rte_flow_error error; 1078 clock_t start_batch, end_batch; 1079 double cpu_time_used = 0; 1080 double deletion_rate; 1081 double cpu_time_per_batch[MAX_BATCHES_COUNT] = { 0 }; 1082 double delta; 1083 uint32_t i; 1084 int rules_batch_idx; 1085 int rules_count_per_core; 1086 1087 rules_count_per_core = rules_count / mc_pool.cores_count; 1088 /* If group > 0 , should add 1 flow which created in group 0 */ 1089 if (flow_group > 0 && core_id == 0) 1090 rules_count_per_core++; 1091 1092 start_batch = rte_rdtsc(); 1093 for (i = 0; i < (uint32_t) rules_count_per_core; i++) { 1094 if (flows_list[i] == 0) 1095 break; 1096 1097 memset(&error, 0x33, sizeof(error)); 1098 if (rte_flow_destroy(port_id, flows_list[i], &error)) { 1099 print_flow_error(error); 1100 rte_exit(EXIT_FAILURE, "Error in deleting flow"); 1101 } 1102 1103 /* 1104 * Save the deletion rate for rules batch. 1105 * Check if the deletion reached the rules 1106 * patch counter, then save the deletion rate 1107 * for this batch. 1108 */ 1109 if (!((i + 1) % rules_batch)) { 1110 end_batch = rte_rdtsc(); 1111 delta = (double) (end_batch - start_batch); 1112 rules_batch_idx = ((i + 1) / rules_batch) - 1; 1113 cpu_time_per_batch[rules_batch_idx] = delta / rte_get_tsc_hz(); 1114 cpu_time_used += cpu_time_per_batch[rules_batch_idx]; 1115 start_batch = rte_rdtsc(); 1116 } 1117 } 1118 1119 /* Print deletion rates for all batches */ 1120 if (dump_iterations) 1121 print_rules_batches(cpu_time_per_batch); 1122 1123 /* Deletion rate for all rules */ 1124 deletion_rate = ((double) (rules_count_per_core / cpu_time_used) / 1000); 1125 printf(":: Port %d :: Core %d :: Rules deletion rate -> %f K Rule/Sec\n", 1126 port_id, core_id, deletion_rate); 1127 printf(":: Port %d :: Core %d :: The time for deleting %d rules is %f seconds\n", 1128 port_id, core_id, rules_count_per_core, cpu_time_used); 1129 1130 mc_pool.create_flow.deletion[port_id][core_id] = cpu_time_used; 1131 } 1132 1133 static struct rte_flow ** 1134 insert_flows(int port_id, uint8_t core_id) 1135 { 1136 struct rte_flow **flows_list; 1137 struct rte_flow_error error; 1138 clock_t start_batch, end_batch; 1139 double cpu_time_used; 1140 double insertion_rate; 1141 double cpu_time_per_batch[MAX_BATCHES_COUNT] = { 0 }; 1142 double delta; 1143 uint32_t flow_index; 1144 uint32_t counter, start_counter = 0, end_counter; 1145 uint64_t global_items[MAX_ITEMS_NUM] = { 0 }; 1146 uint64_t global_actions[MAX_ACTIONS_NUM] = { 0 }; 1147 int rules_batch_idx; 1148 int rules_count_per_core; 1149 1150 rules_count_per_core = rules_count / mc_pool.cores_count; 1151 1152 /* Set boundaries of rules for each core. */ 1153 if (core_id) 1154 start_counter = core_id * rules_count_per_core; 1155 end_counter = (core_id + 1) * rules_count_per_core; 1156 1157 global_items[0] = FLOW_ITEM_MASK(RTE_FLOW_ITEM_TYPE_ETH); 1158 global_actions[0] = FLOW_ITEM_MASK(RTE_FLOW_ACTION_TYPE_JUMP); 1159 1160 flows_list = rte_zmalloc("flows_list", 1161 (sizeof(struct rte_flow *) * rules_count_per_core) + 1, 0); 1162 if (flows_list == NULL) 1163 rte_exit(EXIT_FAILURE, "No Memory available!"); 1164 1165 cpu_time_used = 0; 1166 flow_index = 0; 1167 if (flow_group > 0 && core_id == 0) { 1168 /* 1169 * Create global rule to jump into flow_group, 1170 * this way the app will avoid the default rules. 1171 * 1172 * This rule will be created only once. 1173 * 1174 * Global rule: 1175 * group 0 eth / end actions jump group <flow_group> 1176 */ 1177 flow = generate_flow(port_id, 0, flow_attrs, 1178 global_items, global_actions, 1179 flow_group, 0, 0, 0, 0, core_id, &error); 1180 1181 if (flow == NULL) { 1182 print_flow_error(error); 1183 rte_exit(EXIT_FAILURE, "error in creating flow"); 1184 } 1185 flows_list[flow_index++] = flow; 1186 } 1187 1188 start_batch = rte_rdtsc(); 1189 for (counter = start_counter; counter < end_counter; counter++) { 1190 flow = generate_flow(port_id, flow_group, 1191 flow_attrs, flow_items, flow_actions, 1192 JUMP_ACTION_TABLE, counter, 1193 hairpin_queues_num, 1194 encap_data, decap_data, 1195 core_id, &error); 1196 1197 if (force_quit) 1198 counter = end_counter; 1199 1200 if (!flow) { 1201 print_flow_error(error); 1202 rte_exit(EXIT_FAILURE, "error in creating flow"); 1203 } 1204 1205 flows_list[flow_index++] = flow; 1206 1207 /* 1208 * Save the insertion rate for rules batch. 1209 * Check if the insertion reached the rules 1210 * patch counter, then save the insertion rate 1211 * for this batch. 1212 */ 1213 if (!((counter + 1) % rules_batch)) { 1214 end_batch = rte_rdtsc(); 1215 delta = (double) (end_batch - start_batch); 1216 rules_batch_idx = ((counter + 1) / rules_batch) - 1; 1217 cpu_time_per_batch[rules_batch_idx] = delta / rte_get_tsc_hz(); 1218 cpu_time_used += cpu_time_per_batch[rules_batch_idx]; 1219 start_batch = rte_rdtsc(); 1220 } 1221 } 1222 1223 /* Print insertion rates for all batches */ 1224 if (dump_iterations) 1225 print_rules_batches(cpu_time_per_batch); 1226 1227 printf(":: Port %d :: Core %d boundaries :: start @[%d] - end @[%d]\n", 1228 port_id, core_id, start_counter, end_counter - 1); 1229 1230 /* Insertion rate for all rules in one core */ 1231 insertion_rate = ((double) (rules_count_per_core / cpu_time_used) / 1000); 1232 printf(":: Port %d :: Core %d :: Rules insertion rate -> %f K Rule/Sec\n", 1233 port_id, core_id, insertion_rate); 1234 printf(":: Port %d :: Core %d :: The time for creating %d in rules %f seconds\n", 1235 port_id, core_id, rules_count_per_core, cpu_time_used); 1236 1237 mc_pool.create_flow.insertion[port_id][core_id] = cpu_time_used; 1238 return flows_list; 1239 } 1240 1241 static void 1242 flows_handler(uint8_t core_id) 1243 { 1244 struct rte_flow **flows_list; 1245 uint16_t nr_ports; 1246 int port_id; 1247 1248 nr_ports = rte_eth_dev_count_avail(); 1249 1250 if (rules_batch > rules_count) 1251 rules_batch = rules_count; 1252 1253 printf(":: Rules Count per port: %d\n\n", rules_count); 1254 1255 for (port_id = 0; port_id < nr_ports; port_id++) { 1256 /* If port outside portmask */ 1257 if (!((ports_mask >> port_id) & 0x1)) 1258 continue; 1259 1260 /* Insertion part. */ 1261 mc_pool.last_alloc[core_id] = (int64_t)dump_socket_mem(stdout); 1262 if (has_meter()) 1263 meters_handler(port_id, core_id, METER_CREATE); 1264 flows_list = insert_flows(port_id, core_id); 1265 if (flows_list == NULL) 1266 rte_exit(EXIT_FAILURE, "Error: Insertion Failed!\n"); 1267 mc_pool.current_alloc[core_id] = (int64_t)dump_socket_mem(stdout); 1268 1269 /* Deletion part. */ 1270 if (delete_flag) { 1271 destroy_flows(port_id, core_id, flows_list); 1272 if (has_meter()) 1273 meters_handler(port_id, core_id, METER_DELETE); 1274 } 1275 } 1276 } 1277 1278 static void 1279 dump_used_cpu_time(const char *item, 1280 uint16_t port, struct used_cpu_time *used_time) 1281 { 1282 uint32_t i; 1283 /* Latency: total count of rte rules divided 1284 * over max time used by thread between all 1285 * threads time. 1286 * 1287 * Throughput: total count of rte rules divided 1288 * over the average of the time cosumed by all 1289 * threads time. 1290 */ 1291 double insertion_latency_time; 1292 double insertion_throughput_time; 1293 double deletion_latency_time; 1294 double deletion_throughput_time; 1295 double insertion_latency, insertion_throughput; 1296 double deletion_latency, deletion_throughput; 1297 1298 /* Save first insertion/deletion rates from first thread. 1299 * Start comparing with all threads, if any thread used 1300 * time more than current saved, replace it. 1301 * 1302 * Thus in the end we will have the max time used for 1303 * insertion/deletion by one thread. 1304 * 1305 * As for memory consumption, save the min of all threads 1306 * of last alloc, and save the max for all threads for 1307 * current alloc. 1308 */ 1309 1310 insertion_latency_time = used_time->insertion[port][0]; 1311 deletion_latency_time = used_time->deletion[port][0]; 1312 insertion_throughput_time = used_time->insertion[port][0]; 1313 deletion_throughput_time = used_time->deletion[port][0]; 1314 1315 i = mc_pool.cores_count; 1316 while (i-- > 1) { 1317 insertion_throughput_time += used_time->insertion[port][i]; 1318 deletion_throughput_time += used_time->deletion[port][i]; 1319 if (insertion_latency_time < used_time->insertion[port][i]) 1320 insertion_latency_time = used_time->insertion[port][i]; 1321 if (deletion_latency_time < used_time->deletion[port][i]) 1322 deletion_latency_time = used_time->deletion[port][i]; 1323 } 1324 1325 insertion_latency = ((double) (mc_pool.rules_count 1326 / insertion_latency_time) / 1000); 1327 deletion_latency = ((double) (mc_pool.rules_count 1328 / deletion_latency_time) / 1000); 1329 1330 insertion_throughput_time /= mc_pool.cores_count; 1331 deletion_throughput_time /= mc_pool.cores_count; 1332 insertion_throughput = ((double) (mc_pool.rules_count 1333 / insertion_throughput_time) / 1000); 1334 deletion_throughput = ((double) (mc_pool.rules_count 1335 / deletion_throughput_time) / 1000); 1336 1337 /* Latency stats */ 1338 printf("\n%s\n:: [Latency | Insertion] All Cores :: Port %d :: ", 1339 item, port); 1340 printf("Total flows insertion rate -> %f K Rules/Sec\n", 1341 insertion_latency); 1342 printf(":: [Latency | Insertion] All Cores :: Port %d :: ", port); 1343 printf("The time for creating %d rules is %f seconds\n", 1344 mc_pool.rules_count, insertion_latency_time); 1345 1346 /* Throughput stats */ 1347 printf(":: [Throughput | Insertion] All Cores :: Port %d :: ", port); 1348 printf("Total flows insertion rate -> %f K Rules/Sec\n", 1349 insertion_throughput); 1350 printf(":: [Throughput | Insertion] All Cores :: Port %d :: ", port); 1351 printf("The average time for creating %d rules is %f seconds\n", 1352 mc_pool.rules_count, insertion_throughput_time); 1353 1354 if (delete_flag) { 1355 /* Latency stats */ 1356 printf(":: [Latency | Deletion] All Cores :: Port %d :: Total " 1357 "deletion rate -> %f K Rules/Sec\n", 1358 port, deletion_latency); 1359 printf(":: [Latency | Deletion] All Cores :: Port %d :: ", 1360 port); 1361 printf("The time for deleting %d rules is %f seconds\n", 1362 mc_pool.rules_count, deletion_latency_time); 1363 1364 /* Throughput stats */ 1365 printf(":: [Throughput | Deletion] All Cores :: Port %d :: Total " 1366 "deletion rate -> %f K Rules/Sec\n", 1367 port, deletion_throughput); 1368 printf(":: [Throughput | Deletion] All Cores :: Port %d :: ", 1369 port); 1370 printf("The average time for deleting %d rules is %f seconds\n", 1371 mc_pool.rules_count, deletion_throughput_time); 1372 } 1373 } 1374 1375 static void 1376 dump_used_mem(uint16_t port) 1377 { 1378 uint32_t i; 1379 int64_t last_alloc, current_alloc; 1380 int flow_size_in_bytes; 1381 1382 last_alloc = mc_pool.last_alloc[0]; 1383 current_alloc = mc_pool.current_alloc[0]; 1384 1385 i = mc_pool.cores_count; 1386 while (i-- > 1) { 1387 if (last_alloc > mc_pool.last_alloc[i]) 1388 last_alloc = mc_pool.last_alloc[i]; 1389 if (current_alloc < mc_pool.current_alloc[i]) 1390 current_alloc = mc_pool.current_alloc[i]; 1391 } 1392 1393 flow_size_in_bytes = (current_alloc - last_alloc) / mc_pool.rules_count; 1394 printf("\n:: Port %d :: rte_flow size in DPDK layer: %d Bytes\n", 1395 port, flow_size_in_bytes); 1396 } 1397 1398 static int 1399 run_rte_flow_handler_cores(void *data __rte_unused) 1400 { 1401 uint16_t port; 1402 int lcore_counter = 0; 1403 int lcore_id = rte_lcore_id(); 1404 int i; 1405 1406 RTE_LCORE_FOREACH(i) { 1407 /* If core not needed return. */ 1408 if (lcore_id == i) { 1409 printf(":: lcore %d mapped with index %d\n", lcore_id, lcore_counter); 1410 if (lcore_counter >= (int) mc_pool.cores_count) 1411 return 0; 1412 break; 1413 } 1414 lcore_counter++; 1415 } 1416 lcore_id = lcore_counter; 1417 1418 if (lcore_id >= (int) mc_pool.cores_count) 1419 return 0; 1420 1421 mc_pool.rules_count = rules_count; 1422 1423 flows_handler(lcore_id); 1424 1425 /* Only main core to print total results. */ 1426 if (lcore_id != 0) 1427 return 0; 1428 1429 /* Make sure all cores finished insertion/deletion process. */ 1430 rte_eal_mp_wait_lcore(); 1431 1432 RTE_ETH_FOREACH_DEV(port) { 1433 if (has_meter()) 1434 dump_used_cpu_time("Meters:", 1435 port, &mc_pool.create_meter); 1436 dump_used_cpu_time("Flows:", 1437 port, &mc_pool.create_flow); 1438 dump_used_mem(port); 1439 } 1440 1441 return 0; 1442 } 1443 1444 static void 1445 signal_handler(int signum) 1446 { 1447 if (signum == SIGINT || signum == SIGTERM) { 1448 printf("\n\nSignal %d received, preparing to exit...\n", 1449 signum); 1450 printf("Error: Stats are wrong due to sudden signal!\n\n"); 1451 force_quit = true; 1452 } 1453 } 1454 1455 static inline uint16_t 1456 do_rx(struct lcore_info *li, uint16_t rx_port, uint16_t rx_queue) 1457 { 1458 uint16_t cnt = 0; 1459 cnt = rte_eth_rx_burst(rx_port, rx_queue, li->pkts, MAX_PKT_BURST); 1460 li->rx_pkts += cnt; 1461 return cnt; 1462 } 1463 1464 static inline void 1465 do_tx(struct lcore_info *li, uint16_t cnt, uint16_t tx_port, 1466 uint16_t tx_queue) 1467 { 1468 uint16_t nr_tx = 0; 1469 uint16_t i; 1470 1471 nr_tx = rte_eth_tx_burst(tx_port, tx_queue, li->pkts, cnt); 1472 li->tx_pkts += nr_tx; 1473 li->tx_drops += cnt - nr_tx; 1474 1475 for (i = nr_tx; i < cnt; i++) 1476 rte_pktmbuf_free(li->pkts[i]); 1477 } 1478 1479 /* 1480 * Method to convert numbers into pretty numbers that easy 1481 * to read. The design here is to add comma after each three 1482 * digits and set all of this inside buffer. 1483 * 1484 * For example if n = 1799321, the output will be 1485 * 1,799,321 after this method which is easier to read. 1486 */ 1487 static char * 1488 pretty_number(uint64_t n, char *buf) 1489 { 1490 char p[6][4]; 1491 int i = 0; 1492 int off = 0; 1493 1494 while (n > 1000) { 1495 sprintf(p[i], "%03d", (int)(n % 1000)); 1496 n /= 1000; 1497 i += 1; 1498 } 1499 1500 sprintf(p[i++], "%d", (int)n); 1501 1502 while (i--) 1503 off += sprintf(buf + off, "%s,", p[i]); 1504 buf[strlen(buf) - 1] = '\0'; 1505 1506 return buf; 1507 } 1508 1509 static void 1510 packet_per_second_stats(void) 1511 { 1512 struct lcore_info *old; 1513 struct lcore_info *li, *oli; 1514 int nr_lines = 0; 1515 int i; 1516 1517 old = rte_zmalloc("old", 1518 sizeof(struct lcore_info) * RTE_MAX_LCORE, 0); 1519 if (old == NULL) 1520 rte_exit(EXIT_FAILURE, "No Memory available!"); 1521 1522 memcpy(old, lcore_infos, 1523 sizeof(struct lcore_info) * RTE_MAX_LCORE); 1524 1525 while (!force_quit) { 1526 uint64_t total_tx_pkts = 0; 1527 uint64_t total_rx_pkts = 0; 1528 uint64_t total_tx_drops = 0; 1529 uint64_t tx_delta, rx_delta, drops_delta; 1530 char buf[3][32]; 1531 int nr_valid_core = 0; 1532 1533 sleep(1); 1534 1535 if (nr_lines) { 1536 char go_up_nr_lines[16]; 1537 1538 sprintf(go_up_nr_lines, "%c[%dA\r", 27, nr_lines); 1539 printf("%s\r", go_up_nr_lines); 1540 } 1541 1542 printf("\n%6s %16s %16s %16s\n", "core", "tx", "tx drops", "rx"); 1543 printf("%6s %16s %16s %16s\n", "------", "----------------", 1544 "----------------", "----------------"); 1545 nr_lines = 3; 1546 for (i = 0; i < RTE_MAX_LCORE; i++) { 1547 li = &lcore_infos[i]; 1548 oli = &old[i]; 1549 if (li->mode != LCORE_MODE_PKT) 1550 continue; 1551 1552 tx_delta = li->tx_pkts - oli->tx_pkts; 1553 rx_delta = li->rx_pkts - oli->rx_pkts; 1554 drops_delta = li->tx_drops - oli->tx_drops; 1555 printf("%6d %16s %16s %16s\n", i, 1556 pretty_number(tx_delta, buf[0]), 1557 pretty_number(drops_delta, buf[1]), 1558 pretty_number(rx_delta, buf[2])); 1559 1560 total_tx_pkts += tx_delta; 1561 total_rx_pkts += rx_delta; 1562 total_tx_drops += drops_delta; 1563 1564 nr_valid_core++; 1565 nr_lines += 1; 1566 } 1567 1568 if (nr_valid_core > 1) { 1569 printf("%6s %16s %16s %16s\n", "total", 1570 pretty_number(total_tx_pkts, buf[0]), 1571 pretty_number(total_tx_drops, buf[1]), 1572 pretty_number(total_rx_pkts, buf[2])); 1573 nr_lines += 1; 1574 } 1575 1576 memcpy(old, lcore_infos, 1577 sizeof(struct lcore_info) * RTE_MAX_LCORE); 1578 } 1579 } 1580 1581 static int 1582 start_forwarding(void *data __rte_unused) 1583 { 1584 int lcore = rte_lcore_id(); 1585 int stream_id; 1586 uint16_t cnt; 1587 struct lcore_info *li = &lcore_infos[lcore]; 1588 1589 if (!li->mode) 1590 return 0; 1591 1592 if (li->mode == LCORE_MODE_STATS) { 1593 printf(":: started stats on lcore %u\n", lcore); 1594 packet_per_second_stats(); 1595 return 0; 1596 } 1597 1598 while (!force_quit) 1599 for (stream_id = 0; stream_id < MAX_STREAMS; stream_id++) { 1600 if (li->streams[stream_id].rx_port == -1) 1601 continue; 1602 1603 cnt = do_rx(li, 1604 li->streams[stream_id].rx_port, 1605 li->streams[stream_id].rx_queue); 1606 if (cnt) 1607 do_tx(li, cnt, 1608 li->streams[stream_id].tx_port, 1609 li->streams[stream_id].tx_queue); 1610 } 1611 return 0; 1612 } 1613 1614 static void 1615 init_lcore_info(void) 1616 { 1617 int i, j; 1618 unsigned int lcore; 1619 uint16_t nr_port; 1620 uint16_t queue; 1621 int port; 1622 int stream_id = 0; 1623 int streams_per_core; 1624 int unassigned_streams; 1625 int nb_fwd_streams; 1626 nr_port = rte_eth_dev_count_avail(); 1627 1628 /* First logical core is reserved for stats printing */ 1629 lcore = rte_get_next_lcore(-1, 0, 0); 1630 lcore_infos[lcore].mode = LCORE_MODE_STATS; 1631 1632 /* 1633 * Initialize all cores 1634 * All cores at first must have -1 value in all streams 1635 * This means that this stream is not used, or not set 1636 * yet. 1637 */ 1638 for (i = 0; i < RTE_MAX_LCORE; i++) 1639 for (j = 0; j < MAX_STREAMS; j++) { 1640 lcore_infos[i].streams[j].tx_port = -1; 1641 lcore_infos[i].streams[j].rx_port = -1; 1642 lcore_infos[i].streams[j].tx_queue = -1; 1643 lcore_infos[i].streams[j].rx_queue = -1; 1644 lcore_infos[i].streams_nb = 0; 1645 } 1646 1647 /* 1648 * Calculate the total streams count. 1649 * Also distribute those streams count between the available 1650 * logical cores except first core, since it's reserved for 1651 * stats prints. 1652 */ 1653 nb_fwd_streams = nr_port * RXQ_NUM; 1654 if ((int)(nb_lcores - 1) >= nb_fwd_streams) 1655 for (i = 0; i < (int)(nb_lcores - 1); i++) { 1656 lcore = rte_get_next_lcore(lcore, 0, 0); 1657 lcore_infos[lcore].streams_nb = 1; 1658 } 1659 else { 1660 streams_per_core = nb_fwd_streams / (nb_lcores - 1); 1661 unassigned_streams = nb_fwd_streams % (nb_lcores - 1); 1662 for (i = 0; i < (int)(nb_lcores - 1); i++) { 1663 lcore = rte_get_next_lcore(lcore, 0, 0); 1664 lcore_infos[lcore].streams_nb = streams_per_core; 1665 if (unassigned_streams) { 1666 lcore_infos[lcore].streams_nb++; 1667 unassigned_streams--; 1668 } 1669 } 1670 } 1671 1672 /* 1673 * Set the streams for the cores according to each logical 1674 * core stream count. 1675 * The streams is built on the design of what received should 1676 * forward as well, this means that if you received packets on 1677 * port 0 queue 0 then the same queue should forward the 1678 * packets, using the same logical core. 1679 */ 1680 lcore = rte_get_next_lcore(-1, 0, 0); 1681 for (port = 0; port < nr_port; port++) { 1682 /* Create FWD stream */ 1683 for (queue = 0; queue < RXQ_NUM; queue++) { 1684 if (!lcore_infos[lcore].streams_nb || 1685 !(stream_id % lcore_infos[lcore].streams_nb)) { 1686 lcore = rte_get_next_lcore(lcore, 0, 0); 1687 lcore_infos[lcore].mode = LCORE_MODE_PKT; 1688 stream_id = 0; 1689 } 1690 lcore_infos[lcore].streams[stream_id].rx_queue = queue; 1691 lcore_infos[lcore].streams[stream_id].tx_queue = queue; 1692 lcore_infos[lcore].streams[stream_id].rx_port = port; 1693 lcore_infos[lcore].streams[stream_id].tx_port = port; 1694 stream_id++; 1695 } 1696 } 1697 1698 /* Print all streams */ 1699 printf(":: Stream -> core id[N]: (rx_port, rx_queue)->(tx_port, tx_queue)\n"); 1700 for (i = 0; i < RTE_MAX_LCORE; i++) 1701 for (j = 0; j < MAX_STREAMS; j++) { 1702 /* No streams for this core */ 1703 if (lcore_infos[i].streams[j].tx_port == -1) 1704 break; 1705 printf("Stream -> core id[%d]: (%d,%d)->(%d,%d)\n", 1706 i, 1707 lcore_infos[i].streams[j].rx_port, 1708 lcore_infos[i].streams[j].rx_queue, 1709 lcore_infos[i].streams[j].tx_port, 1710 lcore_infos[i].streams[j].tx_queue); 1711 } 1712 } 1713 1714 static void 1715 init_port(void) 1716 { 1717 int ret; 1718 uint16_t std_queue; 1719 uint16_t hairpin_queue; 1720 uint16_t port_id; 1721 uint16_t nr_ports; 1722 uint16_t nr_queues; 1723 struct rte_eth_hairpin_conf hairpin_conf = { 1724 .peer_count = 1, 1725 }; 1726 struct rte_eth_conf port_conf = { 1727 .rx_adv_conf = { 1728 .rss_conf.rss_hf = 1729 GET_RSS_HF(), 1730 } 1731 }; 1732 struct rte_eth_txconf txq_conf; 1733 struct rte_eth_rxconf rxq_conf; 1734 struct rte_eth_dev_info dev_info; 1735 1736 nr_queues = RXQ_NUM; 1737 if (hairpin_queues_num != 0) 1738 nr_queues = RXQ_NUM + hairpin_queues_num; 1739 1740 nr_ports = rte_eth_dev_count_avail(); 1741 if (nr_ports == 0) 1742 rte_exit(EXIT_FAILURE, "Error: no port detected\n"); 1743 1744 mbuf_mp = rte_pktmbuf_pool_create("mbuf_pool", 1745 TOTAL_MBUF_NUM, MBUF_CACHE_SIZE, 1746 0, MBUF_SIZE, 1747 rte_socket_id()); 1748 if (mbuf_mp == NULL) 1749 rte_exit(EXIT_FAILURE, "Error: can't init mbuf pool\n"); 1750 1751 for (port_id = 0; port_id < nr_ports; port_id++) { 1752 ret = rte_eth_dev_info_get(port_id, &dev_info); 1753 if (ret != 0) 1754 rte_exit(EXIT_FAILURE, 1755 "Error during getting device" 1756 " (port %u) info: %s\n", 1757 port_id, strerror(-ret)); 1758 1759 port_conf.txmode.offloads &= dev_info.tx_offload_capa; 1760 port_conf.rxmode.offloads &= dev_info.rx_offload_capa; 1761 1762 printf(":: initializing port: %d\n", port_id); 1763 1764 ret = rte_eth_dev_configure(port_id, nr_queues, 1765 nr_queues, &port_conf); 1766 if (ret < 0) 1767 rte_exit(EXIT_FAILURE, 1768 ":: cannot configure device: err=%d, port=%u\n", 1769 ret, port_id); 1770 1771 rxq_conf = dev_info.default_rxconf; 1772 for (std_queue = 0; std_queue < RXQ_NUM; std_queue++) { 1773 ret = rte_eth_rx_queue_setup(port_id, std_queue, NR_RXD, 1774 rte_eth_dev_socket_id(port_id), 1775 &rxq_conf, 1776 mbuf_mp); 1777 if (ret < 0) 1778 rte_exit(EXIT_FAILURE, 1779 ":: Rx queue setup failed: err=%d, port=%u\n", 1780 ret, port_id); 1781 } 1782 1783 txq_conf = dev_info.default_txconf; 1784 for (std_queue = 0; std_queue < TXQ_NUM; std_queue++) { 1785 ret = rte_eth_tx_queue_setup(port_id, std_queue, NR_TXD, 1786 rte_eth_dev_socket_id(port_id), 1787 &txq_conf); 1788 if (ret < 0) 1789 rte_exit(EXIT_FAILURE, 1790 ":: Tx queue setup failed: err=%d, port=%u\n", 1791 ret, port_id); 1792 } 1793 1794 /* Catch all packets from traffic generator. */ 1795 ret = rte_eth_promiscuous_enable(port_id); 1796 if (ret != 0) 1797 rte_exit(EXIT_FAILURE, 1798 ":: promiscuous mode enable failed: err=%s, port=%u\n", 1799 rte_strerror(-ret), port_id); 1800 1801 if (hairpin_queues_num != 0) { 1802 /* 1803 * Configure peer which represents hairpin Tx. 1804 * Hairpin queue numbers start after standard queues 1805 * (RXQ_NUM and TXQ_NUM). 1806 */ 1807 for (hairpin_queue = RXQ_NUM, std_queue = 0; 1808 hairpin_queue < nr_queues; 1809 hairpin_queue++, std_queue++) { 1810 hairpin_conf.peers[0].port = port_id; 1811 hairpin_conf.peers[0].queue = 1812 std_queue + TXQ_NUM; 1813 ret = rte_eth_rx_hairpin_queue_setup( 1814 port_id, hairpin_queue, 1815 NR_RXD, &hairpin_conf); 1816 if (ret != 0) 1817 rte_exit(EXIT_FAILURE, 1818 ":: Hairpin rx queue setup failed: err=%d, port=%u\n", 1819 ret, port_id); 1820 } 1821 1822 for (hairpin_queue = TXQ_NUM, std_queue = 0; 1823 hairpin_queue < nr_queues; 1824 hairpin_queue++, std_queue++) { 1825 hairpin_conf.peers[0].port = port_id; 1826 hairpin_conf.peers[0].queue = 1827 std_queue + RXQ_NUM; 1828 ret = rte_eth_tx_hairpin_queue_setup( 1829 port_id, hairpin_queue, 1830 NR_TXD, &hairpin_conf); 1831 if (ret != 0) 1832 rte_exit(EXIT_FAILURE, 1833 ":: Hairpin tx queue setup failed: err=%d, port=%u\n", 1834 ret, port_id); 1835 } 1836 } 1837 1838 ret = rte_eth_dev_start(port_id); 1839 if (ret < 0) 1840 rte_exit(EXIT_FAILURE, 1841 "rte_eth_dev_start:err=%d, port=%u\n", 1842 ret, port_id); 1843 1844 printf(":: initializing port: %d done\n", port_id); 1845 } 1846 } 1847 1848 int 1849 main(int argc, char **argv) 1850 { 1851 int ret; 1852 uint16_t port; 1853 struct rte_flow_error error; 1854 1855 ret = rte_eal_init(argc, argv); 1856 if (ret < 0) 1857 rte_exit(EXIT_FAILURE, "EAL init failed\n"); 1858 1859 force_quit = false; 1860 dump_iterations = false; 1861 rules_count = DEFAULT_RULES_COUNT; 1862 rules_batch = DEFAULT_RULES_BATCH; 1863 delete_flag = false; 1864 dump_socket_mem_flag = false; 1865 flow_group = DEFAULT_GROUP; 1866 1867 signal(SIGINT, signal_handler); 1868 signal(SIGTERM, signal_handler); 1869 1870 argc -= ret; 1871 argv += ret; 1872 if (argc > 1) 1873 args_parse(argc, argv); 1874 1875 init_port(); 1876 1877 nb_lcores = rte_lcore_count(); 1878 if (nb_lcores <= 1) 1879 rte_exit(EXIT_FAILURE, "This app needs at least two cores\n"); 1880 1881 1882 printf(":: Flows Count per port: %d\n\n", rules_count); 1883 1884 if (has_meter()) 1885 create_meter_profile(); 1886 rte_eal_mp_remote_launch(run_rte_flow_handler_cores, NULL, CALL_MAIN); 1887 1888 if (enable_fwd) { 1889 init_lcore_info(); 1890 rte_eal_mp_remote_launch(start_forwarding, NULL, CALL_MAIN); 1891 } 1892 if (has_meter() && delete_flag) 1893 destroy_meter_profile(); 1894 1895 RTE_ETH_FOREACH_DEV(port) { 1896 rte_flow_flush(port, &error); 1897 if (rte_eth_dev_stop(port) != 0) 1898 printf("Failed to stop device on port %u\n", port); 1899 rte_eth_dev_close(port); 1900 } 1901 printf("\nBye ...\n"); 1902 return 0; 1903 } 1904