Skip to content

Instantly share code, notes, and snippets.

@nfarring
Created September 12, 2011 21:37
Show Gist options
  • Save nfarring/1212526 to your computer and use it in GitHub Desktop.
Save nfarring/1212526 to your computer and use it in GitHub Desktop.
ebert: Ethernet Bit Error Rate Tester

ebert: Ethernet Bit Error Rate Tester

/*
* <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;
}
#!/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)
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
/*
* <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);
}
#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
/*
* <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");
}
#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