Merge branch 'json-output' of https://github.com/Reperator/fping into json-output

json-output
David Schweikert 5 years ago
commit bfe7296c60

@ -0,0 +1,135 @@
#!/usr/bin/perl -w
use Test::Command;
use Test::More;
plan tests => 18;
# summary
{
my $cmd = Test::Command->new(cmd => "fping -c 2 -J 127.0.0.1");
$cmd->exit_is_num(0);
$cmd->stdout_like(qr{\{
"hosts": \{
"127\.0\.0\.1": \{
"xmt": 2,
"rcv": 2,
"loss_percentage": 0,
"min": \d.\d+,
"avg": \d.\d+,
"max": \d.\d+
\}
\}
\}}
);
$cmd->stderr_is_eq("");
}
# all RTTs
{
my $cmd = Test::Command->new(cmd => "fping -C 2 -J 127.0.0.1");
$cmd->exit_is_num(0);
$cmd->stdout_like(qr{\{
"hosts": \{
"127\.0\.0\.1": \[
\d.\d+,
\d.\d+
\]
\}
\}}
);
$cmd->stderr_is_eq("");
}
# summary with stats and outage
{
my $cmd = Test::Command->new(cmd => "fping -c 2 -s -o -J 127.0.0.1");
$cmd->exit_is_num(0);
$cmd->stdout_like(qr{\{
"hosts": \{
"127\.0\.0\.1": \{
"xmt": 2,
"rcv": 2,
"loss_percentage": 0,
"outage": 0,
"min": \d.\d+,
"avg": \d.\d+,
"max": \d.\d+
\}
\},
"stats": \{
"targets": 1,
"alive": 1,
"unreachable": 0,
"unknown_addresses": 0,
"timeouts": 0,
"icmp_echos_sent": 2,
"icmp_echo_replies_received": 2,
"other_icmp_received": 0,
"min_rtt": \d.\d+,
"avg_rtt": \d.\d+,
"max_rtt": \d.\d+,
"elapsed_real_time": \d.\d+
\}
\}}
);
$cmd->stderr_is_eq("");
}
# all RTTs with stats
{
my $cmd = Test::Command->new(cmd => "fping -C 2 -s -J 127.0.0.1");
$cmd->exit_is_num(0);
$cmd->stdout_like(qr{\{
"hosts": \{
"127\.0\.0\.1": \[
\d.\d+,
\d.\d+
\]
\},
"stats": \{
"targets": 1,
"alive": 1,
"unreachable": 0,
"unknown_addresses": 0,
"timeouts": 0,
"icmp_echos_sent": 2,
"icmp_echo_replies_received": 2,
"other_icmp_received": 0,
"min_rtt": \d.\d+,
"avg_rtt": \d.\d+,
"max_rtt": \d.\d+,
"elapsed_real_time": \d.\d+
\}
\}}
);
$cmd->stderr_is_eq("");
}
# more indentation
{
my $cmd = Test::Command->new(cmd => "fping -c 2 --json=4 127.0.0.1");
$cmd->exit_is_num(0);
$cmd->stdout_like(qr{\{
"hosts": \{
"127\.0\.0\.1": \{
"xmt": 2,
"rcv": 2,
"loss_percentage": 0,
"min": \d.\d+,
"avg": \d.\d+,
"max": \d.\d+
\}
\}
\}}
);
$cmd->stderr_is_eq("");
}
# no pretty-print
{
my $cmd = Test::Command->new(cmd => "fping -c 2 --json=0 127.0.0.1");
$cmd->exit_is_num(0);
$cmd->stdout_like(qr{\{"hosts":\{"127\.0\.0\.1":\{"xmt":2,"rcv":2,"loss_percentage":0,"min":\d.\d+,"avg":\d.\d+,"max":\d.\d+\}\}\}});
$cmd->stderr_is_eq("");
}

@ -132,6 +132,12 @@ to any target (default is 10, minimum is 1).
Set the interface (requires SO_BINDTODEVICE support). Set the interface (requires SO_BINDTODEVICE support).
=item B<-J>, B<--json>[=I<N>]
Output in JSON format to stdout (implies -q).
Optionally specify JSON indentation (default: 2, max: 16).
Indentation == 0 will disable pretty-printing.
=item B<-l>, B<--loop> =item B<-l>, B<--loop>
Loop sending packets to each target indefinitely. Can be interrupted with Loop sending packets to each target indefinitely. Can be interrupted with

@ -291,6 +291,7 @@ int multif_flag, timeout_flag;
int outage_flag = 0; int outage_flag = 0;
int timestamp_flag = 0; int timestamp_flag = 0;
int random_data_flag = 0; int random_data_flag = 0;
int output_json_flag = 0;
#if defined(DEBUG) || defined(_DEBUG) #if defined(DEBUG) || defined(_DEBUG)
int randomly_lose_flag, sent_times_flag, trace_flag, print_per_system_flag; int randomly_lose_flag, sent_times_flag, trace_flag, print_per_system_flag;
int lose_factor; int lose_factor;
@ -298,6 +299,14 @@ int lose_factor;
char* filename = NULL; /* file containing hosts to ping */ char* filename = NULL; /* file containing hosts to ping */
/* JSON output parameters */
#define JSON_MAX_INDENT 16
#define JSON_DEFAULT_INDENT 2
char json_indent[JSON_MAX_INDENT + 1] = " ";
char json_space[2] = " ";
char json_lf[2] = "\n";
/*** forward declarations ***/ /*** forward declarations ***/
void add_name(char* name); void add_name(char* name);
@ -351,6 +360,7 @@ int main(int argc, char** argv)
int tos = 0; int tos = 0;
HOST_ENTRY* cursor; HOST_ENTRY* cursor;
struct optparse optparse_state; struct optparse optparse_state;
unsigned int json_indent_num = JSON_DEFAULT_INDENT;
/* pre-parse -h/--help, so that we also can output help information /* pre-parse -h/--help, so that we also can output help information
* without trying to open the socket, which might fail */ * without trying to open the socket, which might fail */
@ -401,6 +411,7 @@ int main(int argc, char** argv)
{ "ttl", 'H', OPTPARSE_REQUIRED }, { "ttl", 'H', OPTPARSE_REQUIRED },
{ "interval", 'i', OPTPARSE_REQUIRED }, { "interval", 'i', OPTPARSE_REQUIRED },
{ "iface", 'I', OPTPARSE_REQUIRED }, { "iface", 'I', OPTPARSE_REQUIRED },
{ "json", 'J', OPTPARSE_OPTIONAL },
{ "loop", 'l', OPTPARSE_NONE }, { "loop", 'l', OPTPARSE_NONE },
{ "all", 'm', OPTPARSE_NONE }, { "all", 'm', OPTPARSE_NONE },
{ "dontfrag", 'M', OPTPARSE_NONE }, { "dontfrag", 'M', OPTPARSE_NONE },
@ -704,6 +715,22 @@ int main(int argc, char** argv)
outage_flag = 1; outage_flag = 1;
break; break;
case 'J':
if (optparse_state.optarg && !(json_indent_num = (unsigned int)strtoul(optparse_state.optarg, (char**)NULL, 10)) && errno == EINVAL)
usage(1);
if (json_indent_num > JSON_MAX_INDENT)
usage(1);
json_indent[json_indent_num] = 0;
if (json_indent_num == 0)
json_space[0] = json_lf[0] = 0;
output_json_flag = 1;
verbose_flag = 0;
quiet_flag = 1;
break;
case '?': case '?':
fprintf(stderr, "%s: %s\n", argv[0], optparse_state.errmsg); fprintf(stderr, "%s: %s\n", argv[0], optparse_state.errmsg);
fprintf(stderr, "see 'fping -h' for usage information\n"); fprintf(stderr, "see 'fping -h' for usage information\n");
@ -789,6 +816,11 @@ int main(int argc, char** argv)
} }
} }
if (output_json_flag) {
verbose_flag = 0;
quiet_flag = 1;
}
#if defined(DEBUG) || defined(_DEBUG) #if defined(DEBUG) || defined(_DEBUG)
if (debugging & DBG_TRACE) if (debugging & DBG_TRACE)
trace_flag = 1; trace_flag = 1;
@ -858,6 +890,8 @@ int main(int argc, char** argv)
fprintf(stderr, " outage_flag set\n"); fprintf(stderr, " outage_flag set\n");
if (netdata_flag) if (netdata_flag)
fprintf(stderr, " netdata_flag set\n"); fprintf(stderr, " netdata_flag set\n");
if (output_json_flag)
fprintf(stderr, " output_json_flag set\n");
} }
#endif /* DEBUG || _DEBUG */ #endif /* DEBUG || _DEBUG */
@ -1335,6 +1369,9 @@ void finish()
} }
} }
if (output_json_flag)
printf("{%s", json_lf);
if (count_flag || loop_flag) if (count_flag || loop_flag)
print_per_system_stats(); print_per_system_stats();
#if defined(DEBUG) || defined(_DEBUG) #if defined(DEBUG) || defined(_DEBUG)
@ -1342,14 +1379,26 @@ void finish()
print_per_system_stats(); print_per_system_stats();
#endif /* DEBUG || _DEBUG */ #endif /* DEBUG || _DEBUG */
if (stats_flag) if (stats_flag) {
if (output_json_flag && (count_flag || loop_flag))
printf(",%s", json_lf);
#if defined(DEBUG) || defined(_DEBUG)
else if (output_json_flag && print_per_system_flag)
printf(",%s", json_lf);
#endif /* DEBUG || _DEBUG */
print_global_stats(); print_global_stats();
}
if (output_json_flag)
printf("%s}%s", json_lf, json_lf); /* trailing newline if we're pretty-printing */
if (min_reachable) { if (min_reachable) {
if ((num_hosts-num_unreachable) >= min_reachable) { if ((num_hosts-num_unreachable) >= min_reachable) {
if (!output_json_flag)
printf("Enough hosts reachable (required: %d, reachable: %d)\n", min_reachable, num_hosts-num_unreachable); printf("Enough hosts reachable (required: %d, reachable: %d)\n", min_reachable, num_hosts-num_unreachable);
exit(0); exit(0);
} else { } else {
if (!output_json_flag)
printf("Not enough hosts reachable (required: %d, reachable: %d)\n", min_reachable, num_hosts-num_unreachable); printf("Not enough hosts reachable (required: %d, reachable: %d)\n", min_reachable, num_hosts-num_unreachable);
exit(1); exit(1);
} }
@ -1384,49 +1433,118 @@ void print_per_system_stats(void)
fflush(stdout); fflush(stdout);
if (output_json_flag)
printf("%s\"hosts\":%s{%s", json_indent, json_space, json_lf);
if (verbose_flag || per_recv_flag) if (verbose_flag || per_recv_flag)
fprintf(stderr, "\n"); fprintf(stderr, "\n");
for (i = 0; i < num_hosts; i++) { for (i = 0; i < num_hosts; i++) {
h = table[i]; h = table[i];
if (output_json_flag)
printf("%s%s\"%s\":%s", json_indent, json_indent, h->host, json_space);
else
fprintf(stderr, "%s%s :", h->host, h->pad); fprintf(stderr, "%s%s :", h->host, h->pad);
if (report_all_rtts_flag) { if (report_all_rtts_flag) {
if (output_json_flag)
printf("[%s", json_lf);
for (j = 0; j < h->num_sent; j++) { for (j = 0; j < h->num_sent; j++) {
if ((resp = h->resp_times[j]) >= 0) if ((resp = h->resp_times[j]) >= 0) {
if (output_json_flag)
printf("%s%s%s%d.%02d", json_indent, json_indent, json_indent, resp / 100, resp % 100);
else
fprintf(stderr, " %d.%02d", resp / 100, resp % 100); fprintf(stderr, " %d.%02d", resp / 100, resp % 100);
}
else {
if (output_json_flag)
printf("%s%s%snull", json_indent, json_indent, json_indent);
else else
fprintf(stderr, " -"); fprintf(stderr, " -");
} }
if (output_json_flag) {
/* JSON doesn't allow trailing commas */
if (j + 1 < h->num_sent)
printf(",%s", json_lf);
else
printf("%s", json_lf);
}
}
if (output_json_flag) {
if (i + 1 < num_hosts)
printf("%s%s],%s", json_indent, json_indent, json_lf);
else
printf("%s%s]", json_indent, json_indent);
}
else {
fprintf(stderr, "\n"); fprintf(stderr, "\n");
} }
}
else { else {
if (output_json_flag)
printf("{%s", json_lf);
if (h->num_recv <= h->num_sent) { if (h->num_recv <= h->num_sent) {
if (output_json_flag) {
printf("%s%s%s\"xmt\":%s%d,%s", json_indent, json_indent, json_indent, json_space, h->num_sent, json_lf);
printf("%s%s%s\"rcv\":%s%d,%s", json_indent, json_indent, json_indent, json_space, h->num_recv, json_lf);
printf("%s%s%s\"loss_percentage\":%s%d", json_indent, json_indent, json_indent, json_space,
h->num_sent > 0 ? ((h->num_sent - h->num_recv) * 100) / h->num_sent : 0);
}
else {
fprintf(stderr, " xmt/rcv/%%loss = %d/%d/%d%%", fprintf(stderr, " xmt/rcv/%%loss = %d/%d/%d%%",
h->num_sent, h->num_recv, h->num_sent > 0 ? ((h->num_sent - h->num_recv) * 100) / h->num_sent : 0); h->num_sent, h->num_recv, h->num_sent > 0 ? ((h->num_sent - h->num_recv) * 100) / h->num_sent : 0);
}
if (outage_flag) { if (outage_flag) {
/* Time outage total */ /* Time outage total */
outage_ms = (h->num_sent - h->num_recv) * perhost_interval / 100; outage_ms = (h->num_sent - h->num_recv) * perhost_interval / 100;
if (output_json_flag)
printf(",%s%s%s%s\"outage\":%s%d", json_lf, json_indent, json_indent, json_indent, json_space, outage_ms);
else
fprintf(stderr, ", outage(ms) = %d", outage_ms); fprintf(stderr, ", outage(ms) = %d", outage_ms);
} }
} }
else {
if (output_json_flag) {
printf("%s%s%s\"xmt\":%s%d,%s", json_indent, json_indent, json_indent, json_space, h->num_sent, json_lf);
printf("%s%s%s\"rcv\":%s%d,%s", json_indent, json_indent, json_indent, json_space, h->num_recv, json_lf);
printf("%s%s%s\"return_percentage\":%s%d", json_indent, json_indent, json_indent, json_space,
((h->num_recv * 100) / h->num_sent));
}
else { else {
fprintf(stderr, " xmt/rcv/%%return = %d/%d/%d%%", fprintf(stderr, " xmt/rcv/%%return = %d/%d/%d%%",
h->num_sent, h->num_recv, h->num_sent, h->num_recv,
((h->num_recv * 100) / h->num_sent)); ((h->num_recv * 100) / h->num_sent));
} }
}
if (h->num_recv) { if (h->num_recv) {
avg = h->total_time / h->num_recv; avg = h->total_time / h->num_recv;
if (output_json_flag) {
printf(",%s%s%s%s\"min\":%s%s", json_lf, json_indent, json_indent, json_indent, json_space, sprint_tm(h->min_reply));
printf(",%s%s%s%s\"avg\":%s%s", json_lf, json_indent, json_indent, json_indent, json_space, sprint_tm(avg));
printf(",%s%s%s%s\"max\":%s%s", json_lf, json_indent, json_indent, json_indent, json_space, sprint_tm(h->max_reply));
}
else {
fprintf(stderr, ", min/avg/max = %s", sprint_tm(h->min_reply)); fprintf(stderr, ", min/avg/max = %s", sprint_tm(h->min_reply));
fprintf(stderr, "/%s", sprint_tm(avg)); fprintf(stderr, "/%s", sprint_tm(avg));
fprintf(stderr, "/%s", sprint_tm(h->max_reply)); fprintf(stderr, "/%s", sprint_tm(h->max_reply));
} }
}
if (output_json_flag) {
if (i + 1 < num_hosts)
printf("%s%s%s},%s", json_lf, json_indent, json_indent, json_lf);
else
printf("%s%s%s}", json_lf, json_indent, json_indent);
}
else {
fprintf(stderr, "\n"); fprintf(stderr, "\n");
} }
}
#if defined(DEBUG) || defined(_DEBUG) #if defined(DEBUG) || defined(_DEBUG)
if (sent_times_flag) { if (sent_times_flag) {
@ -1441,6 +1559,9 @@ void print_per_system_stats(void)
} }
#endif #endif
} }
if (output_json_flag)
printf("%s%s}", json_lf, json_indent);
} }
/************************************************************ /************************************************************
@ -1612,6 +1733,31 @@ void print_per_system_splits(void)
void print_global_stats(void) void print_global_stats(void)
{ {
fflush(stdout); fflush(stdout);
if (total_replies == 0) {
min_reply = 0;
max_reply = 0;
total_replies = 1;
sum_replies = 0;
}
if (output_json_flag) {
printf("%s\"stats\":%s{%s", json_indent, json_space, json_lf);
printf("%s%s\"targets\":%s%d,%s", json_indent, json_indent, json_space, num_hosts, json_lf);
printf("%s%s\"alive\":%s%d,%s", json_indent, json_indent, json_space, num_alive, json_lf);
printf("%s%s\"unreachable\":%s%d,%s", json_indent, json_indent, json_space, num_unreachable, json_lf);
printf("%s%s\"unknown_addresses\":%s%d,%s", json_indent, json_indent, json_space, num_noaddress, json_lf);
printf("%s%s\"timeouts\":%s%d,%s", json_indent, json_indent, json_space, num_timeout, json_lf);
printf("%s%s\"icmp_echos_sent\":%s%d,%s", json_indent, json_indent, json_space, num_pingsent, json_lf);
printf("%s%s\"icmp_echo_replies_received\":%s%d,%s", json_indent, json_indent, json_space, num_pingreceived, json_lf);
printf("%s%s\"other_icmp_received\":%s%d,%s", json_indent, json_indent, json_space, num_othericmprcvd, json_lf);
printf("%s%s\"min_rtt\":%s%s,%s", json_indent, json_indent, json_space, sprint_tm(min_reply), json_lf);
printf("%s%s\"avg_rtt\":%s%s,%s", json_indent, json_indent, json_space, sprint_tm((int)(sum_replies / total_replies)), json_lf);
printf("%s%s\"max_rtt\":%s%s,%s", json_indent, json_indent, json_space, sprint_tm(max_reply), json_lf);
printf("%s%s\"elapsed_real_time\":%s%.3f%s", json_indent, json_indent, json_space,
timeval_diff(&end_time, &start_time) / 100000.0, json_lf);
printf("%s}", json_indent);
}
else {
fprintf(stderr, "\n"); fprintf(stderr, "\n");
fprintf(stderr, " %7d targets\n", num_hosts); fprintf(stderr, " %7d targets\n", num_hosts);
fprintf(stderr, " %7d alive\n", num_alive); fprintf(stderr, " %7d alive\n", num_alive);
@ -1623,14 +1769,6 @@ void print_global_stats(void)
fprintf(stderr, " %7d ICMP Echo Replies received\n", num_pingreceived); fprintf(stderr, " %7d ICMP Echo Replies received\n", num_pingreceived);
fprintf(stderr, " %7d other ICMP received\n", num_othericmprcvd); fprintf(stderr, " %7d other ICMP received\n", num_othericmprcvd);
fprintf(stderr, "\n"); fprintf(stderr, "\n");
if (total_replies == 0) {
min_reply = 0;
max_reply = 0;
total_replies = 1;
sum_replies = 0;
}
fprintf(stderr, " %s ms (min round trip time)\n", sprint_tm(min_reply)); fprintf(stderr, " %s ms (min round trip time)\n", sprint_tm(min_reply));
fprintf(stderr, " %s ms (avg round trip time)\n", fprintf(stderr, " %s ms (avg round trip time)\n",
sprint_tm((int)(sum_replies / total_replies))); sprint_tm((int)(sum_replies / total_replies)));
@ -1638,6 +1776,7 @@ void print_global_stats(void)
fprintf(stderr, " %12.3f sec (elapsed real time)\n", fprintf(stderr, " %12.3f sec (elapsed real time)\n",
timeval_diff(&end_time, &start_time) / 100000.0); timeval_diff(&end_time, &start_time) / 100000.0);
fprintf(stderr, "\n"); fprintf(stderr, "\n");
}
} }
/************************************************************ /************************************************************
@ -2791,6 +2930,9 @@ void usage(int is_error)
fprintf(out, " -D, --timestamp print timestamp before each output line\n"); fprintf(out, " -D, --timestamp print timestamp before each output line\n");
fprintf(out, " -e, --elapsed show elapsed time on return packets\n"); fprintf(out, " -e, --elapsed show elapsed time on return packets\n");
fprintf(out, " -i, --interval=MSEC interval between sending ping packets (default: %d ms)\n", interval / 100); fprintf(out, " -i, --interval=MSEC interval between sending ping packets (default: %d ms)\n", interval / 100);
fprintf(out, " -J, --json[=N] output in JSON format to stdout (implies -q)\n");
fprintf(out, " optionally specify JSON indentation (default: %d, max: %d)\n", JSON_DEFAULT_INDENT, JSON_MAX_INDENT);
fprintf(out, " indentation == 0 will disable pretty-printing\n");
fprintf(out, " -n, --name show targets by name (-d is equivalent)\n"); fprintf(out, " -n, --name show targets by name (-d is equivalent)\n");
fprintf(out, " -N, --netdata output compatible for netdata (-l -Q are required)\n"); fprintf(out, " -N, --netdata output compatible for netdata (-l -Q are required)\n");
fprintf(out, " -o, --outage show the accumulated outage time (lost packets * packet interval)\n"); fprintf(out, " -o, --outage show the accumulated outage time (lost packets * packet interval)\n");

Loading…
Cancel
Save