diff --git a/src/iperf.h b/src/iperf.h index f394ba1..9d7eb18 100644 --- a/src/iperf.h +++ b/src/iperf.h @@ -157,6 +157,11 @@ struct protocol { SLIST_ENTRY(protocol) protocols; }; +struct iperf_textline { + char *line; + TAILQ_ENTRY(iperf_textline) textlineentries; +}; + struct iperf_test { char role; /* 'c' lient or 's' erver */ @@ -193,6 +198,7 @@ struct iperf_test int json_output; /* -J option - JSON output */ int zerocopy; /* -Z option - use sendfile */ int debug; /* -d option - enable debug */ + int get_server_output; /* --get-server-output */ int multisend; @@ -239,6 +245,14 @@ struct iperf_test cJSON *json_start; cJSON *json_intervals; cJSON *json_end; + + /* Server output (use on client side only) */ + char *server_output_text; + cJSON *json_server_output; + + /* Server output (use on server side only) */ + TAILQ_HEAD(iperf_textlisthead, iperf_textline) server_output_list; + }; /* default settings */ diff --git a/src/iperf3.1 b/src/iperf3.1 index a11baac..b252049 100644 --- a/src/iperf3.1 +++ b/src/iperf3.1 @@ -154,6 +154,15 @@ Set the congestion control algorithm (Linux and FreeBSD only). An older .B --linux-congestion synonym for this flag is accepted but is deprecated. +.TP +.BR "--get-server-output" +Get the output from the server. +The output format is determined by the server (in particular, if the +server was invoked with the \fB--json\fR flag, the output will be in +JSON format, otherwise it will be in human-readable format). +If the client is run with \fB--json\fR, the server output is included +in a JSON object; otherwise it is appended at the bottom of the +human-readable output. .SH AUTHORS Iperf was originally written by Mark Gates and Alex Warshavsky. diff --git a/src/iperf_api.c b/src/iperf_api.c index 1f13f09..18481f4 100644 --- a/src/iperf_api.c +++ b/src/iperf_api.c @@ -217,6 +217,12 @@ iperf_get_test_zerocopy(struct iperf_test *ipt) return ipt->zerocopy; } +int +iperf_get_test_get_server_output(struct iperf_test *ipt) +{ + return ipt->get_server_output; +} + char iperf_get_test_unit_format(struct iperf_test *ipt) { @@ -358,6 +364,12 @@ iperf_set_test_zerocopy(struct iperf_test *ipt, int zerocopy) ipt->zerocopy = zerocopy; } +void +iperf_set_test_get_server_output(struct iperf_test *ipt, int get_server_output) +{ + ipt->get_server_output = get_server_output; +} + void iperf_set_test_unit_format(struct iperf_test *ipt, char unit_format) { @@ -572,6 +584,7 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) #endif {"pidfile", required_argument, NULL, 'I'}, {"logfile", required_argument, NULL, OPT_LOGFILE}, + {"get-server-output", no_argument, NULL, OPT_GET_SERVER_OUTPUT}, {"debug", no_argument, NULL, 'd'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0} @@ -805,6 +818,10 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) case OPT_LOGFILE: test->logfile = strdup(optarg); break; + case OPT_GET_SERVER_OUTPUT: + test->get_server_output = 1; + client_flag = 1; + break; case 'h': default: usage_long(); @@ -1166,6 +1183,13 @@ send_parameters(struct iperf_test *test) cJSON_AddStringToObject(j, "title", test->title); if (test->congestion) cJSON_AddStringToObject(j, "congestion", test->congestion); + if (test->get_server_output) + cJSON_AddIntToObject(j, "get_server_output", iperf_get_test_get_server_output(test)); + + if (test->debug) { + printf("send_parameters:\n%s\n", cJSON_Print(j)); + } + if (JSON_write(test->ctrl_sck, j) < 0) { i_errno = IESENDPARAMS; r = -1; @@ -1189,6 +1213,10 @@ get_parameters(struct iperf_test *test) i_errno = IERECVPARAMS; r = -1; } else { + if (test->debug) { + printf("get_parameters:\n%s\n", cJSON_Print(j)); + } + if ((j_p = cJSON_GetObjectItem(j, "tcp")) != NULL) set_protocol(test, Ptcp); if ((j_p = cJSON_GetObjectItem(j, "udp")) != NULL) @@ -1229,6 +1257,8 @@ get_parameters(struct iperf_test *test) test->title = strdup(j_p->valuestring); if ((j_p = cJSON_GetObjectItem(j, "congestion")) != NULL) test->congestion = strdup(j_p->valuestring); + if ((j_p = cJSON_GetObjectItem(j, "get_server_output")) != NULL) + iperf_set_test_get_server_output(test, 1); if (test->sender && test->protocol->id == Ptcp && has_tcpinfo_retransmits()) test->sender_has_retransmits = 1; cJSON_Delete(j); @@ -1263,6 +1293,34 @@ send_results(struct iperf_test *test) else sender_has_retransmits = test->sender_has_retransmits; cJSON_AddIntToObject(j, "sender_has_retransmits", sender_has_retransmits); + + /* If on the server and sending server output, then do this */ + if (test->role == 's' && test->get_server_output) { + if (test->json_output) { + /* Add JSON output */ + cJSON_AddItemReferenceToObject(j, "server_output_json", test->json_top); + } + else { + /* Add textual output */ + size_t buflen = 0; + + /* Figure out how much room we need to hold the complete output string */ + struct iperf_textline *t; + TAILQ_FOREACH(t, &(test->server_output_list), textlineentries) { + buflen += strlen(t->line); + } + + /* Allocate and build it up from the component lines */ + char *output = calloc(buflen + 1, 1); + TAILQ_FOREACH(t, &(test->server_output_list), textlineentries) { + strncat(output, t->line, buflen); + buflen -= strlen(t->line); + } + + cJSON_AddStringToObject(j, "server_output_text", output); + } + } + j_streams = cJSON_CreateArray(); if (j_streams == NULL) { i_errno = IEPACKAGERESULTS; @@ -1286,6 +1344,9 @@ send_results(struct iperf_test *test) cJSON_AddIntToObject(j_stream, "packets", sp->packet_count); } } + if (r == 0 && test->debug) { + printf("send_results\n%s\n", cJSON_Print(j)); + } if (r == 0 && JSON_write(test->ctrl_sck, j) < 0) { i_errno = IESENDRESULTS; r = -1; @@ -1317,6 +1378,7 @@ get_results(struct iperf_test *test) cJSON *j_jitter; cJSON *j_errors; cJSON *j_packets; + cJSON *j_server_output; int sid, cerror, pcount; double jitter; iperf_size_t bytes_transferred; @@ -1336,6 +1398,10 @@ get_results(struct iperf_test *test) i_errno = IERECVRESULTS; r = -1; } else { + if (test->debug) { + printf("get_results\n%s\n", cJSON_Print(j)); + } + test->remote_cpu_util[0] = j_cpu_util_total->valuefloat; test->remote_cpu_util[1] = j_cpu_util_user->valuefloat; test->remote_cpu_util[2] = j_cpu_util_system->valuefloat; @@ -1389,6 +1455,24 @@ get_results(struct iperf_test *test) } } } + /* + * If we're the client and we're supposed to get remote results, + * look them up and process accordingly. + */ + if (test->role == 'c' && iperf_get_test_get_server_output(test)) { + /* Look for JSON. If we find it, grab the object so it doesn't get deleted. */ + j_server_output = cJSON_DetachItemFromObject(j, "server_output_json"); + if (j_server_output != NULL) { + test->json_server_output = j_server_output; + } + else { + /* No JSON, look for textual output. Make a copy of the text for later. */ + j_server_output = cJSON_GetObjectItem(j, "server_output_text"); + if (j_server_output != NULL) { + test->server_output_text = strdup(j_server_output->valuestring); + } + } + } } } cJSON_Delete(j); @@ -1652,6 +1736,8 @@ iperf_defaults(struct iperf_test *testp) testp->on_connect = iperf_on_connect; testp->on_test_finish = iperf_on_test_finish; + TAILQ_INIT(&testp->server_output_list); + return 0; } @@ -1695,11 +1781,25 @@ iperf_free_test(struct iperf_test *test) free(prot); } + if (test->server_output_text) { + free(test->server_output_text); + test->server_output_text = NULL; + } + if (test->json_output_string) { free(test->json_output_string); test->json_output_string = NULL; } + /* Free output line buffers, if any (on the server only) */ + struct iperf_textline *t; + while (!TAILQ_EMPTY(&test->server_output_list)) { + t = TAILQ_FIRST(&test->server_output_list); + TAILQ_REMOVE(&test->server_output_list, t, textlineentries); + free(t->line); + free(t); + } + /* XXX: Why are we setting these values to NULL? */ // test->streams = NULL; test->stats_callback = NULL; @@ -1788,6 +1888,15 @@ iperf_reset_test(struct iperf_test *test) test->settings->mss = 0; memset(test->cookie, 0, COOKIE_SIZE); test->multisend = 10; /* arbitrary */ + + /* Free output line buffers, if any (on the server only) */ + struct iperf_textline *t; + while (!TAILQ_EMPTY(&test->server_output_list)) { + t = TAILQ_FIRST(&test->server_output_list); + TAILQ_REMOVE(&test->server_output_list, t, textlineentries); + free(t->line); + free(t); + } } @@ -2143,8 +2252,24 @@ iperf_print_results(struct iperf_test *test) if (test->json_output) cJSON_AddItemToObject(test->json_end, "cpu_utilization_percent", iperf_json_printf("host_total: %f host_user: %f host_system: %f remote_total: %f remote_user: %f remote_system: %f", (double) test->cpu_util[0], (double) test->cpu_util[1], (double) test->cpu_util[2], (double) test->remote_cpu_util[0], (double) test->remote_cpu_util[1], (double) test->remote_cpu_util[2])); - else if (test->verbose) - iprintf(test, report_cpu, report_local, test->sender?report_sender:report_receiver, test->cpu_util[0], test->cpu_util[1], test->cpu_util[2], report_remote, test->sender?report_receiver:report_sender, test->remote_cpu_util[0], test->remote_cpu_util[1], test->remote_cpu_util[2]); + else { + if (test->verbose) { + iprintf(test, report_cpu, report_local, test->sender?report_sender:report_receiver, test->cpu_util[0], test->cpu_util[1], test->cpu_util[2], report_remote, test->sender?report_receiver:report_sender, test->remote_cpu_util[0], test->remote_cpu_util[1], test->remote_cpu_util[2]); + } + + /* Print server output if we're on the client and it was requested/provided */ + if (test->role == 'c' && iperf_get_test_get_server_output(test)) { + if (test->json_server_output) { + iprintf(test, "\nServer JSON output:\n%s\n", cJSON_Print(test->json_server_output)); + cJSON_Delete(test->json_server_output); + test->json_server_output = NULL; + } + if (test->server_output_text) { + iprintf(test, "\nServer output:\n%s\n", test->server_output_text); + test->server_output_text = NULL; + } + } + } } /**************************************************************************/ @@ -2572,13 +2697,20 @@ iperf_json_start(struct iperf_test *test) int iperf_json_finish(struct iperf_test *test) { + /* Include server output */ + if (test->json_server_output) { + cJSON_AddItemToObject(test->json_top, "server_output_json", test->json_server_output); + } + if (test->server_output_text) { + cJSON_AddStringToObject(test->json_top, "server_output_text", test->server_output_text); + } test->json_output_string = cJSON_Print(test->json_top); if (test->json_output_string == NULL) return -1; fprintf(test->outfile, "%s\n", test->json_output_string); iflush(test); cJSON_Delete(test->json_top); - test->json_top = test->json_start = test->json_intervals = test->json_end = NULL; + test->json_top = test->json_start = test->json_intervals = test->json_server_output = test->json_end = NULL; return 0; } @@ -2654,13 +2786,40 @@ int iprintf(struct iperf_test *test, const char* format, ...) { va_list argp; - int r; + int r = -1; - if (test->title) - fprintf(test->outfile, "%s: ", test->title); - va_start(argp, format); - r = vfprintf(test->outfile, format, argp); - va_end(argp); + /* + * There are roughly two use cases here. If we're the client, + * want to print stuff directly to the output stream. + * If we're the sender we might need to buffer up output to send + * to the client. + * + * This doesn't make a whole lot of difference except there are + * some chunks of output on the client (on particular the whole + * of the server output with --get-server-output) that could + * easily exceed the size of the line buffer, but which don't need + * to be buffered up anyway. + */ + if (test->role == 'c') { + if (test->title) + fprintf(test->outfile, "%s: ", test->title); + va_start(argp, format); + r = vfprintf(test->outfile, format, argp); + va_end(argp); + } + else if (test->role == 's') { + char linebuffer[1024]; + va_start(argp, format); + r = vsnprintf(linebuffer, sizeof(linebuffer), format, argp); + va_end(argp); + fprintf(test->outfile, "%s", linebuffer); + + if (test->role == 's' && iperf_get_test_get_server_output) { + struct iperf_textline *l = (struct iperf_textline *) malloc(sizeof(struct iperf_textline)); + l->line = strdup(linebuffer); + TAILQ_INSERT_TAIL(&(test->server_output_list), l, textlineentries); + } + } return r; } diff --git a/src/iperf_api.h b/src/iperf_api.h index 644ca49..c0c5089 100644 --- a/src/iperf_api.h +++ b/src/iperf_api.h @@ -28,6 +28,7 @@ struct iperf_stream; /* short option equivalents, used to support options that only have long form */ #define OPT_SCTP 1 #define OPT_LOGFILE 2 +#define OPT_GET_SERVER_OUTPUT 3 /* states */ #define TEST_START 1 @@ -70,6 +71,7 @@ int iperf_get_test_protocol_id( struct iperf_test* ipt ); int iperf_get_test_json_output( struct iperf_test* ipt ); char* iperf_get_test_json_output_string ( struct iperf_test* ipt ); int iperf_get_test_zerocopy( struct iperf_test* ipt ); +int iperf_get_test_get_server_output( struct iperf_test* ipt ); /* Setter routines for some fields inside iperf_test. */ void iperf_set_verbose( struct iperf_test* ipt, int verbose ); @@ -91,6 +93,7 @@ void iperf_set_test_reverse( struct iperf_test* ipt, int reverse ); void iperf_set_test_json_output( struct iperf_test* ipt, int json_output ); int iperf_has_zerocopy( void ); void iperf_set_test_zerocopy( struct iperf_test* ipt, int zerocopy ); +void iperf_set_test_get_server_output( struct iperf_test* ipt, int get_server_output ); /** * exchange_parameters - handles the param_Exchange part for client diff --git a/src/locale.c b/src/locale.c index e5a5984..cff75c0 100644 --- a/src/locale.c +++ b/src/locale.c @@ -125,6 +125,7 @@ const char usage_longstr[] = "Usage: iperf [-s|-c host] [options]\n" " -Z, --zerocopy use a 'zero copy' method of sending data\n" " -O, --omit N omit the first n seconds\n" " -T, --title str prefix every output line with this string\n" + " --get-server-output get results from server\n" #ifdef NOT_YET_SUPPORTED /* still working on these */ #endif