numam/net/cat.cc
2023-03-15 18:43:37 -04:00

989 lines
27 KiB
C++

#include <atomic>
#include <cstdlib>
#include <ctime>
#include <fstream>
#include <random>
#include <vector>
#include <topo.h>
#include <rte_byteorder.h>
#include <rte_common.h>
#include <rte_config.h>
#include <rte_cycles.h>
#include <rte_eal.h>
#include <rte_ethdev.h>
#include <rte_ether.h>
#include <rte_launch.h>
#include <rte_lcore.h>
#include <rte_mbuf.h>
#include <unistd.h>
#include "ntr.h"
#include "gen.hh"
#include "net/netsup.hh"
#include "net/pkt.hh"
#include "nms.h"
constexpr static unsigned int BURST_SIZE = 32;
constexpr static unsigned int MAX_SLAVES = 32;
constexpr static unsigned int SLAVES_MAX_WAIT_MS = 1000;
struct datapt {
uint32_t epoch;
uint32_t valid;
uint64_t clt_hw_tx;
uint64_t clt_sw_tx;
uint64_t clt_hw_rx;
uint64_t clt_sw_rx;
uint64_t srv_hw_tx;
uint64_t srv_sw_tx;
uint64_t srv_hw_rx;
uint64_t srv_sw_rx;
};
constexpr static uint32_t STATE_WAIT = 0; // waiting for sending
constexpr static uint32_t STATE_SENT = 1; // we sent a packet
constexpr static uint32_t STATE_COMPLETE = 2; // we received everything
constexpr static uint32_t STATE_PKTLOSS = 3; // last packet sent was lost
struct options_t {
// parameters
unsigned int run_time { 5 };
unsigned int warmup_time { 3 };
char output[256] = "output.txt";
char ia_gen_str[256] = "fixed";
unsigned int target_qps { 0 };
unsigned int master_mode { 0 };
struct net_spec server_spec { };
cpuset_t cpu_set = CPUSET_T_INITIALIZER(0x2); // 2nd core
std::vector<struct net_spec *> slaves;
uint32_t pkt_loss_failure_threshold { 0 };
uint32_t pkt_loss_time_ms { UINT32_MAX };
int portid { 0 };
// states
struct net_spec s_host_spec { };
struct conn_spec s_host_conn {
.src = &s_host_spec, .dst = &server_spec, .dst_port = POU_PORT
};
unsigned int s_rxqid { 0 };
unsigned int s_txqid { 0 };
unsigned int s_socketid { 0 };
// for qps calculation
std::atomic<uint32_t> s_recved_pkts { 0 };
std::atomic<uint32_t> s_pkt_loss { 0 };
std::atomic<uint64_t> s_start_time { 0 };
std::atomic<uint64_t> s_end_time { 0 };
std::atomic<uint32_t> s_slave_qps { 0 };
std::atomic<uint32_t> s_slave_recved { 0 };
std::atomic<uint32_t> s_slave_loss { 0 };
uint32_t s_state { STATE_WAIT };
bool s_hwtimestamp { true };
Generator *s_iagen { nullptr };
std::vector<struct datapt *> s_data;
struct datapt *s_last_datapt { nullptr };
uint32_t s_epoch { 0 };
std::atomic<bool> s_stop { false };
std::atomic<uint32_t> s_record { 0 };
};
static struct options_t options;
static uint16_t
rx_add_timestamp(uint16_t port, uint16_t qidx __rte_unused,
struct rte_mbuf **pkts, uint16_t nb_pkts, uint16_t max_pkts __rte_unused,
void *_ __rte_unused)
{
uint64_t now = topo_uptime_ns();
struct pkt_hdr *pkt_data;
struct timespec ts { };
int ret;
if (options.s_state != STATE_SENT) {
return nb_pkts;
}
for (int i = 0; i < nb_pkts; i++) {
pkt_data = check_valid_packet(pkts[i],
&options.s_host_spec.mac_addr);
if (pkt_data == nullptr) {
ntr(NTR_DEP_USER1, NTR_LEVEL_DEBUG,
"rx_add_timestamp: ignoring invalid packet 0x%p.\n",
(void *)pkts[i]);
continue;
}
if (rte_be_to_cpu_16(pkt_data->type) == PKT_TYPE_PROBE_RESP) {
uint32_t epoch = rte_be_to_cpu_32(
((struct pkt_payload_epoch *)pkt_data->payload)
->epoch);
if (options.s_last_datapt != nullptr &&
options.s_last_datapt->epoch == epoch) {
if (options.s_hwtimestamp) {
if ((ret = rte_eth_timesync_read_rx_timestamp(
port, &ts, pkts[i]->timesync & 0x3)) ==
0) {
// has hw rx timestamp
options.s_last_datapt->clt_hw_rx =
ts.tv_sec * S2NS + ts.tv_nsec;
options.s_last_datapt->clt_sw_rx = now;
ntr(NTR_DEP_USER1, NTR_LEVEL_DEBUG,
"rx_add_timestamp: tagged packet %p with sw: %lu hw: %lu.\n",
(void *)pkts[i], now,
options.s_last_datapt->clt_hw_rx);
} else {
rte_exit(EXIT_FAILURE,
"rx_add_timestamp: packet %p not tagged - hw ts not "
"available - %d.\n",
(void *)pkts[i], ret);
}
} else {
options.s_last_datapt->clt_sw_rx = now;
options.s_last_datapt->clt_hw_rx = 0;
ntr(NTR_DEP_USER1, NTR_LEVEL_DEBUG,
"rx_add_timestamp: tagged packet %p with sw: %lu hw: (disabled).\n",
(void *)pkts[i], now);
}
} else {
ntr(NTR_DEP_USER1, NTR_LEVEL_WARNING,
"rx_add_timestamp: packet %p epoch %d != last epoch %d.\n",
(void *)pkts[i], epoch,
options.s_last_datapt->epoch);
}
} else {
ntr(NTR_DEP_USER1, NTR_LEVEL_DEBUG,
"rx_add_timestamp: packet %p not tagged - type %d.\n",
(void *)pkts[i], rte_be_to_cpu_16(pkt_data->type));
}
}
return nb_pkts;
}
static uint16_t
tx_add_timestamp(uint16_t port __rte_unused, uint16_t qidx __rte_unused,
struct rte_mbuf **pkts, uint16_t nb_pkts, void *_ __rte_unused)
{
uint64_t now = topo_uptime_ns();
struct pkt_hdr *pkt_data;
// if (options.s_state != STATE_SENT) {
// return nb_pkts;
// }
for (int i = 0; i < nb_pkts; i++) {
pkt_data = check_valid_packet(pkts[i],
&options.s_host_spec.mac_addr);
if (pkt_data == nullptr) {
ntr(NTR_DEP_USER1, NTR_LEVEL_DEBUG,
"tx_add_timestamp: ignoring invalid packet 0x%p.\n",
(void *)pkts[i]);
continue;
}
if (rte_be_to_cpu_16(pkt_data->type) == PKT_TYPE_PROBE) {
uint32_t epoch = rte_be_to_cpu_32(
((struct pkt_payload_epoch *)pkt_data->payload)
->epoch);
if (options.s_last_datapt == nullptr ||
epoch != options.s_last_datapt->epoch) {
rte_exit(EXIT_FAILURE,
"tx_add_timestamp: packet epoch %d != last epoch %d\n",
epoch, options.s_last_datapt->epoch);
}
options.s_last_datapt->clt_sw_tx = now;
ntr(NTR_DEP_USER1, NTR_LEVEL_DEBUG,
"tx_add_timestamp: tagged packet %p with sw: %lu.\n",
(void *)pkts[i], now);
} else {
ntr(NTR_DEP_USER1, NTR_LEVEL_DEBUG,
"tx_add_timestamp: packet %p not tagged - type %d.\n",
(void *)pkts[i], pkt_data->type);
}
}
return nb_pkts;
}
// returns 0 on success
static void
send_all_slaves(uint16_t type)
{
struct rte_mbuf *tx_bufs[MAX_SLAVES];
//struct rte_eth_stats stats;
struct conn_spec cspec;
cspec.src = &options.s_host_spec;
cspec.dst_port = DEFAULT_RAT_PORT;
cspec.src_port = DEFAULT_RAT_PORT;
// send all clients SYNC
for (unsigned int i = 0; i < options.slaves.size(); i++) {
struct pkt_hdr *hdr;
cspec.dst = options.slaves.at(i);
if (alloc_pkt_hdr(mempool_get(options.s_socketid), type, &cspec, 0,
&tx_bufs[i], &hdr) != 0) {
rte_exit(EXIT_FAILURE, "failed to alloc packet\n");
}
}
// if (rte_eth_stats_get(options.portid, &stats) != 0 ) {
// rte_exit(EXIT_FAILURE, "failed!");
// }
// printf("send_all_slaves: ipackets %lu, opackets %lu, ierrors %lu, oerrors %lu\n", stats.ipackets, stats.opackets, stats.ierrors, stats.oerrors);
if (rte_eth_tx_burst(options.portid, options.s_txqid, tx_bufs,
options.slaves.size()) != options.slaves.size()) {
rte_exit(EXIT_FAILURE, "failed to send some packets\n");
}
}
// sizeof mbuf must >= MAX_SLAVES
// this function fills up to #slave
static void
wait_for_slaves(uint16_t etype, struct rte_mbuf **out)
{
struct rte_mbuf *tx_bufs[MAX_SLAVES];
bool stop = false;
const uint64_t start = topo_uptime_ns();
std::vector<struct rte_ether_addr *> recved;
uint32_t tot = 0;
while (!stop) {
uint64_t now = topo_uptime_ns();
const uint16_t nb_rx = rte_eth_rx_burst(options.portid,
options.s_rxqid, tx_bufs, MAX_SLAVES);
if (nb_rx > 0) {
for (unsigned int i = 0; i < nb_rx; i++) {
struct pkt_hdr *each = check_valid_packet(
tx_bufs[i], &options.s_host_spec.mac_addr);
uint16_t type;
if (each == nullptr) {
ntr(NTR_DEP_USER1, NTR_LEVEL_DEBUG,
"wait_for_slaves: ignoring invalid packet %p.\n",
(void *)tx_bufs[i]);
goto end_loop;
}
type = rte_be_to_cpu_16(each->type);
if (type == etype) {
bool invalid = true;
// check if it is from one of our
// clients
for (auto eaddr : options.slaves) {
if (rte_is_same_ether_addr(
&eaddr->mac_addr,
&each->eth_hdr
.src_addr)) {
invalid = false;
break;
}
}
if (invalid) {
// received invalid packet from
// unregistered slave
ntr(NTR_DEP_USER1,
NTR_LEVEL_WARNING,
"wait_for_slaves: invalid packet %p from unregistered slave\n.",
tx_bufs[i]);
goto end_loop;
}
invalid = false;
// check if we have already received the
// same packet from the mac addr
for (auto eaddr : recved) {
if (rte_is_same_ether_addr(
eaddr,
&each->eth_hdr
.src_addr)) {
invalid = true;
break;
}
}
if (invalid) {
// received invalid packet from
// the same slave
ntr(NTR_DEP_USER1,
NTR_LEVEL_WARNING,
"wait_for_slaves: invalid packet %p - duplicated\n.",
tx_bufs[i]);
goto end_loop;
}
recved.push_back(
&each->eth_hdr.src_addr);
if (recved.size() ==
options.slaves.size()) {
stop = true;
}
if (out != nullptr) {
out[tot] = tx_bufs[i];
tot++;
// don't free this packet
continue;
}
} else {
ntr(NTR_DEP_USER1, NTR_LEVEL_DEBUG,
"wait_for_slaves: ignoring invalid packet %p type %d.\n",
(void *)tx_bufs[i], type);
}
end_loop:
rte_pktmbuf_free(tx_bufs[i]);
}
}
// struct rte_eth_stats stats;
// if (rte_eth_stats_get(options.portid, &stats) != 0 ) {
// rte_exit(EXIT_FAILURE, "failed!");
// }
//printf("wait_slaves <AFTER>: ipackets %lu, opackets %lu, ierrors %lu, oerrors %lu\n", stats.ipackets, stats.opackets, stats.ierrors, stats.oerrors);
if (now - start > SLAVES_MAX_WAIT_MS * MS2NS) {
rte_exit(EXIT_FAILURE,
"cat: waiting for too long %d. I QUIT!!", etype);
}
}
}
static void
pkt_loop()
{
struct rte_mbuf *tx_buf;
struct rte_mbuf *rx_bufs[BURST_SIZE];
struct pkt_hdr *pkt_data;
rdport_generator port_gen(MIN_RANDOM_PORT);
bool read_tx = true;
bool recv_stat = true;
bool recv_resp = true;
if (rte_eth_dev_socket_id(options.portid) > 0 &&
rte_eth_dev_socket_id(options.portid) != (int)rte_socket_id()) {
ntr(NTR_DEP_USER1, NTR_LEVEL_WARNING,
"locore_main: WARNING, port %d is on remote NUMA node to "
"polling thread.\n\tPerformance will "
"not be optimal.\n",
options.portid);
}
uint64_t next_ts = topo_uptime_ns();
uint64_t last_send_ts = next_ts;
bool is_last_pkt_lost = false;
uint32_t num_cts_pkt_lost = 0;
while (!options.s_stop.load()) {
uint64_t now = topo_uptime_ns();
// always pop incoming packets
const uint16_t nb_rx = rte_eth_rx_burst(options.portid,
options.s_rxqid, rx_bufs, BURST_SIZE);
if (nb_rx > 0) {
for (int i = 0; i < nb_rx; i++) {
if (options.s_state != STATE_SENT) {
// only need to process packets after we
// sent one
rte_pktmbuf_free(rx_bufs[i]);
continue;
}
struct pkt_hdr *each = check_valid_packet(
rx_bufs[i], &options.s_host_spec.mac_addr);
if (each == nullptr) {
ntr(NTR_DEP_USER1, NTR_LEVEL_DEBUG,
"locore_main: ignoring invalid packet %p.\n",
(void *)rx_bufs[i]);
rte_pktmbuf_free(rx_bufs[i]);
continue;
}
uint16_t type = rte_be_to_cpu_16(each->type);
NTR_PKT(NTR_DEP_USER1, NTR_LEVEL_DEBUG, each,
"locore_main: received packet %p ", each);
struct pkt_payload_epoch *pld_epoch;
struct pkt_payload_stat *pld_stat;
uint32_t epoch;
switch (type) {
case PKT_TYPE_PROBE_RESP:
pld_epoch = (struct pkt_payload_epoch *)
each->payload;
epoch = rte_be_to_cpu_32(
pld_epoch->epoch);
ntr(NTR_DEP_USER1, NTR_LEVEL_DEBUG, "lcore_main: PROBE_RESP received packet %p epoch %d\n", each, epoch);
if (options.s_last_datapt == nullptr ||
epoch !=
options.s_last_datapt->epoch) {
ntr(NTR_DEP_USER1,
NTR_LEVEL_WARNING,
"locore_main: packet %p epoch %d doesn't match datapt %d.\n",
(void *)rx_bufs[i], epoch,
options.s_last_datapt
->epoch);
break;
}
recv_resp = true;
break;
case PKT_TYPE_STAT:
pld_stat = (struct pkt_payload_stat *)
each->payload;
epoch = rte_be_to_cpu_32(
pld_stat->epoch);
if (options.s_last_datapt == nullptr ||
epoch !=
options.s_last_datapt->epoch) {
ntr(NTR_DEP_USER1,
NTR_LEVEL_WARNING,
"locore_main: packet %p epoch %d doesn't match datapt %d.\n",
(void *)rx_bufs[i], epoch,
options.s_last_datapt
->epoch);
break;
}
options.s_last_datapt->srv_hw_tx =
rte_be_to_cpu_64(pld_stat->hw_tx);
options.s_last_datapt->srv_hw_rx =
rte_be_to_cpu_64(pld_stat->hw_rx);
options.s_last_datapt->srv_sw_tx =
rte_be_to_cpu_64(pld_stat->sw_tx);
options.s_last_datapt->srv_sw_rx =
rte_be_to_cpu_64(pld_stat->sw_rx);
recv_stat = true;
is_last_pkt_lost = false;
break;
default:
ntr(NTR_DEP_USER1, NTR_LEVEL_DEBUG,
"locore_main: ignoring packet %p with unknown type %d.\n",
(void *)rx_bufs[i], type);
}
rte_pktmbuf_free(rx_bufs[i]);
}
}
if (options.s_state == STATE_SENT) {
// check if hw tx ts is read
if (!read_tx) {
int ret;
struct timespec ts;
if (options.s_hwtimestamp) {
if ((ret = rte_eth_timesync_read_tx_timestamp(
options.portid, &ts)) == 0) {
ntr(NTR_DEP_USER1, NTR_LEVEL_DEBUG,
"locore_main: read hw tx timestamp %lu.\n",
(ts.tv_nsec + ts.tv_sec * S2NS));
options.s_last_datapt->clt_hw_tx =
ts.tv_nsec + ts.tv_sec * S2NS;
read_tx = true;
}
} else {
options.s_last_datapt->clt_hw_tx = 0;
read_tx = true;
}
}
if (read_tx && recv_resp && recv_stat) {
options.s_state = STATE_COMPLETE;
} else {
// check packet loss
if (now - last_send_ts >
options.pkt_loss_time_ms * MS2NS) {
if (is_last_pkt_lost) {
num_cts_pkt_lost++;
} else {
is_last_pkt_lost = true;
num_cts_pkt_lost = 1;
}
ntr(NTR_DEP_USER1, NTR_LEVEL_DEBUG,
"locore_main: packet loss: waiting too long for epoch %d. %d in a row.\n",
options.s_last_datapt->epoch,
num_cts_pkt_lost);
delete options.s_last_datapt;
options.s_last_datapt = nullptr;
options.s_state = STATE_PKTLOSS;
options.s_pkt_loss.fetch_add(1);
if (num_cts_pkt_lost >
options
.pkt_loss_failure_threshold) {
rte_exit(EXIT_FAILURE,
"too many continuous packet loss detected\n");
}
}
}
}
if (options.s_state == STATE_COMPLETE ||
options.s_state == STATE_PKTLOSS ||
options.s_state == STATE_WAIT) {
if (options.s_state == STATE_COMPLETE) {
options.s_data.push_back(options.s_last_datapt);
ntr(NTR_DEP_USER1, NTR_LEVEL_DEBUG,
"locore_main: datapt for epoch %d dump:\n"
" Valid: %d\n"
" client TX HW: %lu\n"
" client TX SW: %lu\n"
" client RX HW: %lu\n"
" client RX SW: %lu\n"
" server TX HW: %lu\n"
" server TX SW: %lu\n"
" server RX HW: %lu\n"
" server RX SW: %lu\n\n",
options.s_last_datapt->epoch,
options.s_last_datapt->valid,
options.s_last_datapt->clt_hw_tx,
options.s_last_datapt->clt_sw_tx,
options.s_last_datapt->clt_hw_rx,
options.s_last_datapt->clt_sw_rx,
options.s_last_datapt->srv_hw_tx,
options.s_last_datapt->srv_sw_tx,
options.s_last_datapt->srv_hw_rx,
options.s_last_datapt->srv_sw_rx);
options.s_recved_pkts.fetch_add(1);
options.s_last_datapt = nullptr;
}
options.s_state = STATE_WAIT;
if (now >= next_ts) {
struct pkt_payload_epoch *pld_epoch;
uint32_t epoch;
next_ts += (int)(options.s_iagen->generate() *
S2NS);
options.s_host_conn.src_port = port_gen.next();
if (alloc_pkt_hdr(mempool_get(options.s_socketid),
PKT_TYPE_PROBE, &options.s_host_conn, 0,
&tx_buf, &pkt_data) != 0) {
rte_exit(EXIT_FAILURE,
"failed to alloc probe packet.\n");
}
epoch = options.s_epoch;
options.s_epoch++;
pld_epoch = (struct pkt_payload_epoch *)
pkt_data->payload;
pld_epoch->epoch = rte_cpu_to_be_32(epoch);
options.s_last_datapt = new struct datapt;
options.s_last_datapt->epoch = epoch;
options.s_last_datapt->valid =
options.s_record.load();
last_send_ts = now;
ntr(NTR_DEP_USER1, NTR_LEVEL_DEBUG,
"locore_main: sending packet 0x%p with epoch %d\n",
(void *)tx_buf, epoch);
const uint16_t nb_tx =
rte_eth_tx_burst(options.portid,
options.s_txqid, &tx_buf, 1);
if (nb_tx != 1) {
rte_exit(EXIT_FAILURE,
"failed to send packet 0x%p, epoch %d\n",
(void *)tx_buf, epoch);
}
rte_pktmbuf_free(tx_buf);
read_tx = false;
recv_resp = false;
recv_stat = false;
options.s_state = STATE_SENT;
}
}
}
}
static int
locore_main(void *tif __rte_unused)
{
struct rte_mbuf *mbufs[MAX_SLAVES];
uint32_t core_id = rte_lcore_id();
ntr(NTR_DEP_USER1, NTR_LEVEL_INFO, "locore_main: core %d running...\n",
core_id);
if (options.master_mode == 1) {
ntr(NTR_DEP_USER1, NTR_LEVEL_DEBUG,
"locore_main: sending SYNC ...\n");
send_all_slaves(PKT_TYPE_SYNC);
ntr(NTR_DEP_USER1, NTR_LEVEL_DEBUG,
"locore_main: waiting for SYNC_ACK ...\n");
wait_for_slaves(PKT_TYPE_SYNC_ACK, nullptr);
}
options.s_start_time.store(topo_uptime_ns());
pkt_loop();
options.s_end_time.store(topo_uptime_ns());
if (options.master_mode == 1) {
ntr(NTR_DEP_USER1, NTR_LEVEL_DEBUG,
"locore_main: sending FIN ...\n");
send_all_slaves(PKT_TYPE_FIN);
ntr(NTR_DEP_USER1, NTR_LEVEL_DEBUG,
"locore_main: waiting for FIN_ACK ...\n");
wait_for_slaves(PKT_TYPE_FIN_ACK, mbufs);
// aggregate slave QPS
for (unsigned int i = 0; i < options.slaves.size(); i++) {
// these packets already underwent validity check in
// wait_for_slaves
auto pkt_hdr = rte_pktmbuf_mtod(mbufs[i],
struct pkt_hdr *);
auto pld_qps = (struct pkt_payload_qps *)
pkt_hdr->payload;
uint32_t qps = rte_be_to_cpu_32(pld_qps->qps);
uint32_t recved = rte_be_to_cpu_32(
pld_qps->recved_pkts);
uint32_t loss = rte_be_to_cpu_32(pld_qps->lost_pkts);
ntr(NTR_DEP_USER1, NTR_LEVEL_DEBUG,
"locore_main: received qps %d from client %d\n",
qps, i);
options.s_slave_qps.fetch_add(qps);
options.s_slave_loss.fetch_add(loss);
options.s_slave_recved.fetch_add(recved);
rte_pktmbuf_free(mbufs[i]);
}
}
ntr(NTR_DEP_USER1, NTR_LEVEL_INFO, "locore_main: exited\n");
return 0;
}
static void
dump_options()
{
ntr(NTR_DEP_USER1, NTR_LEVEL_INFO,
"Configuration:\n"
" verbosity = +%d\n"
" run time = %d\n"
" warmup time = %d\n"
" output file = %s\n"
" number of threads = %d\n"
" interarrival dist = %s\n"
" target qps = %d\n"
" host IP = 0x%x\n"
" pkt loss time = %u\n"
" pkt loss failure threshold = %u\n"
" portid = %d\n",
ntr_get_level(NTR_DEP_USER1) - NTR_LEVEL_WARNING, options.run_time,
options.warmup_time, options.output, CPU_COUNT(&options.cpu_set),
options.ia_gen_str, options.target_qps, options.s_host_spec.ip,
options.pkt_loss_time_ms, options.pkt_loss_failure_threshold,
options.portid);
for (auto slave : options.slaves) {
ntr(NTR_DEP_USER1, NTR_LEVEL_INFO,
" slave = 0x%x@%x:%x:%x:%x:%x:%x\n", slave->ip,
slave->mac_addr.addr_bytes[0],
slave->mac_addr.addr_bytes[1],
slave->mac_addr.addr_bytes[2],
slave->mac_addr.addr_bytes[3],
slave->mac_addr.addr_bytes[4],
slave->mac_addr.addr_bytes[5]);
}
}
static void
usage()
{
fprintf(stdout,
"Usage:\n"
" -v(vv): verbose mode\n"
" -s: server net spec\n"
" -S: slave(rat)'s net spec (also turns on master mode)\n"
" -t: run time\n"
" -T: warmup time\n"
" -h: display the information\n"
" -o: output filename\n"
" -A: affinity mask\n"
" -i: inter-arrival time distribution\n"
" -q: target qps\n"
" -H: host net spec\n"
" -L: pkt loss failure threshold\n"
" -l: pkt loss time threshold\n");
}
int
main(int argc, char *argv[])
{
std::ofstream log_file;
bool has_host_spec = false;
ntr_init();
// init dpdk
int ret = rte_eal_init(argc, argv);
if (ret < 0) {
rte_exit(EXIT_FAILURE, "rte_eal_init failed!\n");
}
argc -= ret;
argv += ret;
// set warning level
ntr_set_level(NTR_DEP_USER1, NTR_LEVEL_WARNING);
{
int c;
// parse arguments
struct net_spec *ns;
while ((c = getopt(argc, argv, "vs:S:t:T:ho:A:i:q:H:L:l:p:")) !=
-1) {
switch (c) {
case 'v':
ntr_set_level(NTR_DEP_USER1,
ntr_get_level(NTR_DEP_USER1) + 1);
break;
case 's':
if (str_to_netspec(optarg,
&options.server_spec) != 0) {
rte_exit(EXIT_FAILURE,
"invalid server net spec.\n");
}
break;
case 'S':
ns = new struct net_spec;
if (str_to_netspec(optarg, ns) != 0) {
rte_exit(EXIT_FAILURE,
"invalid client net spec\n");
}
options.slaves.push_back(ns);
options.master_mode = 1;
if (options.slaves.size() > MAX_SLAVES) {
rte_exit(EXIT_FAILURE,
"too many rats.\n");
}
break;
case 't':
options.run_time = strtol(optarg, nullptr, 10);
break;
case 'T':
options.warmup_time = strtol(optarg, nullptr,
10);
break;
case 'h':
usage();
rte_exit(EXIT_SUCCESS, "\n");
case 'o':
strncpy(options.output, optarg,
sizeof(options.output) - 1);
break;
case 'A':
cpulist_to_cpuset(optarg, &options.cpu_set);
break;
case 'i':
strncpy(options.ia_gen_str, optarg,
sizeof(options.ia_gen_str) - 1);
break;
case 'q':
options.target_qps = strtoul(optarg, nullptr,
10);
break;
case 'H':
has_host_spec = true;
if (str_to_netspec(optarg,
&options.s_host_spec) != 0) {
rte_exit(EXIT_FAILURE,
"invalid host net spec.\n");
}
break;
case 'L':
options.pkt_loss_failure_threshold =
strtoul(optarg, nullptr, 10);
break;
case 'l':
options.pkt_loss_time_ms = strtoul(optarg,
nullptr, 10);
if (options.pkt_loss_time_ms == 0) {
options.pkt_loss_time_ms = UINT32_MAX;
}
break;
case 'p':
options.portid = strtol(optarg, nullptr, 10);
break;
default:
usage();
rte_exit(EXIT_FAILURE, "unknown argument: %c\n",
c);
}
}
}
if (!has_host_spec) {
rte_exit(EXIT_FAILURE, "must specify host IP\n");
}
// init libtopo
if (topo_init(ntr_get_level(NTR_DEP_USER1) - NTR_LEVEL_WARNING) !=
0) {
rte_exit(EXIT_FAILURE, "libtopo init failed!\n");
}
// init nms
if (nms_init(ntr_get_level(NTR_DEP_USER1) - NTR_LEVEL_WARNING) != 0) {
rte_exit(EXIT_FAILURE, "failed to init libnms!\n");
}
if (CPU_COUNT(&options.cpu_set) != 1) {
rte_exit(EXIT_FAILURE, "must specify exactly one core\n");
}
int core_id = CPU_FFS(&options.cpu_set) - 1;
dump_options();
// configure memory and port
struct port_conf pconf;
struct device_conf dconf;
struct mem_conf mconf;
portconf_get(options.portid, &pconf);
if (!pconf.timesync) {
options.s_hwtimestamp = false;
ntr(NTR_DEP_USER1, NTR_LEVEL_WARNING,
"main: timesync disabled. hw timestamp unavailable.\n ");
}
if (CPU_COUNT(&options.cpu_set) > 1) {
int ffs = CPU_FFS(&options.cpu_set);
CPU_ZERO(&options.cpu_set);
CPU_SET(ffs - 1, &options.cpu_set);
ntr(NTR_DEP_USER1, NTR_LEVEL_WARNING, "cat only supports one thread, using only core %d.\n", ffs - 1);
}
dconf.mtu = MAX_STANDARD_MTU;
CPU_COPY(&options.cpu_set, &dconf.core_affinity);
dconf.portid = options.portid;
dconf.rss_hf = pconf.rss_hf;
dconf.rx_offloads = pconf.rxoffload;
dconf.tx_offloads = pconf.txoffload;
dconf.timesync = pconf.timesync;
dconf.rx_fn = rx_add_timestamp;
dconf.rx_user = nullptr;
dconf.rx_ring_sz = 2048;
dconf.tx_fn = tx_add_timestamp;
dconf.tx_user = nullptr;
dconf.tx_ring_sz = 2048;
mconf.cache_size = 64;
mconf.priv_size = 0;
mconf.num_elements = 4096;
mconf.data_room_size = RTE_MBUF_DEFAULT_BUF_SIZE + MAX_STANDARD_MTU;
mconf.max_pools = -1;
dpdk_init(&dconf, &mconf);
if (rte_eth_macaddr_get(options.portid,
&options.s_host_spec.mac_addr) != 0) {
rte_exit(EXIT_FAILURE, "cannot get mac address of port %d\n",
options.portid);
}
ntr(NTR_DEP_USER1, NTR_LEVEL_INFO,
"Configured port %d with mac addr %x:%x:%x:%x:%x:%x\n",
options.portid, options.s_host_spec.mac_addr.addr_bytes[0],
options.s_host_spec.mac_addr.addr_bytes[1],
options.s_host_spec.mac_addr.addr_bytes[2],
options.s_host_spec.mac_addr.addr_bytes[3],
options.s_host_spec.mac_addr.addr_bytes[4],
options.s_host_spec.mac_addr.addr_bytes[5]);
// create default generator
options.s_iagen = createGenerator(options.ia_gen_str);
if (options.s_iagen == nullptr) {
rte_exit(EXIT_FAILURE, "invalid generator string %s\n",
options.ia_gen_str);
}
options.s_iagen->set_lambda((double)options.target_qps);
// open log file for writing
log_file.open(options.output, std::ofstream::out);
if (!log_file) {
rte_exit(EXIT_FAILURE, "failed to open log file %s\n",
options.output);
}
sleep(INIT_DELAY);
ntr(NTR_DEP_USER1, NTR_LEVEL_INFO,
"main: launching thread on core %d\n", core_id);
if (rte_eal_remote_launch(locore_main, nullptr, core_id) != 0) {
rte_exit(EXIT_FAILURE, "failed to launch function on locore\n");
}
// XXX: poor man's timer
uint32_t second = 0;
while (true) {
if (second >= options.warmup_time) {
options.s_record.store(1);
}
if (second >= options.run_time + options.warmup_time) {
options.s_stop.store(true);
break;
}
usleep(S2US);
second++;
}
if (rte_eal_wait_lcore(core_id) < 0)
rte_exit(EXIT_FAILURE, "failed to wait for job completion\n");
// calculate QPS
uint32_t qps = (double)options.s_recved_pkts.load() /
(((double)(options.s_end_time.load() -
options.s_start_time.load()) /
(double)S2NS));
qps += options.s_slave_qps.load();
// dump stats
log_file << qps << ',' << options.s_recved_pkts.load() << ','
<< options.s_pkt_loss.load() << ','
<< options.s_slave_recved.load() << ','
<< options.s_slave_loss.load() << std::endl;
for (auto it : options.s_data) {
if (it->valid) {
log_file << it->clt_sw_rx << ',' << it->clt_sw_tx << ','
<< it->clt_hw_rx << ',' << it->clt_hw_tx << ','
<< it->srv_sw_rx << ',' << it->srv_sw_tx << ','
<< it->srv_hw_rx << ',' << it->srv_hw_tx
<< std::endl;
}
delete it;
}
log_file.close();
ntr(NTR_DEP_USER1, NTR_LEVEL_INFO,
"qps = %d, recved = %d, loss = %d, slave recved = %d, slave loss = %d\n",
qps, options.s_recved_pkts.load(), options.s_pkt_loss.load(),
options.s_slave_recved.load(), options.s_slave_loss.load());
// clean up
dpdk_cleanup(&dconf);
return 0;
}