On Sat, Feb 04, 2023 at 12:32:15PM +0900, Michael Paquier wrote:
> That seems rather OK seen from here.  I'll see about getting that
> applied except if there is an objection of any kind.

Okay, I have looked at that again this morning and I've spotted one
tiny issue: specifying --progress with --skip-checksums does not
really make sense.

Ignoring entries with a bad size would lead to incorrect progress
report (for example, say an entry in the manifest has a largely
oversized size number), so your approach on this side is correct.  The
application of the ignore list via -i is also correct, as a patch
matching with should_ignore_relpath() does not compute an extra size
for total_size.

I was also wondering for a few minutes while on it whether it would
have been cleaner to move the check for should_ignore_relpath()
directly in verify_file_checksum() and verify_backup_file(), but
nobody has complained about that as being a problem, either.

What do you think about the updated version attached?
--
Michael
From 08a33cc650fb3cd58e742a79b232dbd5d9843008 Mon Sep 17 00:00:00 2001
From: Michael Paquier <mich...@paquier.xyz>
Date: Mon, 6 Feb 2023 09:34:19 +0900
Subject: [PATCH v4] Add progress reporting to pg_verifybackup

This adds a new option to pg_verifybackup called -P/--progress,
showing every second some information about the state of checksum
verification.

Similarly to what is done for pg_rewind and pg_basebackup, the
information printed in the progress report consists of the current
amount of data computed and the total amount of data to compute.  This
could be extended later on.
---
 src/bin/pg_verifybackup/pg_verifybackup.c | 86 ++++++++++++++++++++++-
 src/bin/pg_verifybackup/t/004_options.pl  | 21 ++++--
 doc/src/sgml/ref/pg_verifybackup.sgml     | 15 ++++
 3 files changed, 116 insertions(+), 6 deletions(-)

diff --git a/src/bin/pg_verifybackup/pg_verifybackup.c b/src/bin/pg_verifybackup/pg_verifybackup.c
index 7634dfc285..8d5cb1c72b 100644
--- a/src/bin/pg_verifybackup/pg_verifybackup.c
+++ b/src/bin/pg_verifybackup/pg_verifybackup.c
@@ -16,12 +16,14 @@
 #include <dirent.h>
 #include <fcntl.h>
 #include <sys/stat.h>
+#include <time.h>
 
 #include "common/hashfn.h"
 #include "common/logging.h"
 #include "fe_utils/simple_list.h"
 #include "getopt_long.h"
 #include "parse_manifest.h"
+#include "pgtime.h"
 
 /*
  * For efficiency, we'd like our hash table containing information about the
@@ -57,6 +59,8 @@ typedef struct manifest_file
 	bool		matched;
 	bool		bad;
 } manifest_file;
+#define should_check_checksums(m) \
+	(((m)->matched) && !((m)->bad) && (((m)->checksum_type) != CHECKSUM_TYPE_NONE))
 
 /*
  * Define a hash table which we can use to store information about the files
@@ -147,10 +151,18 @@ static void report_fatal_error(const char *pg_restrict fmt,...)
 			pg_attribute_printf(1, 2) pg_attribute_noreturn();
 static bool should_ignore_relpath(verifier_context *context, char *relpath);
 
+static void progress_report(bool finished);
 static void usage(void);
 
 static const char *progname;
 
+/* options */
+static bool show_progress = false;
+
+/* Progress indicators */
+static uint64 total_size = 0;
+static uint64 done_size = 0;
+
 /*
  * Main entry point.
  */
@@ -162,6 +174,7 @@ main(int argc, char **argv)
 		{"ignore", required_argument, NULL, 'i'},
 		{"manifest-path", required_argument, NULL, 'm'},
 		{"no-parse-wal", no_argument, NULL, 'n'},
+		{"progress", no_argument, NULL, 'P'},
 		{"quiet", no_argument, NULL, 'q'},
 		{"skip-checksums", no_argument, NULL, 's'},
 		{"wal-directory", required_argument, NULL, 'w'},
@@ -219,7 +232,7 @@ main(int argc, char **argv)
 	simple_string_list_append(&context.ignore_list, "recovery.signal");
 	simple_string_list_append(&context.ignore_list, "standby.signal");
 
-	while ((c = getopt_long(argc, argv, "ei:m:nqsw:", long_options, NULL)) != -1)
+	while ((c = getopt_long(argc, argv, "ei:m:nPqsw:", long_options, NULL)) != -1)
 	{
 		switch (c)
 		{
@@ -241,6 +254,9 @@ main(int argc, char **argv)
 			case 'n':
 				no_parse_wal = true;
 				break;
+			case 'P':
+				show_progress = true;
+				break;
 			case 'q':
 				quiet = true;
 				break;
@@ -277,6 +293,14 @@ main(int argc, char **argv)
 		exit(1);
 	}
 
+	/* Complain if the specified arguments conflict */
+	if (show_progress && quiet)
+		pg_fatal("cannot specify both %s and %s",
+				 "-P/--progress", "-q/--quiet");
+	if (show_progress && skip_checksums)
+		pg_fatal("cannot specify both %s and %s",
+				 "-P/--progress", "-s/--skip-checksums");
+
 	/* Unless --no-parse-wal was specified, we will need pg_waldump. */
 	if (!no_parse_wal)
 	{
@@ -638,6 +662,10 @@ verify_backup_file(verifier_context *context, char *relpath, char *fullpath)
 		m->bad = true;
 	}
 
+	/* Update statistics for progress report, if necessary */
+	if (show_progress && should_check_checksums(m))
+		total_size += m->size;
+
 	/*
 	 * We don't verify checksums at this stage. We first finish verifying that
 	 * we have the expected set of files with the expected sizes, and only
@@ -675,10 +703,12 @@ verify_backup_checksums(verifier_context *context)
 	manifest_files_iterator it;
 	manifest_file *m;
 
+	progress_report(false);
+
 	manifest_files_start_iterate(context->ht, &it);
 	while ((m = manifest_files_iterate(context->ht, &it)) != NULL)
 	{
-		if (m->matched && !m->bad && m->checksum_type != CHECKSUM_TYPE_NONE &&
+		if (should_check_checksums(m) &&
 			!should_ignore_relpath(context, m->pathname))
 		{
 			char	   *fullpath;
@@ -694,6 +724,8 @@ verify_backup_checksums(verifier_context *context)
 			pfree(fullpath);
 		}
 	}
+
+	progress_report(true);
 }
 
 /*
@@ -740,6 +772,10 @@ verify_file_checksum(verifier_context *context, manifest_file *m,
 			close(fd);
 			return;
 		}
+
+		/* Report progress */
+		done_size += rc;
+		progress_report(false);
 	}
 	if (rc < 0)
 		report_backup_error(context, "could not read file \"%s\": %m",
@@ -894,6 +930,51 @@ hash_string_pointer(char *s)
 	return hash_bytes(ss, strlen(s));
 }
 
+/*
+ * Print a progress report based on the global variables.
+ *
+ * Progress report is written at maximum once per second, unless the finished
+ * parameter is set to true.
+ *
+ * If finished is set to true, this is the last progress report. The cursor
+ * is moved to the next line.
+ */
+static void
+progress_report(bool finished)
+{
+	static pg_time_t last_progress_report = 0;
+	pg_time_t	now;
+	int 	percent_size = 0;
+	char	totalsize_str[32];
+	char	donesize_str[32];
+
+	if (!show_progress)
+		return;
+
+	now = time(NULL);
+	if (now == last_progress_report && !finished)
+		return;					/* Max once per second */
+
+	last_progress_report = now;
+	percent_size = total_size ? (int) ((done_size * 100 / total_size)) : 0;
+
+	snprintf(totalsize_str, sizeof(totalsize_str), UINT64_FORMAT,
+			 total_size / 1024);
+	snprintf(donesize_str, sizeof(donesize_str), UINT64_FORMAT,
+			 done_size / 1024);
+
+	fprintf(stderr,
+			_("%*s/%s kB (%d%%) verified"),
+			(int) strlen(totalsize_str),
+			donesize_str, totalsize_str, percent_size);
+
+	/*
+	 * Stay on the same line if reporting to a terminal and we're not done
+	 * yet.
+	 */
+	fputc((!finished && isatty(fileno(stderr))) ? '\r' : '\n', stderr);
+}
+
 /*
  * Print out usage information and exit.
  */
@@ -907,6 +988,7 @@ usage(void)
 	printf(_("  -i, --ignore=RELATIVE_PATH  ignore indicated path\n"));
 	printf(_("  -m, --manifest-path=PATH    use specified path for manifest\n"));
 	printf(_("  -n, --no-parse-wal          do not try to parse WAL files\n"));
+	printf(_("  -P, --progress              show progress information\n"));
 	printf(_("  -q, --quiet                 do not print any output, except for errors\n"));
 	printf(_("  -s, --skip-checksums        skip checksum verification\n"));
 	printf(_("  -w, --wal-directory=PATH    use specified path for WAL files\n"));
diff --git a/src/bin/pg_verifybackup/t/004_options.pl b/src/bin/pg_verifybackup/t/004_options.pl
index 25c485e0ee..654cba743b 100644
--- a/src/bin/pg_verifybackup/t/004_options.pl
+++ b/src/bin/pg_verifybackup/t/004_options.pl
@@ -28,6 +28,16 @@ ok($result, "-q succeeds: exit code 0");
 is($stdout, '', "-q succeeds: no stdout");
 is($stderr, '', "-q succeeds: no stderr");
 
+# Test invalid options
+command_fails_like(
+	[ 'pg_verifybackup', '--progress', '--quiet', $backup_path ],
+	qr{cannot specify both -P/--progress and -q/--quiet},
+	'cannot use --progress and --quiet at the same time');
+command_fails_like(
+	[ 'pg_verifybackup', '--progress', '--skip-checksums', $backup_path ],
+	qr{cannot specify both -P/--progress and -s/--skip-checksums},
+	'cannot use options --progress and --skip-checksums at the same time');
+
 # Corrupt the PG_VERSION file.
 my $version_pathname = "$backup_path/PG_VERSION";
 my $version_contents = slurp_file($version_pathname);
@@ -48,10 +58,13 @@ command_like(
 	qr/backup successfully verified/,
 	'-s skips checksumming');
 
-# Validation should succeed if we ignore the problem file.
-command_like(
-	[ 'pg_verifybackup', '-i', 'PG_VERSION', $backup_path ],
-	qr/backup successfully verified/,
+# Validation should succeed if we ignore the problem file. Also, check
+# the progress information.
+command_checks_all(
+	[ 'pg_verifybackup', '-P', '-i', 'PG_VERSION', $backup_path ],
+	0,
+	[qr/backup successfully verified/],
+	[qr{(\d+/\d+ kB \(\d+%\) verified)+}],
 	'-i ignores problem file');
 
 # PG_VERSION is already corrupt; let's try also removing all of pg_xact.
diff --git a/doc/src/sgml/ref/pg_verifybackup.sgml b/doc/src/sgml/ref/pg_verifybackup.sgml
index 5f83c98706..b4e0a52ad3 100644
--- a/doc/src/sgml/ref/pg_verifybackup.sgml
+++ b/doc/src/sgml/ref/pg_verifybackup.sgml
@@ -178,6 +178,21 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>-P</option></term>
+      <term><option>--progress</option></term>
+      <listitem>
+       <para>
+        Enable progress reporting. Turning this on will deliver a progress
+        report while verifying checksums.
+       </para>
+       <para>
+        This option cannot be used together with the option
+        <option>--quiet</option> or <option>--skip-checksums</option>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-q</option></term>
       <term><option>--quiet</option></term>
-- 
2.39.1

Attachment: signature.asc
Description: PGP signature

Reply via email to