// (c) 2023 Fraunhofer IIS, Maximilian Engelhardt #include #include "common.h" #include "rte_pause.h" #include "rte_ethdev.h" #include "rte_byteorder.h" #include "rte_byteorder_64.h" #include "rte_malloc.h" const size_t BURST_SIZE = 16; // Packets const size_t TARGET_RATE = 10ul * 1000ul * 1000ul * 1000ul; const size_t PAYLOAD_LEN = 8000; const size_t NIC_CLOCK_RATE = 1000ul * 1000ul * 1000ul; const size_t NUM_TX_THREADS = 1; uint64_t total_bytes_tx = 0; struct worker_arg { uint16_t queue_id; uint64_t start_time; double tx_shift_ratio; }; int worker(void* arg_) { struct sched_param sp; memset( &sp, 0, sizeof(sp) ); sp.sched_priority = 99; ASSERTP(sched_setscheduler( 0, SCHED_FIFO, &sp )); auto arg = (worker_arg*)arg_; uint64_t nic_delay_packet = NIC_CLOCK_RATE * PAYLOAD_LEN / (TARGET_RATE/NUM_TX_THREADS); uint64_t nic_delay_burst = nic_delay_packet * BURST_SIZE; uint64_t nic_clock_time = 0; uint64_t next_burst_nic = arg->start_time; uint64_t next_packet_pp_timestamp = next_burst_nic + 1000*1000; next_packet_pp_timestamp += static_cast((double)nic_delay_packet*arg->tx_shift_ratio); uint64_t stream_pos = 0; rte_mbuf* packets[BURST_SIZE]; uint16_t queue_id = arg->queue_id; while (run) { rte_eth_read_clock(0, &nic_clock_time); while (next_burst_nic > nic_clock_time && run) { rte_pause(); rte_eth_read_clock(0, &nic_clock_time); } auto r = rte_pktmbuf_alloc_bulk(mbuf_pool, packets, BURST_SIZE); ASSERT(r == 0, "MBuf pool drained!"); auto first_ts_in_burst = next_packet_pp_timestamp; for (auto packet : packets) { auto data = rte_pktmbuf_append(packet, PAYLOAD_LEN); auto header = (Header*)rte_pktmbuf_prepend(packet, sizeof(Header)); header->stream_pos = stream_pos; header->stream_id = queue_id; auto vlan_header = (struct rte_vlan_hdr *)rte_pktmbuf_prepend(packet,sizeof(struct rte_vlan_hdr)); vlan_header->vlan_tci = rte_cpu_to_be_16(11); vlan_header->eth_proto = rte_cpu_to_be_16(0x0811); auto ether_header = (struct rte_ether_hdr *)rte_pktmbuf_prepend(packet,sizeof(struct rte_ether_hdr)); ether_header->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN); ether_header->src_addr = {0,0,0,0,0,0}; ether_header->dst_addr = {0xff,0xff,0xff,0xff,0xff,0xff}; auto timestamp = RTE_MBUF_DYNFIELD(packet, hwts_dynfield_offset, int64_t *); *timestamp = (int64_t)next_packet_pp_timestamp; packet->ol_flags |= hwts_dynflag_tx_mask; stream_pos += PAYLOAD_LEN; next_packet_pp_timestamp += nic_delay_packet; } rte_eth_read_clock(0, &nic_clock_time); ASSERT(first_ts_in_burst > nic_clock_time + 50*1000, "TX queue slack target missed!"); int packets_sent = rte_eth_tx_burst(0, queue_id, packets, BURST_SIZE); ASSERT(packets_sent == BURST_SIZE, "TX ring overflow!"); total_bytes_tx += BURST_SIZE*PAYLOAD_LEN; next_burst_nic += nic_delay_burst; } } int main(int argc, char *argv[]) { init_dpdk(&argc, &argv); uint64_t start_time = 0; rte_eth_read_clock(0, &start_time); start_time += 1000ul*1000ul*1000ul; struct rte_eth_dev *dev; worker_arg args[NUM_TX_THREADS]; uint lcore_id = -1; for (int i = 0; i < NUM_TX_THREADS; ++i) { args[i] = {.queue_id = static_cast(i), .start_time=start_time, .tx_shift_ratio=(1.0/NUM_TX_THREADS)*i}; lcore_id = rte_get_next_lcore(lcore_id, true, false); rte_eal_remote_launch(&worker, &args[i], lcore_id); } uint64_t xstats_wander_id; uint64_t pp_wander=0, pp_wander_max=0; rte_eth_xstats_get_id_by_name(0, "tx_pp_wander", &xstats_wander_id); int i=0; while (run) { rte_delay_ms(1000); printf("=== Total bytes sent %lu - running for %d seconds, %lu max tx_pp_wander\r\n", total_bytes_tx, i++, pp_wander_max); print_xstats(); rte_eth_xstats_get_by_id(0, &xstats_wander_id, &pp_wander, 1); if (pp_wander_max < pp_wander) pp_wander_max = pp_wander; } return 0; }