ebert: Ethernet Bit Error Rate Tester
Created
September 12, 2011 21:37
-
-
Save nfarring/1212526 to your computer and use it in GitHub Desktop.
ebert: Ethernet Bit Error Rate Tester
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* <features.h> | |
* __STRICT_ANSI__ 1 | |
* _ISOC9X_SOURCE 1 | |
* _POSIX_SOURCE 1 | |
* _POSIX_C_SOURCE 199506L | |
* _XOPEN_SOURCE 500 | |
* _XOPEN_SOURCE_EXTENDED 1 | |
* _LARGEFILE_SOURCE 1 | |
* _BSD_SOURCE 1 | |
* _SVID_SOURCE 1 | |
*/ | |
#define _GNU_SOURCE 1 | |
#include <assert.h> // assert | |
#include <stdint.h> // uint16_t, uint64_t | |
#include <stdio.h> // printf, sprintf | |
#include <stdlib.h> // exit | |
#include <string.h> // memset, strncpy | |
#include <endian.h> // htobe16, htobe64, be16toh, be64toh | |
#include <errno.h> // errno, EWOULDBLOCK | |
#include <net/if.h> // NETDEVICE(7), struct ifr | |
#include <netpacket/packet.h> // PACKET(7), struct sockaddr_ll | |
#include <sys/ioctl.h> // ioctl | |
#include <sys/socket.h> // bind, socket | |
#include <sys/select.h> // pselect | |
#include <time.h> // TIME(2) | |
#include <gmp.h> // http://gmplib.org/ | |
#include "time.h" | |
#include "profile.h" | |
//// CONSTANT DEFINITIONS | |
/* | |
* We need to use a specific L2 protocol number for demultiplexing the frames to | |
* our application. We will use 0x8015. This is marked as reserved by Silicon | |
* Graphics, Inc., but I'm sure they won't mind. Remember this needs to be in | |
* network byte order. | |
*/ | |
#define ETHER_TYPE 0x8015 | |
/* | |
* The minimum number, default number, and maximum number of octets in an | |
* Ethernet datagram payload. Note that the datagram cannot be larger than the | |
* MTU. | |
*/ | |
#define DGRAM_MIN_LEN 46 | |
#define DGRAM_DEF_LEN 1500 | |
#define DGRAM_MAX_LEN 9000 | |
//// TYPE DEFINITIONS | |
/* | |
* The ebert protocol is the following datagram. | |
*/ | |
struct ebert_datagram { | |
uint64_t seq_num; // Each frame has an incrementing sequence number. | |
uint64_t tv_sec; // Timestamp: seconds. | |
uint64_t tv_nsec; // Timestamp: nanoseconds. | |
} __attribute__((__packed__)); | |
//// GLOBAL VARIABLES | |
static char *iface = NULL; // network interface name, e.g. eth0 | |
static uint16_t len = 0; // number of octets in Ethernet frame. Must be between 46 and mtu. | |
//// STATIC FUNCTION DEFINITIONS | |
/* | |
* Parses the command-line arguments. | |
*/ | |
static void parse_args(int argc, char *argv[]) { | |
if (argc < 2 || argc > 3) { | |
printf("usage: ebert <iface> [PAYLOAD_LEN]\n"); | |
exit(0); | |
} | |
iface = argv[1]; | |
if (argc == 3) { | |
len = (uint16_t) atoi(argv[2]); | |
if (len < DGRAM_MIN_LEN) { | |
fprintf(stderr, "Error: MTU must be at least %d octets.\n", DGRAM_MIN_LEN); | |
exit(-1); | |
} | |
else if (len > DGRAM_MAX_LEN) { | |
fprintf(stderr, "Error: MTU must be at most %d octets.\n", DGRAM_MAX_LEN); | |
exit(-1); | |
} | |
} else { | |
len = DGRAM_DEF_LEN; | |
} | |
} | |
/* | |
* Create a random sequence number and store to an ebert_datagram. | |
*/ | |
static void ebert_datagram_rand_seq_num(struct ebert_datagram *datagram) { | |
srandom(time(NULL)); | |
uint64_t seq_num = (uint64_t)random() << 32 | random(); | |
printf("starting sequence number: %llu (0x%016llX)\n", | |
(long long unsigned int)seq_num, | |
(long long unsigned int)seq_num); | |
datagram->seq_num = htobe64(seq_num); | |
} | |
/* | |
* Increment an ebert_datagram sequence number. | |
*/ | |
static inline void ebert_datagram_incr_seq_num(struct ebert_datagram *datagram) { | |
datagram->seq_num = htobe64(be64toh(datagram->seq_num) + 1); | |
} | |
/* | |
* Read the current timestamp and store to an ebert_datagram. | |
*/ | |
static inline void ebert_datagram_fill_time(struct ebert_datagram *datagram) { | |
struct timespec t; | |
if (clock_gettime( | |
/* clockid_t clk_id */ CLOCK_MONOTONIC, | |
/* struct timespec *tp */ &t) == -1) { | |
perror("clock_gettime"); | |
exit(-1); | |
} | |
datagram->tv_sec = htobe64(t.tv_sec); | |
datagram->tv_nsec = htobe64(t.tv_nsec); | |
} | |
static void print_sockaddr_ll(const struct sockaddr_ll *addr) { | |
printf("sockaddr_ll:\n"); | |
if (addr->sll_family == AF_PACKET) { | |
printf("\tsll_family: AF_PACKET\n"); | |
} | |
else { | |
printf("\tsll_family: %d\n", addr->sll_family); | |
} | |
printf("\tsll_protocol: 0x%04X\n", be16toh(addr->sll_protocol)); | |
printf("\tsll_ifindex: %d\n", addr->sll_ifindex); | |
printf("\tsll_hatype: %d\n", addr->sll_hatype); | |
printf("\tsll_pkttype: %u\n", addr->sll_pkttype); | |
printf("\tsll_halen: %u\n", addr->sll_halen); | |
printf("\tsll_addr: %02X:%02X:%02X:%02X:%02X:%02X\n", | |
addr->sll_addr[0], | |
addr->sll_addr[1], | |
addr->sll_addr[2], | |
addr->sll_addr[3], | |
addr->sll_addr[4], | |
addr->sll_addr[5]); | |
} | |
/* | |
* Returns an efficiency multiplier of how many useful bytes are in an Ethernet | |
* link with a given datagram length. Each Ethernet frame has 38 overhead bytes. | |
* The efficiency is a unitless multiplier. | |
*/ | |
static void calc_throughput_efficiency(mpf_t efficiency, const unsigned int datagram_len) { | |
/* | |
* efficiency = datagram_len / (datagram_len + 38) | |
*/ | |
mpf_t denominator; | |
mpf_init(denominator); | |
mpf_set_ui(denominator, datagram_len); | |
mpf_add_ui(denominator, denominator, 38); | |
mpf_set_ui(efficiency, datagram_len); | |
mpf_div(efficiency, efficiency, denominator); | |
mpf_clear(denominator); | |
} | |
//// GLOBAL FUNCTION DEFINITIONS | |
int main(int argc, char *argv[]) { | |
printf("ebert: Ethernet Bit Error Rate Tester\n"); | |
/* | |
* Benchmark the clock overhead. | |
*/ | |
mpz_t time_overhead_ns; | |
mpz_init(time_overhead_ns); | |
benchmark_time_overhead(time_overhead_ns); | |
mpz_clear(time_overhead_ns); | |
/* | |
* Benchmark the clock resolution. | |
*/ | |
mpz_t time_resolution_ns; | |
mpz_init(time_resolution_ns); | |
benchmark_time_resolution(time_resolution_ns); | |
mpz_clear(time_resolution_ns); | |
/* | |
* Parse the command-line arguments. | |
*/ | |
parse_args(argc, argv); | |
/* | |
* What is the NETDEVICE(7) index of this interface? | |
* This is needed for PACKET(7) stuff. | |
*/ | |
int ifindex = if_nametoindex(iface); | |
/* | |
* Open a raw Ethernet socket. We will need to provide the Ethernet header | |
* when we send an Ethernet frame. The NIC will generate the preamble, FCS, | |
* and IFG. | |
*/ | |
int txsockfd = socket( | |
/* int domain */ AF_PACKET, | |
/* int type */ SOCK_DGRAM, | |
/* int protocol */ ETHER_TYPE); | |
///* int protocol */ ETH_P_ALL); | |
///* int protocol */ htobe16(ETH_P_ALL)); | |
if (txsockfd == -1) { | |
perror("socket"); | |
exit(-1); | |
} | |
int rxsockfd = socket( | |
/* int domain */ AF_PACKET, | |
/* int type */ SOCK_DGRAM, | |
/* int protocol */ ETHER_TYPE); | |
///* int protocol */ ETH_P_ALL); | |
///* int protocol */ htobe16(ETH_P_ALL)); | |
if (rxsockfd == -1) { | |
perror("socket"); | |
exit(-1); | |
} | |
/* | |
* Bind the rx socket to the chosen Ethernet interface. This ensures that we | |
* don't send or receive frames on the wrong interface, and that we only | |
* receive frames with the chosen ether_type. For sockaddr_ll, we only need | |
* to supply the family, protocol, and ifindex for binding. | |
* | |
* Technically we only need to bind the rx socket, not the tx socket. But it | |
* doesn't hurt. | |
*/ | |
struct sockaddr_ll addr; | |
memset((void *)&addr, 0, sizeof(addr)); | |
addr.sll_family = AF_PACKET; | |
addr.sll_protocol = htobe16(ETHER_TYPE); | |
addr.sll_ifindex = ifindex; | |
if (bind( | |
/* int sockfd */ txsockfd, | |
/* const struct sockaddr *addr */ (struct sockaddr *)&addr, | |
/* socklen_t addrlen */ sizeof(addr)) == -1) { | |
perror("bind"); | |
exit(-1); | |
} | |
if (bind( | |
/* int sockfd */ rxsockfd, | |
/* const struct sockaddr *addr */ (struct sockaddr *)&addr, | |
/* socklen_t addrlen */ sizeof(addr)) == -1) { | |
perror("bind"); | |
exit(-1); | |
} | |
/* | |
* Read the network interface's hardware address. | |
*/ | |
struct ifreq req; | |
memset((void *)&req, 0, sizeof(req)); | |
strncpy(req.ifr_name, iface, IF_NAMESIZE); | |
if (ioctl( | |
/* int d */ txsockfd, | |
/* int request*/ SIOCGIFHWADDR, | |
/* ... */ (void *)&req) == -1) { | |
perror("ioctl"); | |
exit(-1); | |
} | |
uint8_t hwaddr[6]; | |
memcpy((void *)hwaddr, (void *)req.ifr_hwaddr.sa_data, 6); | |
char hwaddr_str[18]; | |
memset((void *)hwaddr_str, 0, sizeof(hwaddr_str)); | |
sprintf(hwaddr_str, "%02X:%02X:%02X:%02X:%02X:%02X", | |
hwaddr[0], hwaddr[1], hwaddr[2], hwaddr[3], hwaddr[4], hwaddr[5]); | |
/* | |
* Read the network interface's MTU, e.g. 1500, 9000 | |
*/ | |
memset((void *)&req, 0, sizeof(req)); | |
strncpy(req.ifr_name, iface, IF_NAMESIZE); | |
if (ioctl( | |
/* int d */ txsockfd, | |
/* int request*/ SIOCGIFMTU, | |
/* ... */ (void *)&req) == -1) { | |
perror("ioctl"); | |
exit(-1); | |
} | |
int mtu = req.ifr_mtu; | |
/* | |
* Read the network interface's flags. | |
*/ | |
memset((void *)&req, 0, sizeof(req)); | |
strncpy(req.ifr_name, iface, IF_NAMESIZE); | |
if (ioctl( | |
/* int d */ txsockfd, | |
/* int request*/ SIOCGIFFLAGS, | |
/* ... */ (void *)&req) == -1) { | |
perror("ioctl"); | |
exit(-1); | |
} | |
short flags = req.ifr_flags; | |
char flags_str[1024]; | |
sprintf(flags_str, "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", | |
(flags & IFF_UP) ? "UP " : "", | |
(flags & IFF_BROADCAST) ? "BROADCAST " : "", | |
(flags & IFF_DEBUG) ? "DEBUG " : "", | |
(flags & IFF_LOOPBACK) ? "LOOPBACK " : "", | |
(flags & IFF_POINTOPOINT) ? "POINTTOPOINT " : "", | |
(flags & IFF_NOTRAILERS) ? "NOTRAILERS " : "", | |
(flags & IFF_RUNNING) ? "RUNNING " : "", | |
(flags & IFF_NOARP) ? "NOARP " : "", | |
(flags & IFF_PROMISC) ? "PROMISC " : "", | |
(flags & IFF_ALLMULTI) ? "ALLMULTI " : "", | |
(flags & IFF_MASTER) ? "MASTER " : "", | |
(flags & IFF_SLAVE) ? "SLAVE " : "", | |
(flags & IFF_MULTICAST) ? "MULTICAST " : "", | |
(flags & IFF_PORTSEL) ? "PORTSEL " : "", | |
(flags & IFF_AUTOMEDIA) ? "AUTOMEDIA " : "", | |
(flags & IFF_DYNAMIC) ? "DYNAMIC " : ""); | |
/* | |
* Read the network interface's tx queue length. | |
*/ | |
memset((void *)&req, 0, sizeof(req)); | |
strncpy(req.ifr_name, iface, IF_NAMESIZE); | |
if (ioctl( | |
/* int d */ txsockfd, | |
/* int request*/ SIOCGIFTXQLEN, | |
/* ... */ (void *)&req) == -1) { | |
perror("ioctl"); | |
exit(-1); | |
} | |
int txqueuelen = req.ifr_qlen; | |
/* | |
* Print information about this interface. | |
*/ | |
printf("interface: %s\n", iface); | |
printf("ifindex: %d\n", ifindex); | |
printf("hardware address: %s\n", hwaddr_str); | |
printf("flags: %s\n", flags_str); | |
printf("MTU: %d\n", mtu); | |
printf("payload length: %hu (0x%02hX)\n", len, len); | |
printf("txqueuelen: %d\n", txqueuelen); | |
/* | |
* Prepare the tx sockaddr. | |
*/ | |
struct sockaddr_ll dest_addr; | |
memset((void *)&dest_addr, 0, sizeof(dest_addr)); | |
dest_addr.sll_family = AF_PACKET; /* Always AF_PACKET */ | |
dest_addr.sll_protocol = htobe16(ETHER_TYPE); /* Physical layer protocol */ // Uses 802.3 if set to 0 | |
dest_addr.sll_ifindex = ifindex; /* Interface number */ | |
//unsigned short sll_hatype; /* Header type */ // Not needed for sending | |
//unsigned char sll_pkttype; /* Packet type */ // Not needed for sending | |
dest_addr.sll_halen = 6; /* Length of address */ | |
memcpy(dest_addr.sll_addr, hwaddr, 6); /* Physical layer address */ // Send to ourself | |
//memset((void*)dest_addr.sll_addr, 0xFF, 6); /* Physical layer address */ // Broadcast | |
/* | |
* Prepare the rx sockaddr. | |
*/ | |
struct sockaddr_ll src_addr; | |
memset((void *)&src_addr, 0, sizeof(src_addr)); | |
src_addr.sll_family = AF_PACKET; /* Always AF_PACKET */ | |
socklen_t addrlen = sizeof(src_addr); | |
/* | |
* Prepare the tx ebert_datagram. | |
*/ | |
uint8_t tx_data[DGRAM_MAX_LEN]; | |
memset((void *)tx_data, 0, sizeof(tx_data)); | |
struct ebert_datagram *tx_datagram = (struct ebert_datagram *)tx_data; | |
ebert_datagram_rand_seq_num(tx_datagram); | |
ebert_datagram_fill_time(tx_datagram); | |
/* | |
* Prepare the rx ebert_datagram. | |
*/ | |
uint8_t rx_data[DGRAM_MAX_LEN]; | |
memset((void *)rx_data, 0, sizeof(rx_data)); | |
struct ebert_datagram *rx_datagram = (struct ebert_datagram *)rx_data; | |
/* | |
* Prepare the packet counters. | |
* | |
* Yes it's on and poppin | |
* Yes the party's rockin | |
* Yes the cutie shockin | |
* Yes and there ain't no stoppin | |
*/ | |
uint64_t txpktcnt = 0; | |
uint64_t rxpktcnt = 0; | |
/* | |
* Enter infinite loop. | |
*/ | |
mpz_t t0_ns; | |
mpz_t t1_ns; | |
mpz_t elapsed_time_ns; | |
mpf_t elapsed_time_s; | |
mpf_t throughput_bps; | |
mpf_t efficiency; | |
mpf_t max_bps; | |
mpf_t throughput; | |
mpz_init(t0_ns); | |
mpz_init(t1_ns); | |
mpz_init(elapsed_time_ns); | |
mpf_init(elapsed_time_s); | |
mpf_init(throughput_bps); | |
mpf_init(efficiency); | |
mpf_init(max_bps); | |
mpf_init(throughput); | |
get_time_mpz(t0_ns); | |
profile_init(); | |
while (1) { | |
//while (rxpktcnt < 100) { | |
/* | |
* Setup the file descriptor masks for pselect(). | |
*/ | |
//fd_set readfds; | |
//fd_set writefds; | |
//FD_ZERO(&readfds); | |
//FD_ZERO(&writefds); | |
//FD_SET(rxsockfd, &readfds); | |
//FD_SET(txsockfd, &writefds); | |
//int nfds = ((txsockfd > rxsockfd) ? txsockfd : rxsockfd) + 1; | |
//int retval = pselect( | |
// /* int nfds */ nfds, | |
// /* fd_set *readfds */ &readfds, | |
// /* fd_set *writefds */ &writefds, | |
// /* fd_set *exceptfds */ NULL, | |
// /* const struct timespec *timeout */ NULL, | |
// /* const sigset_t *sigmask */ NULL); | |
//if (retval == -1) { | |
// perror("pselect"); | |
// exit(-1); | |
// } | |
/* | |
* Send Ethernet frames until the transmit queue is full. | |
*/ | |
//if (FD_ISSET(txsockfd, &writefds)) { | |
ssize_t num_octets = sendto( | |
/* int sockfd */ txsockfd, | |
/* const void *buf */ tx_data, | |
/* size_t len */ len, | |
/* int flags */ MSG_DONTWAIT, | |
/* const struct sockaddr *dest_addr */ (const struct sockaddr *)&dest_addr, | |
/* socklen_t addrlen */ sizeof(dest_addr)); | |
if (num_octets != len) { | |
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == ENOBUFS) break; | |
perror("sendto"); | |
printf("errno: %d\n", errno); | |
printf("num_octets: %zd\n", num_octets); | |
exit(-1); | |
} | |
txpktcnt++; | |
/* | |
* If we were successful, then make the next frame. | |
*/ | |
ebert_datagram_incr_seq_num(tx_datagram); | |
ebert_datagram_fill_time(tx_datagram); | |
//} | |
/* | |
* Receive Ethernet frames until the receive queue is empty. | |
*/ | |
//if (FD_ISSET(rxsockfd, &readfds)) { | |
while (1) { | |
//memset((void *)&src_addr, 0, sizeof(src_addr)); // Is this required? | |
addrlen = sizeof(src_addr); | |
ssize_t num_octets = recvfrom( | |
/* int sockfd */ rxsockfd, | |
/* void *buf */ rx_data, | |
/* size_t len */ sizeof(rx_data), | |
///* int flags */ MSG_DONTWAIT | MSG_TRUNC, | |
/* int flags */ MSG_DONTWAIT, | |
/* struct sockaddr *src_addr */ (struct sockaddr *)&src_addr, | |
/* socklen_t *addrlen */ &addrlen); | |
if (num_octets != len) { | |
if (errno == EAGAIN || errno == EWOULDBLOCK) break; | |
perror("recvfrom"); | |
printf("num_octets: %zd\n", num_octets); | |
exit(-1); | |
} | |
rxpktcnt++; | |
/* | |
* Check the frame. | |
*/ | |
/* | |
* Print statistics. | |
*/ | |
if (rxpktcnt % 100000 == 0) { | |
/* | |
* elapsed_time_ns = t1_ns - t0_ns | |
* throughput_bps = total_bits / elapsed_time_s | |
* throughput_bps = throughput_bps * effic | |
*/ | |
get_time_mpz(t1_ns); | |
mpz_sub(elapsed_time_ns,t1_ns,t0_ns); | |
mpf_set_z(elapsed_time_s,elapsed_time_ns); | |
mpf_div_ui(elapsed_time_s,elapsed_time_s,1000000000); | |
uint64_t total_bytes = rxpktcnt * (uint64_t)len; // Note: there is overhead | |
uint64_t total_bits = total_bytes * 8; | |
mpf_set_ui(throughput_bps, total_bits); | |
mpf_div(throughput_bps, throughput_bps, elapsed_time_s); | |
calc_throughput_efficiency(efficiency, len); | |
mpf_mul_ui(max_bps, efficiency, 10000000000); | |
mpf_div(throughput, throughput_bps, max_bps); | |
mpf_mul_ui(throughput, throughput, 100); | |
printf("txpktcnt: %lu, rxpktcnt: %lu, throughput: ", txpktcnt, rxpktcnt); | |
gmp_printf("%0.1Ff\%\n", throughput); | |
} | |
//} | |
} | |
} | |
profile_print_timeline(); | |
return 0; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# | |
# Note: I was only able to achieve 52% throughput with this Python script on a 10G link in loopback using 9000B frames. | |
# cat /proc/cpuinfo: Intel(R) Core(TM)2 Quad CPU Q9650 @ 3.00GHz | |
# | |
# http://docs.python.org/dev/library/argparse.html | |
import argparse | |
# http://docs.python.org/dev/library/inspect.html | |
import inspect | |
# http://docs.python.org/dev/library/pprint.html | |
import pprint | |
# http://docs.python.org/library/socket.html | |
import socket | |
# http://docs.python.org/library/sys.html | |
import sys | |
# http://docs.python.org/library/time.html | |
import time | |
BROADCAST_ADDR = '\xFF\xFF\xFF\xFF\xFF\xFF' | |
DGRAM_LEN_MIN = 46 | |
DGRAM_LEN_DEF = 1500 | |
DGRAM_LEN_MAX = 9000 # socket.error: [Errno 90] Message too long, for len > 1976 on Linux | |
DGRAM_INCR = bytearray((range(256)*36)[:DGRAM_LEN_MAX]) | |
DGRAM_ZEROS = bytearray([0]*DGRAM_LEN_MAX) | |
ETHER_TYPE = 0x8015 | |
def s(s=None,ms=None,us=None,ns=None): | |
"Format time as seconds." | |
if s: return '%.9fs' % s | |
if ms: return '%.9fs' % (ms / 1000.0) | |
if us: return '%.9fs' % (us / 1000000.0) | |
if ns: return '%.9fs' % (ns / 1000000000.0) | |
def ms(s=None,ms=None,us=None,ns=None): | |
"Format time as milliseconds." | |
if s: return '%.6fms' % (s * 1000.0) | |
if ms: return '%.6fms' % ms | |
if us: return '%.6fms' % (us / 1000.0) | |
if ns: return '%.6fms' % (ns / 1000000.0) | |
def us(s=None,ms=None,us=None,ns=None): | |
"Format time as microseconds." | |
if s: return '%.3fus' % (s * 1000000.0) | |
if ms: return '%.3fus' % (ms * 1000.0) | |
if us: return '%.3fus' % us | |
if ns: return '%.3fus' % (ns / 1000.0) | |
def ns(s=None,ms=None,us=None,ns=None): | |
"Format time as nanoseconds." | |
if s: return '%.0fns' % (s * 1000000000.0) | |
if ms: return '%.0fns' % (ms * 1000000.0) | |
if us: return '%.0fns' % (us * 1000.0) | |
if ns: return '%.0fns' % ns | |
def benchmark_time_overhead(N=100,in_units_of=ns): | |
"Return the amount of time it takes to call time.time()." | |
def mini_benchmark(N=1000): | |
t0 = time.time() | |
for i in xrange(N): time.time() | |
t1 = time.time() | |
return (t1 -t0) / N | |
runs = [mini_benchmark(N) for i in xrange(N)] | |
overhead_s = min(runs) | |
print('benchmark: time.time() overhead: %s' % in_units_of(s=overhead_s)) | |
return overhead_s | |
def benchmark_time_resolution(N=100,in_units_of=ns): | |
"Return the minimum clock resolution of time.time()." | |
def mini_benchmark(): | |
t0 = time.time() | |
i = 0 | |
while True: | |
t1 = time.time() | |
if (t1 - t0) > 0: return t1 - t0 | |
i += 1 | |
runs = [mini_benchmark() for i in xrange(N)] | |
resolution_s = min(runs) | |
print('benchmark: time.time() resolution: %s' % in_units_of(s=resolution_s)) | |
return resolution_s | |
def serialization_latency(ethernet_payload_len,in_units_of=us): | |
"Return the amount of time taken to serialize a 10G Ethernet frame." | |
assert DGRAM_LEN_MIN <= ethernet_payload_len <= DGRAM_LEN_MAX | |
bits_per_ns = 10 | |
PREAMBLE_LEN = 8 | |
HEADER_LEN = 14 | |
FCS_LEN = 4 | |
IFG_LEN = 12 | |
bits = (PREAMBLE_LEN + HEADER_LEN + ethernet_payload_len + FCS_LEN + IFG_LEN) * 8 | |
latency_ns = bits / bits_per_ns | |
print('10G Ethernet serialization latency for %d octet payload: %s' % ( | |
ethernet_payload_len,in_units_of(ns=latency_ns))) | |
return latency_ns | |
def throughput_efficiency(ethernet_payload_len): | |
"Returns the percentage of throughput that can be achieved with a given payload length." | |
assert DGRAM_LEN_MIN <= ethernet_payload_len <= DGRAM_LEN_MAX | |
return ethernet_payload_len / (ethernet_payload_len + 38.0) | |
if __name__=='__main__': | |
parser = argparse.ArgumentParser(description='Ethernet Bit Error Rate Tester') | |
parser.add_argument('iface', help='network interface, e.g. eth0') | |
parser.add_argument('len', type=int, help='payload length; default: 46') | |
args = parser.parse_args() | |
if args.len < 46 or args.len > 9000: | |
print('len must be between 46 and 9000') | |
sys.exit(0) | |
# | |
# Run some benchmarks. | |
# | |
overhead_s = benchmark_time_overhead() | |
resolution_s = benchmark_time_resolution() | |
serialization_latency(args.len) | |
# | |
# Create the sender socket. | |
# | |
tx = socket.socket(socket.AF_PACKET, socket.SOCK_DGRAM) | |
tx.setblocking(False) | |
tx_frame = str(DGRAM_INCR[:args.len]) | |
# | |
# Create the receiver socket. | |
# | |
rx = socket.socket(socket.AF_PACKET, socket.SOCK_DGRAM) | |
rx.setblocking(False) | |
rx.bind((args.iface,ETHER_TYPE)) | |
# | |
# Send and receive packets. | |
# | |
def send_frame(): | |
""" | |
Sends and Ethernet frame and returns the amount of time taken and for minimum sized payloads. | |
Minimum: 1.907us | |
Median: 2.861us | |
Maximum: 4.053us | |
""" | |
t0 = time.time() | |
tx.sendto(tx_frame, (args.iface,ETHER_TYPE,0,0,BROADCAST_ADDR)) | |
t1 = time.time() | |
return t1 - t0 | |
def receive_frame(): | |
""" | |
Receives an Ethernet frame and returns the amount of time taken. | |
Note: These measurements are for when an Ethernet frame is already in the NIC and for minimum sized payloads. | |
Minimum: 0.954us | |
Median: 0.954us | |
Maximum: 2.146us | |
""" | |
t0 = time.time() | |
rx_frame = rx.recv(args.len) | |
t1 = time.time() | |
return t1 - t0 | |
def take_some_measurements(): | |
N = 50 | |
send_frame_samples = [send_frame() for i in xrange(N)] | |
receive_frame_samples = [receive_frame() for i in xrange(N)] | |
# | |
# Print the statistics. | |
# | |
def print_stats(): | |
print('send frame') | |
for sample_s in send_frame_samples: print('sample: %s' % us(s=sample_s)) | |
print('receive frame') | |
for sample_s in receive_frame_samples: print('sample: %s' % us(s=sample_s)) | |
print_stats() | |
print('sorting') | |
send_frame_samples.sort() | |
receive_frame_samples.sort() | |
print_stats() | |
def clear_rx_queue(): | |
try: | |
while True: | |
receive_frame() | |
except socket.error: | |
return | |
# | |
# | |
# | |
N = 10000 | |
packets_sent = 0 | |
packets_received = 0 | |
tx_samples = [] | |
rx_samples = [] | |
clear_rx_queue() | |
t0 = time.time() | |
while packets_received < N: | |
try: | |
tx_samples.append(send_frame()) | |
packets_sent += 1 | |
except socket.error: | |
pass | |
try: | |
rx_samples.append(receive_frame()) | |
packets_received += 1 | |
except socket.error: | |
pass | |
t1 = time.time() | |
print('tx: %s' % us(s=min(tx_samples))) | |
print('rx: %s' % us(s=min(rx_samples))) | |
throughput_bps = (N * args.len) * 8 / (t1 - t0) | |
THROUGHPUT_MAX_BPS = throughput_efficiency(args.len) * 10000000000.0 | |
throughput_fraction = throughput_bps / THROUGHPUT_MAX_BPS | |
print('throughput: %.1f%%' % (throughput_fraction * 100)) | |
#sendto_samples = [abs(t0-t1) - overhead_s for (t0,t1,t2) in samples] | |
#sendto_min = min(sendto_samples) | |
#sendto_max = max(sendto_samples) | |
#recv_samples = [abs(t1-t2) - overhead_s for (t0,t1,t2) in samples] | |
#recv_min = min(recv_samples) | |
#recv_max = max(recv_samples) | |
#print('tx.sendto.min: %s' % us(s=sendto_min)) | |
#print('tx.sendto.max: %s' % us(s=sendto_max)) | |
#print('tx.recv.min: %s' % us(s=recv_min)) | |
#print('tx.recv.max: %s' % us(s=recv_max)) | |
# s.bind((HOST,PORT)) | |
# s.sendto('foo','192.168.2.1') | |
# data = s.recv(1024) | |
# s.close() | |
# print(data) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
CFLAGS=-std=gnu99 -O3 | |
LDFLAGS=-lrt -lgmp | |
.PHONY: all | |
all: ebert | |
ebert: ebert.c time.o profile.o | |
time.o: time.c time.h | |
profile.o: profile.c profile.h | |
.PHONY: clean | |
clean: | |
rm -f ebert *.o | |
.PHONY: install | |
install: ebert | |
install ebert /usr/local/bin |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* <features.h> | |
* __STRICT_ANSI__ 1 | |
* _ISOC9X_SOURCE 1 | |
* _POSIX_SOURCE 1 | |
* _POSIX_C_SOURCE 199506L | |
* _XOPEN_SOURCE 500 | |
* _XOPEN_SOURCE_EXTENDED 1 | |
* _LARGEFILE_SOURCE 1 | |
* _BSD_SOURCE 1 | |
* _SVID_SOURCE 1 | |
*/ | |
#define _GNU_SOURCE 1 | |
#include <assert.h> // assert | |
#include <stdint.h> // uint16_t, uint64_t | |
#include <stdio.h> // printf, sprintf | |
#include <stdlib.h> // exit | |
#include <string.h> // memset, strncpy | |
#include <time.h> // TIME(2) | |
#include <gmp.h> // http://gmplib.org/ | |
#include "time.h" | |
#include "profile.h" | |
//// GLOBAL VARIABLES | |
struct profile_event events[MAX_PROFILE_EVENTS]; | |
int next_profile_event = 0; | |
//// GLOBAL FUNCTION DEFINITIONS | |
void profile_init(void) { | |
memset((void *)events, 0, sizeof(events)); | |
for (int i = 0; i < MAX_PROFILE_EVENTS; i++) { | |
mpz_init(events[i].timestamp_ns); | |
} | |
} | |
void profile_snapshot(char *file, int line) { | |
if (next_profile_event >= MAX_PROFILE_EVENTS) { | |
return; | |
} | |
struct profile_event *e = &events[next_profile_event++]; | |
e->file = file; | |
e->line = line; | |
get_time_mpz(e->timestamp_ns); | |
} | |
void profile_print_timeline(void) { | |
if (next_profile_event == 0) return; | |
mpf_t start_time_ns; | |
mpf_t elapsed_time_ns; | |
mpf_t elapsed_time_us; | |
mpf_init(start_time_ns); | |
mpf_init(elapsed_time_ns); | |
mpf_init(elapsed_time_us); | |
mpf_set_z(start_time_ns, events[0].timestamp_ns); | |
for (int i = 0; i < next_profile_event; i++) { | |
mpf_set_z(elapsed_time_ns, events[i].timestamp_ns); | |
mpf_sub(elapsed_time_ns, elapsed_time_ns, start_time_ns); | |
mpf_div_ui(elapsed_time_us, elapsed_time_ns, 1000); | |
printf("%04d: event: %s:%d: ", i, events[i].file, events[i].line); | |
gmp_printf("%0.3Ffns\n", elapsed_time_us); | |
//mpf_set(start_time_ns, events[i].timestamp_ns); | |
} | |
mpf_clear(elapsed_time_us); | |
mpf_clear(elapsed_time_ns); | |
mpf_clear(start_time_ns); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef __PROFILE_H__ | |
#define __PROFILE_H__ | |
//// CONSTANT DEFINITIONS | |
#define MAX_PROFILE_EVENTS 10000 | |
//// TYPE DEFINITIONS | |
struct profile_event { | |
char *file; | |
int line; | |
mpz_t timestamp_ns; | |
}; | |
//// GLOBAL VARIABLE DECLARATIONS | |
extern struct profile_event events[]; | |
extern int next_profile_event; | |
//// GLOBAL FUNCTION DEFINITIONS | |
/* | |
* Initialize this module. | |
*/ | |
void profile_init(void); | |
/* | |
* Take a profile snapshot. | |
*/ | |
void profile_snapshot(char *file, int line); | |
/* | |
* Prints a timeline of all recorded events. | |
*/ | |
void profile_print_timeline(void); | |
#endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* <features.h> | |
* __STRICT_ANSI__ 1 | |
* _ISOC9X_SOURCE 1 | |
* _POSIX_SOURCE 1 | |
* _POSIX_C_SOURCE 199506L | |
* _XOPEN_SOURCE 500 | |
* _XOPEN_SOURCE_EXTENDED 1 | |
* _LARGEFILE_SOURCE 1 | |
* _BSD_SOURCE 1 | |
* _SVID_SOURCE 1 | |
*/ | |
#define _GNU_SOURCE 1 | |
#include <stdint.h> // uint16_t, uint64_t | |
#include <stdio.h> // printf, sprintf | |
#include <stdlib.h> // exit | |
#include <string.h> // memset, strncpy | |
#include <errno.h> // errno, EWOULDBLOCK | |
#include <time.h> // TIME(2) | |
#include <gmp.h> // http://gmplib.org/ | |
#include "time.h" | |
void get_time(struct timespec *ts) { | |
if (clock_gettime( | |
/* clockid_t clk_id */ CLOCK_MONOTONIC, | |
/* struct timespec *tp */ ts) == -1) { | |
perror("clock_gettime"); | |
exit(-1); | |
} | |
} | |
void get_time_mpz(mpz_t t_ns) { | |
struct timespec ts; | |
if (clock_gettime( | |
/* clockid_t clk_id */ CLOCK_MONOTONIC, | |
/* struct timespec *tp */ &ts) == -1) { | |
perror("clock_gettime"); | |
exit(-1); | |
} | |
/* | |
* t_ns <-- tv_sec * 1000000000 + tv_nsec | |
*/ | |
mpz_set_ui(t_ns, ts.tv_sec); | |
mpz_mul_ui(t_ns, t_ns, 1000000000); | |
mpz_add_ui(t_ns, t_ns, ts.tv_nsec); | |
} | |
void timespec_to_mpz(const struct timespec *ts, mpz_t t_ns) { | |
/* | |
* t_ns <-- tv_sec * 1000000000 + tv_nsec | |
*/ | |
mpz_set_ui(t_ns, ts->tv_sec); | |
mpz_mul_ui(t_ns, t_ns, 1000000000); | |
mpz_add_ui(t_ns, t_ns, ts->tv_nsec); | |
} | |
void benchmark_time_overhead(mpz_t overhead_ns) { | |
mpz_t t0_ns; | |
mpz_t t1_ns; | |
mpz_t elapsed_time_ns; | |
mpz_t minimum_overhead_ns; | |
mpz_init(t0_ns); | |
mpz_init(t1_ns); | |
mpz_init(elapsed_time_ns); | |
mpz_init(minimum_overhead_ns); | |
const unsigned int N = 1000; | |
int first_time = 1; | |
for (int i = 0; i < N; i++) { | |
/* | |
* Run get_time() N times in a tight loop. | |
* Note that performance improves when N > 1, most likely due to CPU | |
* instruction caching. | |
*/ | |
const unsigned int N = 1; | |
struct timespec t0; | |
struct timespec t1; | |
get_time_mpz(t0_ns); | |
for (int i = 0; i < N; i++) { | |
get_time_mpz(t1_ns); | |
} | |
/* | |
* overhead_ns <-- (t1 - t0) / N | |
*/ | |
mpz_sub(elapsed_time_ns, t1_ns, t0_ns); | |
mpz_div_ui(overhead_ns, elapsed_time_ns, N); | |
/* | |
* If this is the first time, then store the result to min_overhead_ns. | |
*/ | |
if (first_time) { | |
first_time = 0; | |
mpz_set(minimum_overhead_ns,overhead_ns); | |
} | |
/* | |
* If the latest measurement is smaller than the current minimum, | |
* then store the result to min_overhead_ns. | |
*/ | |
else { | |
if (mpz_cmp(overhead_ns, minimum_overhead_ns) < 0) { | |
mpz_set(minimum_overhead_ns, overhead_ns); | |
} | |
} | |
} | |
/* | |
* Store the minimum back to overhead_ns. | |
* Print the result. | |
*/ | |
mpz_set(overhead_ns, minimum_overhead_ns); | |
printf("benchmark: get_time() overhead: "); | |
mpz_out_str(stdout, 10, overhead_ns); | |
printf("ns\n"); | |
mpz_clear(minimum_overhead_ns); | |
mpz_clear(elapsed_time_ns); | |
mpz_clear(t1_ns); | |
mpz_clear(t0_ns); | |
} | |
void benchmark_time_resolution(mpz_t resolution_ns) { | |
struct timespec res; | |
if (clock_getres( | |
/* clockid_t clk_id */ CLOCK_MONOTONIC, | |
/* struct timespec *tp */ &res) == -1) { | |
perror("clock_gettime"); | |
exit(-1); | |
} | |
timespec_to_mpz(&res,resolution_ns); | |
printf("benchmark: get_time() resolution: "); | |
mpz_out_str(stdout, 10, resolution_ns); | |
printf("ns\n"); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef __TIME_H__ | |
#define __TIME_H__ | |
/* | |
* Gets the current time in the form of a struct timespec. | |
*/ | |
void get_time(struct timespec *ts); | |
/* | |
* Gets the current time in the form of an mpz_t integer. | |
*/ | |
void get_time_mpz(mpz_t t_ns); | |
/* | |
* Converts a struct timespec to a GNU multiprecision mpz integer in units of nanoseconds. | |
* The caller must call mpz_init(t) before calling this function. | |
* The caller must call mpz_clear(t) after calling this function. | |
*/ | |
void timespec_to_mpz(const struct timespec *ts, mpz_t t_ns); | |
/* | |
* Returns the time taken to call get_time(). | |
* The caller must call mpz_init(overhead_ns) before calling this function. | |
* The caller must call mpz_clear(overhead_ns) after calling this function. | |
*/ | |
void benchmark_time_overhead(mpz_t overhead_ns); | |
/* | |
* Returns the minimum resolution of the clock. | |
* The caller must call mpz_init(resolution_ns) before calling this function. | |
* The caller must call mpz_clear(resolution_ns) after calling this function. | |
*/ | |
void benchmark_time_resolution(mpz_t resolution_ns); | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment