Skip to content

Commit

Permalink
Neatstat - Cross-platform TCP statistics (#328)
Browse files Browse the repository at this point in the history
* Added print of json neat statistics to client_http_get for testing and debug purposes (testing multiple flows statistics)

* Added option explanation for json debug option

* Added statistics for global bytes sent and global bytes received

* Added flow properties to the statistics

* Added BSD support for TCP_INFO to neatstat

* Resolved OSX-specific issues with TCP_INFO

* Fixed BSD problems with fetching TCP_INFO stats
  • Loading branch information
apetlund authored and weinrank committed Jun 20, 2017
1 parent c29ab29 commit feb4764
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 11 deletions.
34 changes: 33 additions & 1 deletion examples/client_http_get.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
-n : number of requests/flows
-R : receive buffer size in byte
-v : log level (0 .. 2)
-J : Print json stats on receiving data
**********************************************************************/

Expand All @@ -38,6 +39,7 @@ static int result = 0;
static uint32_t config_rcv_buffer_size = 32*1024*1024; // 32MB rcv buffer
static uint32_t config_max_flows = 2000;
static uint8_t config_log_level = 0;
static uint8_t config_json_stats = 0;
static char request[512];
static uint32_t flows_active = 0;
static const char *request_tail = "HTTP/1.0\r\nUser-agent: libneat\r\nConnection: close\r\n\r\n";
Expand Down Expand Up @@ -84,6 +86,23 @@ on_error(struct neat_flow_operations *opCB)
return NEAT_OK;
}

static void
print_neat_stats(struct neat_flow_operations *opCB)
{
neat_error_code error;

char* stats = NULL;
error = neat_get_stats(opCB->ctx, &stats);
if (error != NEAT_OK){
printf("NEAT ERROR: %i\n", (int)error);
return;
} else if (stats != NULL) {
printf("json %s\n", stats);
}
// Need to free the string allocated by jansson
free(stats);
}

static neat_error_code
on_readable(struct neat_flow_operations *opCB)
{
Expand Down Expand Up @@ -163,6 +182,11 @@ on_readable(struct neat_flow_operations *opCB)
// fwrite(buffer, sizeof(char), bytes_read, stdout);
}
}

if (config_json_stats){
print_neat_stats(opCB);
}

return NEAT_OK;
}

Expand Down Expand Up @@ -205,6 +229,8 @@ print_timer_stats(uv_timer_t *handle)
uv_timer_again(&(stat->timer));
}



static neat_error_code
on_connected(struct neat_flow_operations *opCB)
{
Expand Down Expand Up @@ -275,7 +301,7 @@ main(int argc, char *argv[])

snprintf(request, sizeof(request), "GET %s %s", "/", request_tail);

while ((arg = getopt(argc, argv, "P:R:u:n:v:")) != -1) {
while ((arg = getopt(argc, argv, "P:R:u:n:Jv:")) != -1) {
switch(arg) {
case 'P':
if (read_file(optarg, &arg_property) < 0) {
Expand All @@ -300,6 +326,12 @@ main(int argc, char *argv[])
}
fprintf(stderr, "%s - option - number of flows: %d\n", __func__, num_flows);
break;
case 'J':
config_json_stats = 1;
if (config_log_level >= 1) {
fprintf(stderr, "%s - option - json stats when reading data enabled\n", __func__);
}
break;
case 'v':
config_log_level = atoi(optarg);
if (config_log_level >= 1) {
Expand Down
54 changes: 54 additions & 0 deletions neat_bsd.c
Original file line number Diff line number Diff line change
Expand Up @@ -324,3 +324,57 @@ struct neat_ctx
}
return ctx;
}

/**Get the BSD TCP_INFO and copy the relevant fields into the neat-specific
* TCP_INFO struct. Return pointer to the struct with the copied data.
* NOTE: TCP_INFO in BSD isa tagged as unstable, potentially leading to a
* need to update this code in case of API changes*/
int bsd_get_tcp_info(neat_flow *flow, struct neat_tcp_info *neat_tcp_info)
{

int tcp_info_length;
#if defined(__APPLE__)
struct tcp_connection_info tcpi;
tcp_info_length = sizeof(struct tcp_connection_info);
#else
struct tcp_info tcpi;
tcp_info_length = sizeof(struct tcp_info);
#endif

neat_log(flow->ctx, NEAT_LOG_DEBUG, "%s", __func__);


// if (getsockopt(flow->socket->fd, IPPROTO_TCP, TCP_CONNECTION_INFO, (void *)&tcpi,
// (socklen_t *)&tcp_info_length ))
// return 1; /* failed! */

#if defined(__APPLE__)
if (getsockopt(flow->socket->fd, IPPROTO_TCP, TCP_CONNECTION_INFO, (void *)&tcpi,
(socklen_t *)&tcp_info_length ))
#else
if (getsockopt(flow->socket->fd, IPPROTO_TCP, TCP_INFO, (void *)&tcpi,
(socklen_t *)&tcp_info_length ))
#endif
return 1; /* failed! */


/* Copy relevant fields between structs
* OSX has severly more limited Statistics than Linux and *BSD */
#if defined(__APPLE__)
neat_tcp_info->tcpi_rttvar = tcpi.tcpi_rttvar;
neat_tcp_info->tcpi_snd_ssthresh = tcpi.tcpi_snd_ssthresh;
neat_tcp_info->tcpi_snd_cwnd = tcpi.tcpi_snd_cwnd;
#else
neat_tcp_info->tcpi_pmtu = tcpi.__tcpi_pmtu;
neat_tcp_info->tcpi_rcv_ssthresh = tcpi.__tcpi_rcv_ssthresh;
neat_tcp_info->tcpi_rtt = tcpi.tcpi_rtt;
neat_tcp_info->tcpi_rttvar = tcpi.tcpi_rttvar;
neat_tcp_info->tcpi_snd_ssthresh = tcpi.tcpi_snd_ssthresh;
neat_tcp_info->tcpi_snd_cwnd = tcpi.tcpi_snd_cwnd;
neat_tcp_info->tcpi_advmss = tcpi.__tcpi_advmss;
neat_tcp_info->tcpi_reordering = tcpi.__tcpi_reordering;;
neat_tcp_info->tcpi_total_retrans = tcpi.tcpi_snd_rexmitpack;
#endif
return 0;
}

3 changes: 3 additions & 0 deletions neat_bsd_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@

struct neat_ctx *neat_bsd_init_ctx(struct neat_ctx *nic);

/* Get statistics from BSD TCP_INFO */
int bsd_get_tcp_info(struct neat_flow * , struct neat_tcp_info *);

#endif
2 changes: 0 additions & 2 deletions neat_linux.c
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,6 @@ int linux_get_tcp_info(neat_flow *flow, struct neat_tcp_info *neat_tcp_info)
neat_tcp_info->tcpi_snd_cwnd = tcpi.tcpi_snd_cwnd;
neat_tcp_info->tcpi_advmss = tcpi.tcpi_advmss;
neat_tcp_info->tcpi_reordering = tcpi.tcpi_reordering;
neat_tcp_info->tcpi_rcv_rtt = tcpi.tcpi_rcv_rtt;
neat_tcp_info->tcpi_rcv_space = tcpi.tcpi_rcv_space;
neat_tcp_info->tcpi_total_retrans = tcpi.tcpi_total_retrans;

return RETVAL_SUCCESS;
Expand Down
46 changes: 41 additions & 5 deletions neat_stat.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
#include "neat_linux_internal.h"
#endif

#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__)
#include "neat_bsd_internal.h"
#endif


/* This function assumes it is only called when the flow is a TCP flow */
static int
get_tcp_info(neat_flow *flow, struct neat_tcp_info *tcpinfo)
Expand All @@ -19,15 +24,32 @@ get_tcp_info(neat_flow *flow, struct neat_tcp_info *tcpinfo)
* relevant fields of the neat-generic tcp-info struct */
neat_log(flow->ctx, NEAT_LOG_DEBUG, "%s", __func__);

memset(tcpinfo, 0, sizeof(struct neat_tcp_info));

#ifdef __linux__
return linux_get_tcp_info(flow, tcpinfo);
#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__)
return bsd_get_tcp_info(flow, tcpinfo);
#else
// TODO: implement error reporting for not-supported OSes
memset(tcpinfo, 0, sizeof(struct neat_tcp_info));

#endif


return RETVAL_FAILURE;
}

static int collect_global_statistics(struct neat_ctx *ctx, struct neat_global_statistics *gstats)
{
struct neat_flow *flow;

LIST_FOREACH(flow, &ctx->flows, next_flow) {
gstats->global_bytes_received += flow->flow_stats.bytes_received;
gstats->global_bytes_sent += flow->flow_stats.bytes_sent;
}

return NEAT_OK;
}
/* Traverse the relevant subsystems of NEAT and gather the stats
then format the stats as a json string to return */
void
Expand All @@ -36,6 +58,7 @@ neat_stats_build_json(struct neat_ctx *ctx, char **json_stats)
json_t *json_root, *protostat, *newflow;
struct neat_flow *flow;
struct neat_tcp_info *neat_tcpi;
struct neat_global_statistics gstats;
uint flowcount;
char flow_name[128];

Expand All @@ -44,27 +67,38 @@ neat_stats_build_json(struct neat_ctx *ctx, char **json_stats)
flowcount = 0;
json_root = json_object();

/* Collect global statistics
* - Can be inlined with JSON generation to avoid having 2 passes */
memset(&gstats, 0, sizeof(struct neat_global_statistics));
collect_global_statistics(ctx, &gstats);

LIST_FOREACH(flow, &ctx->flows, next_flow) {
flowcount++;

/* Create entries for flow#n in a separate object */
/* Create entries for flow#n in a separate object
* TODO: Make each flow generate a json object containing its own properties */
newflow = json_object();;

json_object_set_new(newflow, "flow number", json_integer( flowcount));
json_object_set_new(newflow, "remote_host", json_string( flow->name ));
json_object_set_new(newflow, "socket type", json_integer( flow->socket->type ));
json_object_set_new(newflow, "sock_protocol", json_integer( neat_stack_to_protocol(flow->socket->stack)));
json_object_set_new(newflow, "port", json_integer( flow->port ));
json_object_set_new(newflow, "port", json_integer( flow->port )) ;
json_object_set_new(newflow, "ecn", json_integer( flow->ecn ));
json_object_set_new(newflow, "qos", json_integer( flow->qos ));
json_object_set_new(newflow, "write_size", json_integer( flow->socket->write_size));
json_object_set_new(newflow, "read_size", json_integer( flow->socket->read_size));
json_object_set_new(newflow, "bytes sent", json_integer( flow->flow_stats.bytes_sent));
json_object_set_new(newflow, "bytes received", json_integer( flow->flow_stats.bytes_received ));
json_object_set_new(newflow, "priority", json_real( flow->priority ));

snprintf(flow_name, 128, "flow-%d", flowcount);
json_object_set_new(json_root, flow_name, newflow);
json_object_set(newflow, "flow_properties", flow->properties);
/* Gather stack-specific info */
switch (flow->socket->stack) {
case NEAT_STACK_UDP:
/* Any UDP-specific statistics?*/
break;
case NEAT_STACK_TCP:
{
Expand All @@ -85,8 +119,6 @@ neat_stats_build_json(struct neat_ctx *ctx, char **json_stats)
json_object_set_new(protostat, "snd_cwnd", json_integer(neat_tcpi->tcpi_snd_cwnd));
json_object_set_new(protostat, "advmss", json_integer(neat_tcpi->tcpi_advmss));
json_object_set_new(protostat, "reordering", json_integer(neat_tcpi->tcpi_reordering));
json_object_set_new(protostat, "rcv_rtt", json_integer(neat_tcpi->tcpi_rcv_rtt));
json_object_set_new(protostat, "rcv_space", json_integer(neat_tcpi->tcpi_rcv_space));
json_object_set_new(protostat, "total retrans", json_integer(neat_tcpi->tcpi_total_retrans));

json_object_set_new(newflow, "tcpstats", protostat);
Expand All @@ -98,12 +130,16 @@ neat_stats_build_json(struct neat_ctx *ctx, char **json_stats)
case NEAT_STACK_SCTP:
break;
case NEAT_STACK_UDPLITE:
/* Any UDPLite-specific statistics? */
break;
case NEAT_STACK_SCTP_UDP:
break;
}
}
/* Global statistics */
json_object_set_new( json_root, "Number of flows", json_integer( flowcount ));
json_object_set_new( json_root, "Total bytes sent", json_integer(gstats.global_bytes_sent));
json_object_set_new( json_root, "Total bytes received", json_integer(gstats.global_bytes_received));

/* Callers must remember to free the output */
*json_stats = json_dumps(json_root, JSON_INDENT(4));
Expand Down
8 changes: 5 additions & 3 deletions neat_stat.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@ struct neat_tcp_info {
uint32_t tcpi_snd_cwnd;
uint32_t tcpi_advmss;
uint32_t tcpi_reordering;

uint32_t tcpi_rcv_rtt;
uint32_t tcpi_rcv_space;
uint32_t tcpi_total_retrans;
};

Expand All @@ -33,6 +30,11 @@ struct neat_flow_statistics {
uint64_t bytes_received;
};

struct neat_global_statistics {
uint64_t global_bytes_sent;
uint64_t global_bytes_received;
};

void neat_stats_build_json(struct neat_ctx *ctx, char **json_stats);


Expand Down

0 comments on commit feb4764

Please sign in to comment.