Skip to content

Commit e4c4cc0

Browse files
kleinwebyswlars
andauthored
Implement streaming json output (#1098)
Currently when enabling json output, the results are only written once the test concludes. This is done to output one full json document containing all relevant informations. To allow status output during the run while using json as output, this patch adds a newline-delimited JSON output. In order to achive this multiple event objects are emitted. These are serialized as json and printed with a newline seperating them. Each event contains a event name and its data. The following events have been introduced: start, interval, end, error, server_output_text and server_output_json. The data contains the relevant portion of the normal JSON output. Co-authored-by: swlars <[email protected]>
1 parent e07eb70 commit e4c4cc0

File tree

6 files changed

+103
-4
lines changed

6 files changed

+103
-4
lines changed

src/iperf.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,7 @@ struct iperf_test
344344
int bidirectional; /* --bidirectional */
345345
int verbose; /* -V option - verbose mode */
346346
int json_output; /* -J option - JSON output */
347+
int json_stream; /* --json-stream */
347348
int zerocopy; /* -Z option - use sendfile */
348349
int debug; /* -d option - enable debug */
349350
enum debug_level debug_level; /* -d option option - level of debug messages to show */

src/iperf3.1

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,11 @@ test by specifying the --get-server-output flag.
9696
Either the client or the server can produce its output in a JSON
9797
structure, useful for integration with other programs, by passing it
9898
the -J flag.
99-
Because the contents of the JSON structure are only completely known
99+
Normally the contents of the JSON structure are only competely known
100100
after the test has finished, no JSON output will be emitted until the
101101
end of the test.
102+
By enabling line-delimited JSON multiple objects will be emitted to
103+
provide a real-time parsable JSON output.
102104
.PP
103105
iperf3 has a (overly) large set of command-line options that can be
104106
used to set the parameters of a test.
@@ -157,6 +159,9 @@ give more detailed output
157159
.BR -J ", " --json " "
158160
output in JSON format
159161
.TP
162+
.BR --json-stream " "
163+
output in line-delimited JSON format
164+
.TP
160165
.BR --logfile " \fIfile\fR"
161166
send output to a log file.
162167
.TP

src/iperf_api.c

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ static int diskfile_recv(struct iperf_stream *sp);
102102
static int JSON_write(int fd, cJSON *json);
103103
static void print_interval_results(struct iperf_test *test, struct iperf_stream *sp, cJSON *json_interval_streams);
104104
static cJSON *JSON_read(int fd);
105+
static int JSONStream_Output(struct iperf_test *test, const char* event_name, cJSON* obj);
105106

106107

107108
/*************************** Print usage functions ****************************/
@@ -326,6 +327,12 @@ iperf_get_test_json_output_string(struct iperf_test *ipt)
326327
return ipt->json_output_string;
327328
}
328329

330+
int
331+
iperf_get_test_json_stream(struct iperf_test *ipt)
332+
{
333+
return ipt->json_stream;
334+
}
335+
329336
int
330337
iperf_get_test_zerocopy(struct iperf_test *ipt)
331338
{
@@ -682,6 +689,12 @@ iperf_set_test_json_output(struct iperf_test *ipt, int json_output)
682689
ipt->json_output = json_output;
683690
}
684691

692+
void
693+
iperf_set_test_json_stream(struct iperf_test *ipt, int json_stream)
694+
{
695+
ipt->json_stream = json_stream;
696+
}
697+
685698
int
686699
iperf_has_zerocopy( void )
687700
{
@@ -892,8 +905,12 @@ iperf_on_test_start(struct iperf_test *test)
892905
iperf_printf(test, test_start_time, test->protocol->name, test->num_streams, test->settings->blksize, test->omit, test->duration, test->settings->tos);
893906
}
894907
}
908+
if (test->json_stream) {
909+
JSONStream_Output(test, "start", test->json_start);
910+
}
895911
}
896912

913+
897914
/* This converts an IPv6 string address from IPv4-mapped format into regular
898915
** old IPv4 format, which is easier on the eyes of network veterans.
899916
**
@@ -1058,6 +1075,7 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv)
10581075
{"one-off", no_argument, NULL, '1'},
10591076
{"verbose", no_argument, NULL, 'V'},
10601077
{"json", no_argument, NULL, 'J'},
1078+
{"json-stream", no_argument, NULL, OPT_JSON_STREAM},
10611079
{"version", no_argument, NULL, 'v'},
10621080
{"server", no_argument, NULL, 's'},
10631081
{"client", required_argument, NULL, 'c'},
@@ -1207,6 +1225,10 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv)
12071225
case 'J':
12081226
test->json_output = 1;
12091227
break;
1228+
case OPT_JSON_STREAM:
1229+
test->json_output = 1;
1230+
test->json_stream = 1;
1231+
break;
12101232
case 'v':
12111233
printf("%s (cJSON %s)\n%s\n%s\n", version, cJSON_Version(), get_system_info(),
12121234
get_optional_features());
@@ -2736,6 +2758,29 @@ JSON_read(int fd)
27362758
return json;
27372759
}
27382760

2761+
/*************************************************************/
2762+
/**
2763+
* JSONStream_Output - outputs an obj as event without distrubing it
2764+
*/
2765+
2766+
static int
2767+
JSONStream_Output(struct iperf_test * test, const char * event_name, cJSON * obj)
2768+
{
2769+
cJSON *event = cJSON_CreateObject();
2770+
if (!event)
2771+
return -1;
2772+
cJSON_AddStringToObject(event, "event", event_name);
2773+
cJSON_AddItemReferenceToObject(event, "data", obj);
2774+
char *str = cJSON_PrintUnformatted(event);
2775+
if (str == NULL)
2776+
return -1;
2777+
fprintf(test->outfile, "%s\n", str);
2778+
iflush(test);
2779+
cJSON_free(str);
2780+
cJSON_Delete(event);
2781+
return 0;
2782+
}
2783+
27392784
/*************************************************************/
27402785
/**
27412786
* add_to_interval_list -- adds new interval to the interval_list
@@ -3400,6 +3445,7 @@ iperf_print_intermediate(struct iperf_test *test)
34003445

34013446
int lower_mode, upper_mode;
34023447
int current_mode;
3448+
int discard_json;
34033449

34043450
/*
34053451
* Due to timing oddities, there can be cases, especially on the
@@ -3445,11 +3491,20 @@ iperf_print_intermediate(struct iperf_test *test)
34453491
return;
34463492
}
34473493

3494+
/*
3495+
* When we use streamed json, we don't actually need to keep the interval
3496+
* results around unless we're the server and the client requested the server output.
3497+
*
3498+
* This avoids unneeded memory build up for long sessions.
3499+
*/
3500+
discard_json = test->json_stream == 1 && !(test->role == 's' && test->get_server_output);
3501+
34483502
if (test->json_output) {
34493503
json_interval = cJSON_CreateObject();
34503504
if (json_interval == NULL)
34513505
return;
3452-
cJSON_AddItemToArray(test->json_intervals, json_interval);
3506+
if (!discard_json)
3507+
cJSON_AddItemToArray(test->json_intervals, json_interval);
34533508
json_interval_streams = cJSON_CreateArray();
34543509
if (json_interval_streams == NULL)
34553510
return;
@@ -3600,6 +3655,11 @@ iperf_print_intermediate(struct iperf_test *test)
36003655
}
36013656
}
36023657
}
3658+
3659+
if (test->json_stream)
3660+
JSONStream_Output(test, "interval", json_interval);
3661+
if (discard_json)
3662+
cJSON_Delete(json_interval);
36033663
}
36043664

36053665
/**
@@ -4830,7 +4890,35 @@ iperf_json_finish(struct iperf_test *test)
48304890
cJSON_Delete(test->json_top);
48314891
test->json_top = NULL;
48324892
}
4833-
test->json_start = test->json_connected = test->json_intervals = test->json_server_output = test->json_end = NULL;
4893+
// Get ASCII rendering of JSON structure. Then make our
4894+
// own copy of it and return the storage that cJSON allocated
4895+
// on our behalf. We keep our own copy around.
4896+
char *str = cJSON_Print(test->json_top);
4897+
if (str == NULL)
4898+
return -1;
4899+
test->json_output_string = strdup(str);
4900+
cJSON_free(str);
4901+
if (test->json_output_string == NULL)
4902+
return -1;
4903+
if (test->json_stream) {
4904+
cJSON *error = cJSON_GetObjectItem(test->json_top, "error");
4905+
if (error) {
4906+
JSONStream_Output(test, "error", error);
4907+
}
4908+
if (test->json_server_output) {
4909+
JSONStream_Output(test, "server_output_json", test->json_server_output);
4910+
}
4911+
if (test->server_output_text) {
4912+
JSONStream_Output(test, "server_output_text", cJSON_CreateString(test->server_output_text));
4913+
}
4914+
JSONStream_Output(test, "end", test->json_end);
4915+
}
4916+
else {
4917+
fprintf(test->outfile, "%s\n", test->json_output_string);
4918+
iflush(test);
4919+
}
4920+
cJSON_Delete(test->json_top);
4921+
test->json_top = test->json_start = test->json_connected = test->json_intervals = test->json_server_output = test->json_end = NULL;
48344922
return 0;
48354923
}
48364924

src/iperf_api.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ typedef atomic_uint_fast64_t atomic_iperf_size_t;
100100
#define OPT_IDLE_TIMEOUT 25
101101
#define OPT_DONT_FRAGMENT 26
102102
#define OPT_RCV_TIMEOUT 27
103-
#define OPT_SND_TIMEOUT 28
103+
#define OPT_JSON_STREAM 28
104+
#define OPT_SND_TIMEOUT 29
104105

105106
/* states */
106107
#define TEST_START 1
@@ -151,6 +152,7 @@ char* iperf_get_test_template( struct iperf_test* ipt );
151152
int iperf_get_test_protocol_id( struct iperf_test* ipt );
152153
int iperf_get_test_json_output( struct iperf_test* ipt );
153154
char* iperf_get_test_json_output_string ( struct iperf_test* ipt );
155+
int iperf_get_test_json_stream( struct iperf_test* ipt );
154156
int iperf_get_test_zerocopy( struct iperf_test* ipt );
155157
int iperf_get_test_get_server_output( struct iperf_test* ipt );
156158
char iperf_get_test_unit_format(struct iperf_test *ipt);
@@ -195,6 +197,7 @@ void iperf_set_test_server_hostname( struct iperf_test* ipt, const char* server_
195197
void iperf_set_test_template( struct iperf_test *ipt, const char *tmp_template );
196198
void iperf_set_test_reverse( struct iperf_test* ipt, int reverse );
197199
void iperf_set_test_json_output( struct iperf_test* ipt, int json_output );
200+
void iperf_set_test_json_stream( struct iperf_test* ipt, int json_stream );
198201
int iperf_has_zerocopy( void );
199202
void iperf_set_test_zerocopy( struct iperf_test* ipt, int zerocopy );
200203
void iperf_set_test_get_server_output( struct iperf_test* ipt, int get_server_output );

src/iperf_locale.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ const char usage_longstr[] = "Usage: iperf3 [-s|-c host] [options]\n"
126126
#endif /* HAVE_SO_BINDTODEVICE */
127127
" -V, --verbose more detailed output\n"
128128
" -J, --json output in JSON format\n"
129+
" --json-stream output in line-delimited JSON format\n"
129130
" --logfile f send output to a log file\n"
130131
" --forceflush force flushing output at every interval\n"
131132
" --timestamps<=format> emit a timestamp at the start of each output line\n"

src/libiperf.3

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Setting test parameters:
3232
void iperf_set_test_blksize( struct iperf_test *t, int blksize );
3333
void iperf_set_test_num_streams( struct iperf_test *t, int num_streams );
3434
void iperf_set_test_json_output( struct iperf_test *t, int json_output );
35+
void iperf_set_test_json_stream( struct iperf_test *t, int json_stream );
3536
int iperf_has_zerocopy( void );
3637
void iperf_set_test_zerocopy( struct iperf_test* t, int zerocopy );
3738
void iperf_set_test_tos( struct iperf_test* t, int tos );

0 commit comments

Comments
 (0)