989 lines
27 KiB
C++
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;
|
|
} |