From 4b905c0275f0cf85327ab4cb0f1ab94d9cfc69ad Mon Sep 17 00:00:00 2001
From: Erik Auerswald <auerswal@unix-ag.uni-kl.de>
Date: Sun, 30 Jan 2022 21:50:54 +0100
Subject: [PATCH 2/5] New option -F, --full to change -Q SECS output

By default, -Q SECS emits information for the just finished
interval.  This is good for long-running fping processes where
one would like to get an update on recent results, ignoring
missing responses from older intervals.  This seems appropriate
for use with other software like netdata or smokeping.

But for short(er)-running fping processes, e.g., during a change
window, it may be more interesting to get a status update since
the beginning of the change window, i.e., the start of fping.
The new option -F / --full changes the -Q SECS interim reports
to output the current per system overall statistics.

This patch extracts the actual host statistic output part from
print_per_system_stats() and print_per_system_splits() into
new functions print_host_stats() and print_host_splits().  This
enables calling either one during the interim output function
print_per_system_splits().  Which one is called is determined
by the contents of a new global variable, full_stats_flag, which
is set by -F, --full.

The new option is documented in fping.pod and added to fping -h
output.

One existing test is extended to test the default per-interval
output of -Q SECS, and another test for the new functionality is
added.
---
 ci/test-08-options-n-q.pl |  20 ++++-
 doc/fping.pod             |   5 ++
 src/fping.c               | 150 ++++++++++++++++++++++++++------------
 3 files changed, 124 insertions(+), 51 deletions(-)

diff --git a/ci/test-08-options-n-q.pl b/ci/test-08-options-n-q.pl
index 51720bf..b76aa00 100755
--- a/ci/test-08-options-n-q.pl
+++ b/ci/test-08-options-n-q.pl
@@ -1,6 +1,6 @@
 #!/usr/bin/perl -w
 
-use Test::Command tests => 18;
+use Test::Command tests => 21;
 
 #  -n         show targets by name (-d is equivalent)
 #  -O n       set the type of service (tos) flag on the ICMP packets
@@ -59,13 +59,27 @@ $cmd->stderr_like(qr{127\.0\.0\.1 : xmt/rcv/%loss = 3/3/0%, min/avg/max = \d\.\d
 
 # fping -Q
 {
-my $cmd = Test::Command->new(cmd => "fping -Q 1 -p 400 -c 4 127.0.0.1");
+my $cmd = Test::Command->new(cmd => "fping -Q 1 -p 450 -c 6 127.0.0.1");
 $cmd->exit_is_num(0);
 $cmd->stdout_is_eq("");
 $cmd->stderr_like(qr{\[\d+:\d+:\d+\]
 127\.0\.0\.1 : xmt/rcv/%loss = 3/3/0%, min/avg/max = \d\.\d+/\d\.\d+/\d\.\d+
-127\.0\.0\.1 : xmt/rcv/%loss = 4/4/0%, min/avg/max = \d\.\d+/\d\.\d+/\d\.\d+
+\[\d+:\d+:\d+\]
+127\.0\.0\.1 : xmt/rcv/%loss = 2/2/0%, min/avg/max = \d\.\d+/\d\.\d+/\d\.\d+
+127\.0\.0\.1 : xmt/rcv/%loss = 6/6/0%, min/avg/max = \d\.\d+/\d\.\d+/\d\.\d+
 });
 }
 
+# fping -Q -F
+{
+my $cmd = Test::Command->new(cmd => "fping -Q 1 -F -p 450 -c 6 127.0.0.1");
+$cmd->exit_is_num(0);
+$cmd->stdout_is_eq("");
+$cmd->stderr_like(qr{\[\d+:\d+:\d+\]
+127\.0\.0\.1 : xmt/rcv/%loss = 3/3/0%, min/avg/max = \d\.\d+/\d\.\d+/\d\.\d+
+\[\d+:\d+:\d+\]
+127\.0\.0\.1 : xmt/rcv/%loss = 5/5/0%, min/avg/max = \d\.\d+/\d\.\d+/\d\.\d+
+127\.0\.0\.1 : xmt/rcv/%loss = 6/6/0%, min/avg/max = \d\.\d+/\d\.\d+/\d\.\d+
+});
+}
 
diff --git a/doc/fping.pod b/doc/fping.pod
index 7702df1..45d670e 100644
--- a/doc/fping.pod
+++ b/doc/fping.pod
@@ -102,6 +102,11 @@ user. Regular users should pipe in the file via stdin:
 
  $ fping < targets_file
 
+=item B<-F>, B<--full>
+
+Print full per host statistics since fping start instead of per interval
+statistics in regular status reports enabled by B<-Q> I<SECS>.
+
 =item B<-g>, B<--generate> I<addr/mask>
 
 Generate a target list from a supplied IP netmask, or a starting and ending IP.
diff --git a/src/fping.c b/src/fping.c
index 887ba9b..3ac480b 100644
--- a/src/fping.c
+++ b/src/fping.c
@@ -359,6 +359,7 @@ int multif_flag, timeout_flag;
 int outage_flag = 0;
 int timestamp_flag = 0;
 int random_data_flag = 0;
+int full_stats_flag = 0;
 #if defined(DEBUG) || defined(_DEBUG)
 int randomly_lose_flag, trace_flag, print_per_system_flag;
 int lose_factor;
@@ -514,6 +515,7 @@ int main(int argc, char** argv)
         { "timestamp", 'D', OPTPARSE_NONE },
         { "elapsed", 'e', OPTPARSE_NONE },
         { "file", 'f', OPTPARSE_REQUIRED },
+        { "full", 'F', OPTPARSE_NONE },
         { "generate", 'g', OPTPARSE_NONE },
         { "help", 'h', OPTPARSE_NONE },
         { "ttl", 'H', OPTPARSE_REQUIRED },
@@ -827,6 +829,10 @@ int main(int argc, char** argv)
             outage_flag = 1;
             break;
 
+        case 'F':
+            full_stats_flag = 1;
+            break;
+
         case '?':
             fprintf(stderr, "%s: %s\n", argv[0], optparse_state.errmsg);
             fprintf(stderr, "see 'fping -h' for usage information\n");
@@ -1575,6 +1581,50 @@ void finish()
     exit(0);
 }
 
+/************************************************************
+
+  Function: print_host_stats
+
+*************************************************************
+
+  Inputs:  HOST_ENTRY* h
+
+  Description:
+
+  Print cumulative statistics for the given host h.
+
+************************************************************/
+
+void print_host_stats(HOST_ENTRY* h)
+{
+    int avg, outage_ms;
+
+    if (h->num_recv <= h->num_sent) {
+        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);
+
+        if (outage_flag) {
+            /* Time outage total */
+            outage_ms = (h->num_sent - h->num_recv) * perhost_interval / 1e6;
+            fprintf(stderr, ", outage(ms) = %d", outage_ms);
+        }
+    }
+    else {
+        fprintf(stderr, " xmt/rcv/%%return = %d/%d/%d%%",
+            h->num_sent, h->num_recv,
+            h->num_sent > 0 ? ((h->num_recv * 100) / h->num_sent) : 0);
+    }
+
+    if (h->num_recv) {
+        avg = h->total_time / h->num_recv;
+        fprintf(stderr, ", min/avg/max = %s", sprint_tm(h->min_reply));
+        fprintf(stderr, "/%s", sprint_tm(avg));
+        fprintf(stderr, "/%s", sprint_tm(h->max_reply));
+    }
+
+    fprintf(stderr, "\n");
+}
+
 /************************************************************
 
   Function: print_per_system_stats
@@ -1590,7 +1640,7 @@ void finish()
 
 void print_per_system_stats(void)
 {
-    int i, j, avg, outage_ms;
+    int i, j;
     HOST_ENTRY* h;
     int64_t resp;
 
@@ -1612,30 +1662,7 @@ void print_per_system_stats(void)
             fprintf(stderr, "\n");
         }
         else {
-            if (h->num_recv <= h->num_sent) {
-                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);
-
-                if (outage_flag) {
-                    /* Time outage total */
-                    outage_ms = (h->num_sent - h->num_recv) * perhost_interval / 1e6;
-                    fprintf(stderr, ", outage(ms) = %d", outage_ms);
-                }
-            }
-            else {
-                fprintf(stderr, " xmt/rcv/%%return = %d/%d/%d%%",
-                    h->num_sent, h->num_recv,
-                    h->num_sent > 0 ? ((h->num_recv * 100) / h->num_sent) : 0);
-            }
-
-            if (h->num_recv) {
-                avg = h->total_time / h->num_recv;
-                fprintf(stderr, ", min/avg/max = %s", sprint_tm(h->min_reply));
-                fprintf(stderr, "/%s", sprint_tm(avg));
-                fprintf(stderr, "/%s", sprint_tm(h->max_reply));
-            }
-
-            fprintf(stderr, "\n");
+            print_host_stats(h);
         }
     }
 }
@@ -1714,6 +1741,51 @@ void print_netdata(void)
     sent_charts = 1;
 }
 
+/************************************************************
+
+  Function: print_host_splits
+
+*************************************************************
+
+  Inputs:  HOST_ENTRY* h
+
+  Description:
+
+  Print host interval statistics accumulated since the last
+  such report and reset the interval counters.
+
+************************************************************/
+
+void print_host_splits(HOST_ENTRY* h)
+{
+    int avg, outage_ms_i;
+
+    if (h->num_recv_i <= h->num_sent_i) {
+        fprintf(stderr, " xmt/rcv/%%loss = %d/%d/%d%%",
+            h->num_sent_i, h->num_recv_i, h->num_sent_i > 0 ? ((h->num_sent_i - h->num_recv_i) * 100) / h->num_sent_i : 0);
+
+        if (outage_flag) {
+            /* Time outage  */
+            outage_ms_i = (h->num_sent_i - h->num_recv_i) * perhost_interval / 1e6;
+            fprintf(stderr, ", outage(ms) = %d", outage_ms_i);
+        }
+    }
+    else {
+        fprintf(stderr, " xmt/rcv/%%return = %d/%d/%d%%",
+            h->num_sent_i, h->num_recv_i, h->num_sent_i > 0 ? ((h->num_recv_i * 100) / h->num_sent_i) : 0);
+    }
+
+    if (h->num_recv_i) {
+        avg = h->total_time_i / h->num_recv_i;
+        fprintf(stderr, ", min/avg/max = %s", sprint_tm(h->min_reply_i));
+        fprintf(stderr, "/%s", sprint_tm(avg));
+        fprintf(stderr, "/%s", sprint_tm(h->max_reply_i));
+    }
+
+    fprintf(stderr, "\n");
+    h->num_sent_i = h->num_recv_i = h->max_reply_i = h->min_reply_i = h->total_time_i = 0;
+}
+
 /************************************************************
 
   Function: print_per_system_splits
@@ -1729,7 +1801,7 @@ void print_netdata(void)
 
 void print_per_system_splits(void)
 {
-    int i, avg, outage_ms_i;
+    int i;
     HOST_ENTRY* h;
     struct tm* curr_tm;
 
@@ -1744,31 +1816,12 @@ void print_per_system_splits(void)
     for (i = 0; i < num_hosts; i++) {
         h = table[i];
         fprintf(stderr, "%-*s :", max_hostname_len, h->host);
-
-        if (h->num_recv_i <= h->num_sent_i) {
-            fprintf(stderr, " xmt/rcv/%%loss = %d/%d/%d%%",
-                h->num_sent_i, h->num_recv_i, h->num_sent_i > 0 ? ((h->num_sent_i - h->num_recv_i) * 100) / h->num_sent_i : 0);
-
-            if (outage_flag) {
-                /* Time outage  */
-                outage_ms_i = (h->num_sent_i - h->num_recv_i) * perhost_interval / 1e6;
-                fprintf(stderr, ", outage(ms) = %d", outage_ms_i);
-            }
+        if (full_stats_flag) {
+            print_host_stats(h);
         }
         else {
-            fprintf(stderr, " xmt/rcv/%%return = %d/%d/%d%%",
-                h->num_sent_i, h->num_recv_i, h->num_sent_i > 0 ? ((h->num_recv_i * 100) / h->num_sent_i) : 0);
-        }
-
-        if (h->num_recv_i) {
-            avg = h->total_time_i / h->num_recv_i;
-            fprintf(stderr, ", min/avg/max = %s", sprint_tm(h->min_reply_i));
-            fprintf(stderr, "/%s", sprint_tm(avg));
-            fprintf(stderr, "/%s", sprint_tm(h->max_reply_i));
+            print_host_splits(h);
         }
-
-        fprintf(stderr, "\n");
-        h->num_sent_i = h->num_recv_i = h->max_reply_i = h->min_reply_i = h->total_time_i = 0;
     }
 }
 
@@ -2934,6 +2987,7 @@ void usage(int is_error)
     fprintf(out, "   -d, --rdns         show targets by name (force reverse-DNS lookup)\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, "   -F, --full         stats since beginning (not per interval) with -Q\n");
     fprintf(out, "   -i, --interval=MSEC  interval between sending ping packets (default: %.0f ms)\n", interval / 1e6);
     fprintf(out, "   -n, --name         show targets by name (reverse-DNS lookup for target IPs)\n");
     fprintf(out, "   -N, --netdata      output compatible for netdata (-l -Q are required)\n");
-- 
2.25.1

