aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRobert Haas <rhaas@postgresql.org>2024-01-11 12:47:28 -0500
committerRobert Haas <rhaas@postgresql.org>2024-01-11 12:48:27 -0500
commitee1bfd168390bc843c6704d16e909692c0a79f27 (patch)
tree88e263fab46e17acca8d2b17fefeaa8dfee84050 /src
parentd9ef650fca7bc574586f4171cd929cfd5240326e (diff)
downloadpostgresql-ee1bfd168390bc843c6704d16e909692c0a79f27.tar.gz
postgresql-ee1bfd168390bc843c6704d16e909692c0a79f27.zip
Add new pg_walsummary tool.
This can dump the contents of the WAL summary files found in pg_wal/summaries. Normally, this shouldn't really be something anyone needs to do, but it may be needed for debugging problems with incremental backup, or could possibly be useful to external tools. Discussion: http://postgr.es/m/CA+Tgmobvqqj-DW9F7uUzT-cQqs6wcVb-Xhs=w=hzJnXSE-kRGw@mail.gmail.com
Diffstat (limited to 'src')
-rw-r--r--src/bin/Makefile1
-rw-r--r--src/bin/meson.build1
-rw-r--r--src/bin/pg_walsummary/.gitignore1
-rw-r--r--src/bin/pg_walsummary/Makefile48
-rw-r--r--src/bin/pg_walsummary/meson.build30
-rw-r--r--src/bin/pg_walsummary/nls.mk6
-rw-r--r--src/bin/pg_walsummary/pg_walsummary.c280
-rw-r--r--src/bin/pg_walsummary/t/001_basic.pl19
-rw-r--r--src/bin/pg_walsummary/t/002_blocks.pl88
-rw-r--r--src/tools/pgindent/typedefs.list2
10 files changed, 476 insertions, 0 deletions
diff --git a/src/bin/Makefile b/src/bin/Makefile
index 117ffdab6a2..fc789da17bb 100644
--- a/src/bin/Makefile
+++ b/src/bin/Makefile
@@ -31,6 +31,7 @@ SUBDIRS = \
pg_upgrade \
pg_verifybackup \
pg_waldump \
+ pg_walsummary \
pgbench \
psql \
scripts
diff --git a/src/bin/meson.build b/src/bin/meson.build
index f292752c5ce..aa60ebaa302 100644
--- a/src/bin/meson.build
+++ b/src/bin/meson.build
@@ -17,6 +17,7 @@ subdir('pg_test_timing')
subdir('pg_upgrade')
subdir('pg_verifybackup')
subdir('pg_waldump')
+subdir('pg_walsummary')
subdir('pgbench')
subdir('pgevent')
subdir('psql')
diff --git a/src/bin/pg_walsummary/.gitignore b/src/bin/pg_walsummary/.gitignore
new file mode 100644
index 00000000000..d71ec192faf
--- /dev/null
+++ b/src/bin/pg_walsummary/.gitignore
@@ -0,0 +1 @@
+pg_walsummary
diff --git a/src/bin/pg_walsummary/Makefile b/src/bin/pg_walsummary/Makefile
new file mode 100644
index 00000000000..2c24bc6db52
--- /dev/null
+++ b/src/bin/pg_walsummary/Makefile
@@ -0,0 +1,48 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/bin/pg_walsummary
+#
+# Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/bin/pg_walsummary/Makefile
+#
+#-------------------------------------------------------------------------
+
+PGFILEDESC = "pg_walsummary - print contents of WAL summary files"
+PGAPPICON=win32
+
+subdir = src/bin/pg_walsummary
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS)
+LDFLAGS_INTERNAL += -L$(top_builddir)/src/fe_utils -lpgfeutils
+
+OBJS = \
+ $(WIN32RES) \
+ pg_walsummary.o
+
+all: pg_walsummary
+
+pg_walsummary: $(OBJS) | submake-libpgport submake-libpgfeutils
+ $(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
+
+
+install: all installdirs
+ $(INSTALL_PROGRAM) pg_walsummary$(X) '$(DESTDIR)$(bindir)/pg_walsummary$(X)'
+
+installdirs:
+ $(MKDIR_P) '$(DESTDIR)$(bindir)'
+
+uninstall:
+ rm -f '$(DESTDIR)$(bindir)/pg_walsummary$(X)'
+
+clean distclean maintainer-clean:
+ rm -f pg_walsummary$(X) $(OBJS)
+
+check:
+ $(prove_check)
+
+installcheck:
+ $(prove_installcheck)
diff --git a/src/bin/pg_walsummary/meson.build b/src/bin/pg_walsummary/meson.build
new file mode 100644
index 00000000000..f886a4cb360
--- /dev/null
+++ b/src/bin/pg_walsummary/meson.build
@@ -0,0 +1,30 @@
+# Copyright (c) 2022-2023, PostgreSQL Global Development Group
+
+pg_walsummary_sources = files(
+ 'pg_walsummary.c',
+)
+
+if host_system == 'windows'
+ pg_walsummary_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+ '--NAME', 'pg_walsummary',
+ '--FILEDESC', 'pg_walsummary - print contents of WAL summary files',])
+endif
+
+pg_walsummary = executable('pg_walsummary',
+ pg_walsummary_sources,
+ dependencies: [frontend_code],
+ kwargs: default_bin_args,
+)
+bin_targets += pg_walsummary
+
+tests += {
+ 'name': 'pg_walsummary',
+ 'sd': meson.current_source_dir(),
+ 'bd': meson.current_build_dir(),
+ 'tap': {
+ 'tests': [
+ 't/001_basic.pl',
+ 't/002_blocks.pl',
+ ],
+ }
+}
diff --git a/src/bin/pg_walsummary/nls.mk b/src/bin/pg_walsummary/nls.mk
new file mode 100644
index 00000000000..f411dcfe9e4
--- /dev/null
+++ b/src/bin/pg_walsummary/nls.mk
@@ -0,0 +1,6 @@
+# src/bin/pg_combinebackup/nls.mk
+CATALOG_NAME = pg_walsummary
+GETTEXT_FILES = $(FRONTEND_COMMON_GETTEXT_FILES) \
+ pg_walsummary.c
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_FLAGS = $(FRONTEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/bin/pg_walsummary/pg_walsummary.c b/src/bin/pg_walsummary/pg_walsummary.c
new file mode 100644
index 00000000000..0c0225eeb89
--- /dev/null
+++ b/src/bin/pg_walsummary/pg_walsummary.c
@@ -0,0 +1,280 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_walsummary.c
+ * Prints the contents of WAL summary files.
+ *
+ * Copyright (c) 2017-2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/bin/pg_walsummary/pg_walsummary.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres_fe.h"
+
+#include <fcntl.h>
+#include <limits.h>
+
+#include "common/blkreftable.h"
+#include "common/logging.h"
+#include "fe_utils/option_utils.h"
+#include "lib/stringinfo.h"
+#include "getopt_long.h"
+
+typedef struct ws_options
+{
+ bool individual;
+ bool quiet;
+} ws_options;
+
+typedef struct ws_file_info
+{
+ int fd;
+ char *filename;
+} ws_file_info;
+
+static BlockNumber *block_buffer = NULL;
+static unsigned block_buffer_size = 512; /* Initial size. */
+
+static void dump_one_relation(ws_options *opt, RelFileLocator *rlocator,
+ ForkNumber forknum, BlockNumber limit_block,
+ BlockRefTableReader *reader);
+static void help(const char *progname);
+static int compare_block_numbers(const void *a, const void *b);
+static int walsummary_read_callback(void *callback_arg, void *data,
+ int length);
+static void walsummary_error_callback(void *callback_arg, char *fmt,...) pg_attribute_printf(2, 3);
+
+/*
+ * Main program.
+ */
+int
+main(int argc, char *argv[])
+{
+ static struct option long_options[] = {
+ {"individual", no_argument, NULL, 'i'},
+ {"quiet", no_argument, NULL, 'q'},
+ {NULL, 0, NULL, 0}
+ };
+
+ const char *progname;
+ int optindex;
+ int c;
+ ws_options opt;
+
+ memset(&opt, 0, sizeof(ws_options));
+
+ pg_logging_init(argv[0]);
+ progname = get_progname(argv[0]);
+ handle_help_version_opts(argc, argv, progname, help);
+
+ /* process command-line options */
+ while ((c = getopt_long(argc, argv, "f:iqw:",
+ long_options, &optindex)) != -1)
+ {
+ switch (c)
+ {
+ case 'i':
+ opt.individual = true;
+ break;
+ case 'q':
+ opt.quiet = true;
+ break;
+ default:
+ /* getopt_long already emitted a complaint */
+ pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+ exit(1);
+ }
+ }
+
+ if (optind >= argc)
+ {
+ pg_log_error("%s: no input files specified", progname);
+ pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+ exit(1);
+ }
+
+ while (optind < argc)
+ {
+ ws_file_info ws;
+ BlockRefTableReader *reader;
+ RelFileLocator rlocator;
+ ForkNumber forknum;
+ BlockNumber limit_block;
+
+ ws.filename = argv[optind++];
+ if ((ws.fd = open(ws.filename, O_RDONLY | PG_BINARY, 0)) < 0)
+ pg_fatal("could not open file \"%s\": %m", ws.filename);
+
+ reader = CreateBlockRefTableReader(walsummary_read_callback, &ws,
+ ws.filename,
+ walsummary_error_callback, NULL);
+ while (BlockRefTableReaderNextRelation(reader, &rlocator, &forknum,
+ &limit_block))
+ dump_one_relation(&opt, &rlocator, forknum, limit_block, reader);
+
+ DestroyBlockRefTableReader(reader);
+ close(ws.fd);
+ }
+
+ exit(0);
+}
+
+/*
+ * Dump details for one relation.
+ */
+static void
+dump_one_relation(ws_options *opt, RelFileLocator *rlocator,
+ ForkNumber forknum, BlockNumber limit_block,
+ BlockRefTableReader *reader)
+{
+ unsigned i = 0;
+ unsigned nblocks;
+ BlockNumber startblock = InvalidBlockNumber;
+ BlockNumber endblock = InvalidBlockNumber;
+
+ /* Dump limit block, if any. */
+ if (limit_block != InvalidBlockNumber)
+ printf("TS %u, DB %u, REL %u, FORK %s: limit %u\n",
+ rlocator->spcOid, rlocator->dbOid, rlocator->relNumber,
+ forkNames[forknum], limit_block);
+
+ /* If we haven't allocated a block buffer yet, do that now. */
+ if (block_buffer == NULL)
+ block_buffer = palloc_array(BlockNumber, block_buffer_size);
+
+ /* Try to fill the block buffer. */
+ nblocks = BlockRefTableReaderGetBlocks(reader,
+ block_buffer,
+ block_buffer_size);
+
+ /* If we filled the block buffer completely, we must enlarge it. */
+ while (nblocks >= block_buffer_size)
+ {
+ unsigned new_size;
+
+ /* Double the size, being careful about overflow. */
+ new_size = block_buffer_size * 2;
+ if (new_size < block_buffer_size)
+ new_size = PG_UINT32_MAX;
+ block_buffer = repalloc_array(block_buffer, BlockNumber, new_size);
+
+ /* Try to fill the newly-allocated space. */
+ nblocks +=
+ BlockRefTableReaderGetBlocks(reader,
+ block_buffer + block_buffer_size,
+ new_size - block_buffer_size);
+
+ /* Save the new size for later calls. */
+ block_buffer_size = new_size;
+ }
+
+ /* If we don't need to produce any output, skip the rest of this. */
+ if (opt->quiet)
+ return;
+
+ /*
+ * Sort the returned block numbers. If the block reference table was using
+ * the bitmap representation for a given chunk, the block numbers in that
+ * chunk will already be sorted, but when the array-of-offsets
+ * representation is used, we can receive block numbers here out of order.
+ */
+ qsort(block_buffer, nblocks, sizeof(BlockNumber), compare_block_numbers);
+
+ /* Dump block references. */
+ while (i < nblocks)
+ {
+ /*
+ * Find the next range of blocks to print, but if --individual was
+ * specified, then consider each block a separate range.
+ */
+ startblock = endblock = block_buffer[i++];
+ if (!opt->individual)
+ {
+ while (i < nblocks && block_buffer[i] == endblock + 1)
+ {
+ endblock++;
+ i++;
+ }
+ }
+
+ /* Format this range of block numbers as a string. */
+ if (startblock == endblock)
+ printf("TS %u, DB %u, REL %u, FORK %s: block %u\n",
+ rlocator->spcOid, rlocator->dbOid, rlocator->relNumber,
+ forkNames[forknum], startblock);
+ else
+ printf("TS %u, DB %u, REL %u, FORK %s: blocks %u..%u\n",
+ rlocator->spcOid, rlocator->dbOid, rlocator->relNumber,
+ forkNames[forknum], startblock, endblock);
+ }
+}
+
+/*
+ * Quicksort comparator for block numbers.
+ */
+static int
+compare_block_numbers(const void *a, const void *b)
+{
+ BlockNumber aa = *(BlockNumber *) a;
+ BlockNumber bb = *(BlockNumber *) b;
+
+ if (aa > bb)
+ return 1;
+ else if (aa == bb)
+ return 0;
+ else
+ return -1;
+}
+
+/*
+ * Error callback.
+ */
+void
+walsummary_error_callback(void *callback_arg, char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ pg_log_generic_v(PG_LOG_ERROR, PG_LOG_PRIMARY, fmt, ap);
+ va_end(ap);
+
+ exit(1);
+}
+
+/*
+ * Read callback.
+ */
+int
+walsummary_read_callback(void *callback_arg, void *data, int length)
+{
+ ws_file_info *ws = callback_arg;
+ int rc;
+
+ if ((rc = read(ws->fd, data, length)) < 0)
+ pg_fatal("could not read file \"%s\": %m", ws->filename);
+
+ return rc;
+}
+
+/*
+ * help
+ *
+ * Prints help page for the program
+ *
+ * progname: the name of the executed program, such as "pg_walsummary"
+ */
+static void
+help(const char *progname)
+{
+ printf(_("%s prints the contents of a WAL summary file.\n\n"), progname);
+ printf(_("Usage:\n"));
+ printf(_(" %s [OPTION]... FILE...\n"), progname);
+ printf(_("\nOptions:\n"));
+ printf(_(" -i, --individual list block numbers individually, not as ranges\n"));
+ printf(_(" -q, --quiet don't print anything, just parse the files\n"));
+ printf(_(" -?, --help show this help, then exit\n"));
+
+ printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
+ printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
+}
diff --git a/src/bin/pg_walsummary/t/001_basic.pl b/src/bin/pg_walsummary/t/001_basic.pl
new file mode 100644
index 00000000000..10a232a150c
--- /dev/null
+++ b/src/bin/pg_walsummary/t/001_basic.pl
@@ -0,0 +1,19 @@
+# Copyright (c) 2021-2023, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $tempdir = PostgreSQL::Test::Utils::tempdir;
+
+program_help_ok('pg_walsummary');
+program_version_ok('pg_walsummary');
+program_options_handling_ok('pg_walsummary');
+
+command_fails_like(
+ ['pg_walsummary'],
+ qr/no input files specified/,
+ 'input files must be specified');
+
+done_testing();
diff --git a/src/bin/pg_walsummary/t/002_blocks.pl b/src/bin/pg_walsummary/t/002_blocks.pl
new file mode 100644
index 00000000000..170976f9e2d
--- /dev/null
+++ b/src/bin/pg_walsummary/t/002_blocks.pl
@@ -0,0 +1,88 @@
+# Copyright (c) 2021-2023, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use File::Compare;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Set up a new database instance.
+my $node1 = PostgreSQL::Test::Cluster->new('node1');
+$node1->init(has_archiving => 1, allows_streaming => 1);
+$node1->append_conf('postgresql.conf', 'summarize_wal = on');
+$node1->start;
+
+# See what's been summarized up until now.
+my $progress = $node1->safe_psql('postgres', <<EOM);
+SELECT summarized_tli, summarized_lsn FROM pg_get_wal_summarizer_state()
+EOM
+my ($summarized_tli, $summarized_lsn) = split(/\|/, $progress);
+note("before insert, summarized TLI $summarized_tli through $summarized_lsn");
+
+# Create a table and insert a few test rows into it. VACUUM FREEZE it so that
+# autovacuum doesn't induce any future modifications unexpectedly. Then
+# trigger a checkpoint.
+$node1->safe_psql('postgres', <<EOM);
+CREATE TABLE mytable (a int, b text);
+INSERT INTO mytable
+SELECT
+ g, random()::text||random()::text||random()::text||random()::text
+FROM
+ generate_series(1, 400) g;
+VACUUM FREEZE;
+CHECKPOINT;
+EOM
+
+# Wait for a new summary to show up.
+$node1->poll_query_until('postgres', <<EOM);
+SELECT EXISTS (
+ SELECT * from pg_available_wal_summaries()
+ WHERE tli = $summarized_tli AND end_lsn > '$summarized_lsn'
+)
+EOM
+
+# Again check the progress of WAL summarization.
+$progress = $node1->safe_psql('postgres', <<EOM);
+SELECT summarized_tli, summarized_lsn FROM pg_get_wal_summarizer_state()
+EOM
+($summarized_tli, $summarized_lsn) = split(/\|/, $progress);
+note("after insert, summarized TLI $summarized_tli through $summarized_lsn");
+
+# Update a row in the first block of the table and trigger a checkpoint.
+$node1->safe_psql('postgres', <<EOM);
+UPDATE mytable SET b = 'abcdefghijklmnopqrstuvwxyz' WHERE a = 2;
+CHECKPOINT;
+EOM
+
+# Again wait for a new summary to show up.
+$node1->poll_query_until('postgres', <<EOM);
+SELECT EXISTS (
+ SELECT * from pg_available_wal_summaries()
+ WHERE tli = $summarized_tli AND end_lsn > '$summarized_lsn'
+)
+EOM
+
+# Figure out the exact details for the new sumamry file.
+my $details = $node1->safe_psql('postgres', <<EOM);
+SELECT tli, start_lsn, end_lsn from pg_available_wal_summaries()
+ WHERE tli = $summarized_tli AND end_lsn > '$summarized_lsn'
+EOM
+my ($tli, $start_lsn, $end_lsn) = split(/\|/, $details);
+note("examining summary for TLI $tli from $start_lsn to $end_lsn");
+
+# Reconstruct the full pathname for the WAL summary file.
+my $filename = sprintf "%s/pg_wal/summaries/%08s%08s%08s%08s%08s.summary",
+ $node1->data_dir, $tli,
+ split(m@/@, $start_lsn),
+ split(m@/@, $end_lsn);
+ok(-f $filename, "WAL summary file exists");
+
+# Run pg_walsummary on it. We expect block 0 to be modified, but block 1
+# to be unmodified, so the output should say block 0, not block 0..1 or
+# similar.
+my ($stdout, $stderr) = run_command([ 'pg_walsummary', $filename ]);
+like($stdout, qr/FORK main: block 0$/m, "stdout shows block 0 modified");
+is($stderr, '', 'stderr is empty');
+
+done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 5fd46b7bd1f..f582eb59e7d 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -4039,3 +4039,5 @@ cb_tablespace_mapping
manifest_data
manifest_writer
rfile
+ws_options
+ws_file_info