diff --git a/doc/src/sgml/ref/pg_verifybackup.sgml b/doc/src/sgml/ref/pg_verifybackup.sgml
index 5f83c98706..36335e0a18 100644
--- a/doc/src/sgml/ref/pg_verifybackup.sgml
+++ b/doc/src/sgml/ref/pg_verifybackup.sgml
@@ -178,6 +178,21 @@ PostgreSQL documentation
+
+
+
+
+
+ Enable progress reporting. Turning this on will deliver a progress
+ report while verifying checksums.
+
+
+ This option cannot be used together with the option
+ .
+
+
+
+
diff --git a/src/bin/pg_verifybackup/pg_verifybackup.c b/src/bin/pg_verifybackup/pg_verifybackup.c
index 7634dfc285..059836f0e6 100644
--- a/src/bin/pg_verifybackup/pg_verifybackup.c
+++ b/src/bin/pg_verifybackup/pg_verifybackup.c
@@ -16,12 +16,14 @@
#include
#include
#include
+#include
#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
@@ -58,6 +60,9 @@ typedef struct manifest_file
bool bad;
} manifest_file;
+#define should_verify_checksum(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
* mentioned in the backup manifest.
@@ -147,10 +152,19 @@ 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;
+static bool skip_checksums = false;
+
+/* Progress indicators */
+static uint64 total_size = 0;
+static uint64 done_size = 0;
+
/*
* Main entry point.
*/
@@ -162,6 +176,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'},
@@ -174,7 +189,6 @@ main(int argc, char **argv)
char *manifest_path = NULL;
bool no_parse_wal = false;
bool quiet = false;
- bool skip_checksums = false;
char *wal_directory = NULL;
char *pg_waldump_path = NULL;
@@ -219,7 +233,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 +255,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 +294,11 @@ 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");
+
/* Unless --no-parse-wal was specified, we will need pg_waldump. */
if (!no_parse_wal)
{
@@ -638,6 +660,10 @@ verify_backup_file(verifier_context *context, char *relpath, char *fullpath)
m->bad = true;
}
+ /* Update statistics for progress report, if necessary */
+ if (show_progress && !skip_checksums && should_verify_checksum(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 +701,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_verify_checksum(m) &&
!should_ignore_relpath(context, m->pathname))
{
char *fullpath;
@@ -694,6 +722,8 @@ verify_backup_checksums(verifier_context *context)
pfree(fullpath);
}
}
+
+ progress_report(true);
}
/*
@@ -740,6 +770,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 +928,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 +986,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..591a6b36be 100644
--- a/src/bin/pg_verifybackup/t/004_options.pl
+++ b/src/bin/pg_verifybackup/t/004_options.pl
@@ -28,6 +28,12 @@ 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');
+
# Corrupt the PG_VERSION file.
my $version_pathname = "$backup_path/PG_VERSION";
my $version_contents = slurp_file($version_pathname);
@@ -48,10 +54,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', '--progress', '-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.