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.
Этот коммит содержится в:
родитель
c110a92d08
Коммит
ba8d6e6246
14
src/iperf.h
14
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 */
|
||||
|
@ -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.
|
||||
|
177
src/iperf_api.c
177
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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
Загрузка…
x
Ссылка в новой задаче
Block a user