1
1

Implement a --get-server-output flag that allows the client to

retrieve (most of) the output emitted by the server.

If the server was invoked with the --json flag, the output will be in
JSON, otherwise it will be in the human-readable format.

If the client was invoked with the --json flag, the output will be
contained within the JSON output structure, otherwise it will be
appended (in whatever format) to the bottom of the human-readable
output.

Because of the sequencing of the output generation and display, the
server-side output includes only the starting output, interval
statistics and summaries, but not the overall summaries.  (The overall
summaries were already displayed in the client's output.)

Towards issue #160.
Этот коммит содержится в:
Bruce A. Mah 2014-06-05 09:48:55 -07:00
родитель c110a92d08
Коммит ba8d6e6246
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4984910A8CAAEE8A
5 изменённых файлов: 195 добавлений и 9 удалений

Просмотреть файл

@ -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 */

Просмотреть файл

@ -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.

Просмотреть файл

@ -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;
}

Просмотреть файл

@ -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

Просмотреть файл

@ -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