Skip to content

Instantly share code, notes, and snippets.

@fishi0x01
Last active November 17, 2023 07:44
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fishi0x01/b76b7362216f4db49d09 to your computer and use it in GitHub Desktop.
Save fishi0x01/b76b7362216f4db49d09 to your computer and use it in GitHub Desktop.
Measuring Bandwidth and Round-Trip Time of a TCP Connection inside the Application Layer. Code for blog post https://fishi.devtail.io/weblog/2015/04/12/measuring-bandwidth-and-round-trip-time-tcp-connection-inside-application-layer/
/* Code snippet for measuring bandwidth with harmonic mean */
#define SKIPS 10 // initial measurement skips - skip the first slow-start values
// bandwidth metrics
double bandwidth = -1; // in MB/s
float total_bytes = 0;
int n = 0; // number of measure values
int skips = SKIPS;
double timeval_subtract(struct timeval *x, struct timeval *y)
{
double diff = x->tv_sec - y->tv_sec;
diff += (x->tv_usec - y->tv_usec)/1000000.0;
return diff;
}
/* measure bandwidth (with harmonic mean)
cur_ts - start_ts define the total time interval.
bytes is the number of bytes read with the last socket.read() call */
double measure_bw(struct timeval *start_ts, struct timeval *cur_ts, float bytes)
{
total_bytes += bytes;
// check if we need to skip this measurement
if(skips > 0)
{
skips--;
return bandwidth;
}
// calculate current measurement
struct timeval result;
timeval_subtract(&result,cur_ts,start_ts);
double ts_diff = result.tv_sec;
ts_diff += result.tv_usec/1000000.0;
double cur_bw = (total_bytes/(1024*1024))/ts_diff;
if(bandwidth < 0)
{
// first measurement
bandwidth = cur_bw;
}
else
{
// harmonic mean
bandwidth = (n+1)/((n/bandwidth)+(1/cur_bw));
}
n++;
return bandwidth;
}
/* Code snippet for measuring rtt with weighed moving average */
// rtt
double rtt = -1; // in s
double timeval_subtract(struct timeval *x, struct timeval *y)
{
double diff = x->tv_sec - y->tv_sec;
diff += (x->tv_usec - y->tv_usec)/1000000.0;
return diff;
}
/* measure rtt (with weighed moving average).
cur_ts - start_ts is the time between request sent and first response socket.read() */
double measure_rtt(struct timeval *start_ts, struct timeval *cur_ts)
{
struct timeval result;
timeval_subtract(&result,cur_ts,start_ts);
double cur_rtt = result.tv_sec;
cur_rtt += result.tv_usec/1000000.0;
if(rtt < 0)
{
// first measurement
rtt = cur_rtt;
}
else
{
// weighed moving average
rtt = 0.8*rtt + 0.2*cur_rtt;
}
return rtt;
}
/* Putting it all together: measure bandwidth and rtt */
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#define BUF_SIZE 1024*1024*2 // 2MB
#define SKIPS 10 // initial measurement skips - skip the first slow-start values + initial burst
#define ROUNDS 2 // measurement rounds - number of file downloads
#define TIMEOUT 2 // socket timeout in s
// connection specifications
char *domain = "devtail.com"; // your domain
char *resource = "/tmp_blob"; // your resource
int port = 80;
// measurement metrics
// bandwidth
double bandwidth[2] = {-1,-1}; // in MB/s
float total_bytes[2] = {0,0};
int n[2] = {0,0};
int skips = SKIPS;
// rtt
double rtt = -1; // in s
// measurement rounds
int rounds = ROUNDS;
double timeval_subtract(struct timeval *x, struct timeval *y)
{
double diff = x->tv_sec - y->tv_sec;
diff += (x->tv_usec - y->tv_usec)/1000000.0;
return diff;
}
/* measure bandwidth (with harmonic mean)
cur_ts - start_ts define the total time interval.
bytes is the number of bytes read with the last socket.read() call */
double measure_bw(struct timeval *start_ts, struct timeval *cur_ts, float bytes, int option)
{
total_bytes[option] += bytes;
// calculate current measurement
double ts_diff = timeval_subtract(cur_ts,start_ts);
double cur_bw = (total_bytes[option]/(1024*1024))/ts_diff;
if(bandwidth[option] < 0)
{
// first measurement
bandwidth[option] = cur_bw;
}
else
{
// harmonic mean
bandwidth[option] = (n[option]+1)/((n[option]/bandwidth[option])+(1/cur_bw));
}
n[option]++;
return bandwidth[option];
}
/* measure rtt (with weighed moving average).
cur_ts - start_ts is the time between request sent and first response socket.read() */
double measure_rtt(struct timeval *start_ts, struct timeval *cur_ts)
{
double cur_rtt = timeval_subtract(cur_ts,start_ts);
if(rtt < 0)
{
// first measurement
rtt = cur_rtt;
}
else
{
// weighed moving average
rtt = 0.8*rtt + 0.2*cur_rtt;
}
return rtt;
}
void make_request(int sock, char *buf, int size)
{
// 5. prepare and send request
bzero(buf,size);
sprintf(buf, "GET %s HTTP/1.1\r\nHost: %s\r\n\r\n", resource, domain);
if(send(sock, buf, strlen(buf), 0) < 0)
{
perror("Error while sending request");
return;
}
}
int create_tcp_connection()
{
// create socket
int sock;
if((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("Error : Could not create socket\n");
return -1;
}
// find server
struct hostent *server;
server = gethostbyname(domain);
if(server == NULL)
{
perror("Could not find server\n");
return -1;
}
// create address
struct sockaddr_in serveraddr;
bzero((char *) &serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
bcopy((char *)server->h_addr, (char *)&serveraddr.sin_addr.s_addr, server->h_length);
serveraddr.sin_port = htons(port);
// connect to server
if(connect(sock, (struct sockaddr *) &serveraddr, sizeof(serveraddr)) < 0)
{
perror("Could not connect to server\n");
return -1;
}
return sock;
}
int main(int argc, char *argv[])
{
int sock = create_tcp_connection();
// prepare timestamps
struct timeval start_ts,n_pack_ts,first_pack_ts,cur_ts;
start_ts.tv_sec = 0;
start_ts.tv_usec = 0;
n_pack_ts.tv_sec = 0;
n_pack_ts.tv_usec = 0;
first_pack_ts.tv_sec = 0;
first_pack_ts.tv_usec = 0;
cur_ts.tv_sec = 0;
cur_ts.tv_usec = 0;
// prepare objects for blocking IO
fd_set set;
struct timeval timeout;
timeout.tv_sec = TIMEOUT;
timeout.tv_usec = 0;
FD_ZERO(&set);
FD_SET(sock,&set);
// metrics
int bytes;
double bw_a = 0; // option 1
double bw_b = 0; // option 2
double rtl = 0;
// buffers
char *buf = malloc(BUF_SIZE);
int req_buf_size = 1024*4; // 4KB
char *req_buf = malloc(req_buf_size);
while(rounds-- > 0)
{
// make request
gettimeofday(&start_ts,NULL);
make_request(sock,req_buf,req_buf_size);
// refresh byte count for round
// (thank you Ivan Golubev!)
total_bytes[0] = 0;
total_bytes[1] = 0;
// flags
int first_packet = 1;
int got_nth_packet = 0;
// receive response and measure metrics
while(select(FD_SETSIZE,&set,NULL,NULL,&timeout))
{
// reset timeout
timeout.tv_sec = TIMEOUT;
gettimeofday(&cur_ts,NULL);
bytes = read(sock,buf,BUF_SIZE);
// OPTION 1: we take the time of the n-th read() as the start time - to handle initial bursts and TCP slow-start
if(skips > 0)
{
skips--;
}
else
{
if(!got_nth_packet)
{
gettimeofday(&n_pack_ts,NULL);
got_nth_packet = 1;
}
else
{
bw_a = measure_bw(&n_pack_ts,&cur_ts,bytes,0);
}
}
// very first socket.read()
if(first_packet)
{
// RTT
rtl = measure_rtt(&start_ts,&cur_ts);
gettimeofday(&first_pack_ts,NULL);
first_packet = 0;
}
else
{
// OPTION 2: we take the request sent timestamp as the start time
bw_b = measure_bw(&first_pack_ts,&cur_ts,bytes,1);
}
}
}
// free
free(req_buf);
free(buf);
// close socket
close(sock);
printf("Option 1 Goodput: %f MB/s\nOption 2 Goodput: %f MB/s\nRTT: %d ms\n",bw_a,bw_b,(int)(rtl*1000));
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment