From f879d58dd8beb8408563baf8b40634628b5f2086 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Date: Sat, 20 Nov 2021 04:31:11 +0000
Subject: [PATCH v2] pg_waldump: emit stats while terminating

Currently, pg_waldump keeps accumulating the stats with options
--follow and --stats, but outputs nothing while terminating/exiting.
This patch adds signal handlers for SIGINT, SIGTERM and SIGQUIT to
display the stats computed as of the termination.
---
 doc/src/sgml/ref/pg_waldump.sgml |  9 ++++
 src/bin/pg_waldump/pg_waldump.c  | 84 ++++++++++++++++++++++++--------
 2 files changed, 74 insertions(+), 19 deletions(-)

diff --git a/doc/src/sgml/ref/pg_waldump.sgml b/doc/src/sgml/ref/pg_waldump.sgml
index 432254d2d5..cf75d1dd21 100644
--- a/doc/src/sgml/ref/pg_waldump.sgml
+++ b/doc/src/sgml/ref/pg_waldump.sgml
@@ -261,6 +261,15 @@ PostgreSQL documentation
     <literal>.partial</literal>. If those files need to be read, <literal>.partial</literal>
     suffix needs to be removed from the file name.
   </para>
+
+  <para>
+    When <option>--follow</option> is used with <option>--stats</option> and
+    the <application>pg_waldump</application> is terminated or interrupted
+    with signal <systemitem>SIGINT</systemitem> or <systemitem>SIGTERM</systemitem>
+    or <systemitem>SIGQUIT</systemitem>, the summary statistics computed
+    as of the termination will be displayed.
+  </para>
+
  </refsect1>
 
  <refsect1>
diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c
index 1e3894b9c4..d7ec48a658 100644
--- a/src/bin/pg_waldump/pg_waldump.c
+++ b/src/bin/pg_waldump/pg_waldump.c
@@ -13,6 +13,7 @@
 #include "postgres.h"
 
 #include <dirent.h>
+#include <signal.h>
 #include <sys/stat.h>
 #include <unistd.h>
 
@@ -25,10 +26,8 @@
 #include "getopt_long.h"
 #include "rmgrdesc.h"
 
-static const char *progname;
-
-static int	WalSegSz;
-
+#define MAX_XLINFO_TYPES 16
+#define fatal_error(...) do { pg_log_fatal(__VA_ARGS__); exit(EXIT_FAILURE); } while(0)
 typedef struct XLogDumpPrivate
 {
 	TimeLineID	timeline;
@@ -62,8 +61,6 @@ typedef struct Stats
 	uint64		fpi_len;
 } Stats;
 
-#define MAX_XLINFO_TYPES 16
-
 typedef struct XLogDumpStats
 {
 	uint64		count;
@@ -71,7 +68,35 @@ typedef struct XLogDumpStats
 	Stats		record_stats[RM_NEXT_ID][MAX_XLINFO_TYPES];
 } XLogDumpStats;
 
-#define fatal_error(...) do { pg_log_fatal(__VA_ARGS__); exit(EXIT_FAILURE); } while(0)
+static const char *progname;
+static int	WalSegSz;
+static XLogDumpConfig *opts = NULL;
+static XLogDumpStats *stats	= NULL;
+static XLogRecPtr stats_startptr  = InvalidXLogRecPtr;
+static XLogRecPtr stats_endptr	= InvalidXLogRecPtr;
+
+static void XLogDumpDisplayStats(void);
+
+static void
+SignalHandlerForTermination(int signum)
+{
+	/* Display the stats only if they are valid */
+	if (stats &&
+		!XLogRecPtrIsInvalid(stats_startptr) &&
+		!XLogRecPtrIsInvalid(stats_endptr))
+	{
+		XLogDumpDisplayStats();
+		pg_free(stats);
+	}
+
+	/*
+	 * Although the pg_free calls (above and below) are unnecessary here on the
+	 * process exit, let's not leak any memory knowingly.
+	 */
+	pg_free(opts);
+
+	exit(EXIT_FAILURE);
+}
 
 static void
 print_rmgr_list(void)
@@ -410,8 +435,7 @@ XLogDumpRecordLen(XLogReaderState *record, uint32 *rec_len, uint32 *fpi_len)
  * Store per-rmgr and per-record statistics for a given record.
  */
 static void
-XLogDumpCountRecord(XLogDumpConfig *config, XLogDumpStats *stats,
-					XLogReaderState *record)
+XLogDumpCountRecord(XLogReaderState *record)
 {
 	RmgrId		rmid;
 	uint8		recid;
@@ -457,7 +481,7 @@ XLogDumpCountRecord(XLogDumpConfig *config, XLogDumpStats *stats,
  * Print a record to stdout
  */
 static void
-XLogDumpDisplayRecord(XLogDumpConfig *config, XLogReaderState *record)
+XLogDumpDisplayRecord(XLogReaderState *record)
 {
 	const char *id;
 	const RmgrDescData *desc = &RmgrDescTable[XLogRecGetRmid(record)];
@@ -491,7 +515,7 @@ XLogDumpDisplayRecord(XLogDumpConfig *config, XLogReaderState *record)
 	printf("%s", s.data);
 	pfree(s.data);
 
-	if (!config->bkp_details)
+	if (!opts->bkp_details)
 	{
 		/* print block references (short format) */
 		for (block_id = 0; block_id <= record->max_block_id; block_id++)
@@ -621,7 +645,7 @@ XLogDumpStatsRow(const char *name,
  * Display summary statistics about the records seen so far.
  */
 static void
-XLogDumpDisplayStats(XLogDumpConfig *config, XLogDumpStats *stats)
+XLogDumpDisplayStats(void)
 {
 	int			ri,
 				rj;
@@ -645,6 +669,12 @@ XLogDumpDisplayStats(XLogDumpConfig *config, XLogDumpStats *stats)
 	}
 	total_len = total_rec_len + total_fpi_len;
 
+	Assert(!XLogRecPtrIsInvalid(stats_startptr) &&
+		   !XLogRecPtrIsInvalid(stats_endptr) &&
+		   stats_startptr <= stats_endptr);
+
+	printf("Summary of the WAL statistics computed between LSN %X/%X and LSN %X/%X is:\n",
+		   LSN_FORMAT_ARGS(stats_startptr), LSN_FORMAT_ARGS(stats_endptr));
 	/*
 	 * 27 is strlen("Transaction/COMMIT_PREPARED"), 20 is strlen(2^64), 8 is
 	 * strlen("(100.00%)")
@@ -663,7 +693,7 @@ XLogDumpDisplayStats(XLogDumpConfig *config, XLogDumpStats *stats)
 					tot_len;
 		const RmgrDescData *desc = &RmgrDescTable[ri];
 
-		if (!config->stats_per_record)
+		if (!opts->stats_per_record)
 		{
 			count = stats->rmgr_stats[ri].count;
 			rec_len = stats->rmgr_stats[ri].rec_len;
@@ -768,7 +798,6 @@ main(int argc, char **argv)
 	XLogReaderState *xlogreader_state;
 	XLogDumpPrivate private;
 	XLogDumpConfig config;
-	XLogDumpStats stats;
 	XLogRecord *record;
 	XLogRecPtr	first_record;
 	char	   *waldir = NULL;
@@ -794,6 +823,10 @@ main(int argc, char **argv)
 	int			option;
 	int			optindex = 0;
 
+	pqsignal(SIGINT, SignalHandlerForTermination);
+	pqsignal(SIGTERM, SignalHandlerForTermination);
+	pqsignal(SIGQUIT, SignalHandlerForTermination);
+
 	pg_logging_init(argv[0]);
 	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_waldump"));
 	progname = get_progname(argv[0]);
@@ -813,8 +846,8 @@ main(int argc, char **argv)
 	}
 
 	memset(&private, 0, sizeof(XLogDumpPrivate));
-	memset(&config, 0, sizeof(XLogDumpConfig));
-	memset(&stats, 0, sizeof(XLogDumpStats));
+	opts = (XLogDumpConfig *) pg_malloc0(sizeof(XLogDumpConfig));
+	config = *opts;
 
 	private.timeline = 1;
 	private.startptr = InvalidXLogRecPtr;
@@ -1084,6 +1117,12 @@ main(int argc, char **argv)
 			   LSN_FORMAT_ARGS(first_record),
 			   (uint32) (first_record - private.startptr));
 
+	if (config.stats == true && !config.quiet)
+	{
+		stats = (XLogDumpStats *) pg_malloc0(sizeof(XLogDumpStats));
+		stats_startptr = first_record;
+	}
+
 	for (;;)
 	{
 		/* try to read the next record */
@@ -1112,9 +1151,12 @@ main(int argc, char **argv)
 		if (!config.quiet)
 		{
 			if (config.stats == true)
-				XLogDumpCountRecord(&config, &stats, xlogreader_state);
+			{
+				XLogDumpCountRecord(xlogreader_state);
+				stats_endptr = xlogreader_state->currRecPtr;
+			}
 			else
-				XLogDumpDisplayRecord(&config, xlogreader_state);
+				XLogDumpDisplayRecord(xlogreader_state);
 		}
 
 		/* check whether we printed enough */
@@ -1125,7 +1167,7 @@ main(int argc, char **argv)
 	}
 
 	if (config.stats == true && !config.quiet)
-		XLogDumpDisplayStats(&config, &stats);
+		XLogDumpDisplayStats();
 
 	if (errormsg)
 		fatal_error("error in WAL record at %X/%X: %s",
@@ -1134,9 +1176,13 @@ main(int argc, char **argv)
 
 	XLogReaderFree(xlogreader_state);
 
+	pg_free(opts);
+	pg_free(stats);
+
 	return EXIT_SUCCESS;
 
 bad_argument:
 	fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
+	pg_free(opts);
 	return EXIT_FAILURE;
 }
-- 
2.25.1

