Last active
November 17, 2023 07:44
-
-
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/
This file contains 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
/* 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; | |
} |
This file contains 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
/* 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; | |
} |
This file contains 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
/* 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