Attached v3 is cleaned up and includes a pg_walinspect docs update as
well as some edited comments in rmgr_utils.c

On Mon, Mar 27, 2023 at 6:27 PM Peter Geoghegan <p...@bowt.ie> wrote:
>
> On Mon, Mar 27, 2023 at 2:29 PM Melanie Plageman
> <melanieplage...@gmail.com> wrote:
> > I went to add dedup records and noticed that since the actual
> > BTDedupInterval struct is what is put in the xlog, I would need access
> > to that type from nbtdesc.c, however, including nbtree.h doesn't seem to
> > work because it includes files that cannot be included in frontend code.
>
> I suppose that the BTDedupInterval struct could have just as easily
> gone in nbtxlog.h, next to xl_btree_dedup. There might have been a
> moment where I thought about doing it that way, but I guess I found it
> slightly preferable to use that symbol name (BTDedupInterval) rather
> than (say) xl_btree_dedup_interval in places like the nearby
> BTDedupStateData struct.
>
> Actually, I suppose that it's hard to make that alternative work, at
> least without
> including nbtxlog.h in nbtree.h. Which sounds wrong.
>
> > I, of course, could make some local struct in nbtdesc.c which has an
> > OffsetNumber and a uint16, since the BTDedupInterval is pretty
> > straightforward, but that seems a bit annoying.
> > I'm probably missing something obvious, but is there a better way to do
> > this?
>
> It was probably just one of those cases where I settled on the
> arrangement that looked least odd overall. Not a particularly
> principled approach. But the approach that I'm going to take once more
> here.  ;-)
>
> All of the available alternatives are annoying in roughly the same
> way, though perhaps to varying degrees. All except one: I'm okay with
> just not adding coverage for deduplication records, for the time being
> -- just seeing the number of intervals alone is relatively informative
> with deduplication records, unlike (say) nbtree delete records. I'm
> also okay with having coverage for dedup records if you feel it's
> worth having. Your call.
>
> If we're going to have coverage for deduplication records then it
> seems to me that we have to have a struct in nbtxlog.h for your code
> to work off of. It also seems likely that we'll want to use that same
> struct within nbtxlog.c. What's less clear is what that means for the
> BTDedupInterval struct. I don't think that we should include nbtxlog.h
> in nbtree.h, nor should we do the converse.
>
> I guess maybe two identical structs would be okay. BTDedupInterval,
> and xl_btree_dedup_interval, with the former still used in nbtdedup.c,
> and the latter used through a pointer at the point that nbtxlog.c
> reads a dedup record. Then maybe at a sizeof() static assert beside
> the existing btree_xlog_dedup() assertions that check that the dedup
> state interval array matches the array taken from the WAL record.
> That's still a bit weird, but I find it preferable to any alternative
> that I can think of.

I've omitted enhancements for the dedup record type for now.

> > On another note, I've thought about how to include some example output
> > in docs, and, for example we could modify the example output in the
> > pgwalinspect docs which includes a PRUNE record already for
> > pg_get_wal_record_info() docs. We'd probably just want to keep it short.
>
> Yeah. Perhaps a PRUNE record for one of the system catalogs whose
> relfilenode is relatively recognizable. Say pg_class. It probably
> doesn't matter that much, but there is perhaps some small value in
> picking an example that is relatively easy to recreate later on (or to
> approximately recreate). I'm certainly not insisting on that, though.

I've added such an example to pg_walinspect docs.

On Tue, Mar 21, 2023 at 6:37 PM Peter Geoghegan <p...@bowt.ie> wrote:
>
> On Mon, Mar 13, 2023 at 6:41 PM Peter Geoghegan <p...@bowt.ie> wrote:
> > There are several different things that seem important to me
> > personally. These are in tension with each other, to a degree. These
> > are:
> >
> > 1. Like Andres, I'd really like to have some way of inspecting things
> > like heapam PRUNE, VACUUM, and FREEZE_PAGE records in significant
> > detail. These record types happen to be very important in general, and
> > the ability to see detailed information about the WAL record would
> > definitely help with some debugging scenarios. I've really missed
> > stuff like this while debugging serious issues under time pressure.
>
> One problem that I often run into when performing analysis of VACUUM
> using pg_walinspect is the issue of *who* pruned which heap page, for
> any given PRUNE record. Was it VACUUM/autovacuum, or was it
> opportunistic pruning? There is no way of knowing for sure right now.
> You *cannot* rely on an xid of 0 as an indicator of a given PRUNE
> record coming from VACUUM; it could just have been an opportunistic
> prune operation that happened to take place when a SELECT query ran,
> before any XID was ever allocated.
>
> I think that we should do something like the attached, to completely
> avoid this ambiguity. This patch adds a new XLOG_HEAP2 bit that's
> similar to XLOG_HEAP_INIT_PAGE -- XLOG_HEAP2_BYVACUUM. This allows all
> XLOG_HEAP2 record types to indicate that they took place during
> VACUUM, by XOR'ing the flag with the record type/info when
> XLogInsert() is called. For now this is only used by PRUNE records.
> Tools like pg_walinspect will report a separate "Heap2/PRUNE+BYVACUUM"
> record_type, as well as the unadorned Heap2/PRUNE record_type, which
> we'll now know must have been opportunistic pruning.
>
> The approach of using a bit in the style of the heapam init bit makes
> sense to me, because the bit is available, and works in a way that is
> minimally invasive. Also, one can imagine needing to resolve a similar
> ambiguity in the future, when (say) opportunistic freezing is added.
>
> I think that it makes sense to treat this within the scope of
> Melanie's ongoing work to improve the instrumentation of these records
> -- meaning that it's in scope for Postgres 16. Admittedly this is a
> slightly creative interpretation, so if others disagree then I won't
> argue. This is quite a small patch, though, which makes debugging
> significantly easier. I think that there could be a great deal of
> utility in being able to easily "pair up" corresponding
> "Heap2/PRUNE+BYVACUUM" and "Heap2/VACUUM" records in debugging
> scenarios. I can imagine linking these to "Heap2/FREEZE_PAGE" and
> "Heap2/VISIBLE" records, too, since they're all closely related record
> types.

I really like this idea and would find it useful. I reviewed the patch
and tried it out and it worked for me and code looked fine as well.

I didn't include it in the attached patchset because I don't feel
confident enough in my own understanding of any potential implications
of splitting up these record types to definitively endorse it. But, if
someone else felt comfortable with it, I would like to see it in the
tree.

- Melanie
From b584717e9de72c1ac084698453667cb301551e36 Mon Sep 17 00:00:00 2001
From: Peter Geoghegan <p...@bowt.ie>
Date: Tue, 21 Mar 2023 14:40:43 -0700
Subject: [PATCH v1] Record which PRUNE records are from VACUUM.

---
 src/include/access/heapam.h            |  2 +-
 src/include/access/heapam_xlog.h       |  8 +++++++-
 src/backend/access/heap/pruneheap.c    | 25 +++++++++++++++----------
 src/backend/access/rmgrdesc/heapdesc.c |  3 +++
 4 files changed, 26 insertions(+), 12 deletions(-)

diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index faf502651..2eedd7efd 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -289,7 +289,7 @@ extern int	heap_page_prune(Relation relation, Buffer buffer,
 							TransactionId old_snap_xmin,
 							TimestampTz old_snap_ts,
 							int *nnewlpdead,
-							OffsetNumber *off_loc);
+							OffsetNumber *off_loc_vacuum);
 extern void heap_page_prune_execute(Buffer buffer,
 									OffsetNumber *redirected, int nredirected,
 									OffsetNumber *nowdead, int ndead,
diff --git a/src/include/access/heapam_xlog.h b/src/include/access/heapam_xlog.h
index a2c67d1cd..3372d1938 100644
--- a/src/include/access/heapam_xlog.h
+++ b/src/include/access/heapam_xlog.h
@@ -48,7 +48,7 @@
  * We ran out of opcodes, so heapam.c now has a second RmgrId.  These opcodes
  * are associated with RM_HEAP2_ID, but are not logically different from
  * the ones above associated with RM_HEAP_ID.  XLOG_HEAP_OPMASK applies to
- * these, too.
+ * these, too.  The BYVACUUM bit is used in place of RM_HEAP_ID's init bit.
  */
 #define XLOG_HEAP2_REWRITE		0x00
 #define XLOG_HEAP2_PRUNE		0x10
@@ -59,6 +59,12 @@
 #define XLOG_HEAP2_LOCK_UPDATED 0x60
 #define XLOG_HEAP2_NEW_CID		0x70
 
+/*
+ * RM_HEAP2_ID operations that take place during VACUUM set the BYVACUUM bit
+ * as instrumentation.  Only set in XLOG_HEAP2_PRUNE records, for now.
+ */
+#define XLOG_HEAP2_BYVACUUM		0x80
+
 /*
  * xl_heap_insert/xl_heap_multi_insert flag values, 8 bits are available.
  */
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 4e65cbcad..5f4ef384e 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -257,8 +257,8 @@ heap_page_prune_opt(Relation relation, Buffer buffer)
  * Sets *nnewlpdead for caller, indicating the number of items that were
  * newly set LP_DEAD during prune operation.
  *
- * off_loc is the offset location required by the caller to use in error
- * callback.
+ * off_loc_vacuum is the offset location required by VACUUM caller to use in
+ * error callback.  Opportunistic pruning caller should pass NULL.
  *
  * Returns the number of tuples deleted from the page during this call.
  */
@@ -268,7 +268,7 @@ heap_page_prune(Relation relation, Buffer buffer,
 				TransactionId old_snap_xmin,
 				TimestampTz old_snap_ts,
 				int *nnewlpdead,
-				OffsetNumber *off_loc)
+				OffsetNumber *off_loc_vacuum)
 {
 	int			ndeleted = 0;
 	Page		page = BufferGetPage(buffer);
@@ -345,8 +345,8 @@ heap_page_prune(Relation relation, Buffer buffer,
 		 * Set the offset number so that we can display it along with any
 		 * error that occurred while processing this tuple.
 		 */
-		if (off_loc)
-			*off_loc = offnum;
+		if (off_loc_vacuum)
+			*off_loc_vacuum = offnum;
 
 		prstate.htsv[offnum] = heap_prune_satisfies_vacuum(&prstate, &tup,
 														   buffer);
@@ -364,8 +364,8 @@ heap_page_prune(Relation relation, Buffer buffer,
 			continue;
 
 		/* see preceding loop */
-		if (off_loc)
-			*off_loc = offnum;
+		if (off_loc_vacuum)
+			*off_loc_vacuum = offnum;
 
 		/* Nothing to do if slot is empty or already dead */
 		itemid = PageGetItemId(page, offnum);
@@ -377,8 +377,8 @@ heap_page_prune(Relation relation, Buffer buffer,
 	}
 
 	/* Clear the offset information once we have processed the given page. */
-	if (off_loc)
-		*off_loc = InvalidOffsetNumber;
+	if (off_loc_vacuum)
+		*off_loc_vacuum = InvalidOffsetNumber;
 
 	/* Any error while applying the changes is critical */
 	START_CRIT_SECTION();
@@ -416,6 +416,7 @@ heap_page_prune(Relation relation, Buffer buffer,
 		if (RelationNeedsWAL(relation))
 		{
 			xl_heap_prune xlrec;
+			uint8		info = XLOG_HEAP2_PRUNE;
 			XLogRecPtr	recptr;
 
 			xlrec.snapshotConflictHorizon = prstate.snapshotConflictHorizon;
@@ -445,7 +446,11 @@ heap_page_prune(Relation relation, Buffer buffer,
 				XLogRegisterBufData(0, (char *) prstate.nowunused,
 									prstate.nunused * sizeof(OffsetNumber));
 
-			recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_PRUNE);
+			/* Set bit indicating pruning by VACUUM where appropriate */
+			if (off_loc_vacuum)
+				info |= XLOG_HEAP2_BYVACUUM;
+
+			recptr = XLogInsert(RM_HEAP2_ID, info);
 
 			PageSetLSN(BufferGetPage(buffer), recptr);
 		}
diff --git a/src/backend/access/rmgrdesc/heapdesc.c b/src/backend/access/rmgrdesc/heapdesc.c
index 628f7e821..9231b7411 100644
--- a/src/backend/access/rmgrdesc/heapdesc.c
+++ b/src/backend/access/rmgrdesc/heapdesc.c
@@ -235,6 +235,9 @@ heap2_identify(uint8 info)
 		case XLOG_HEAP2_PRUNE:
 			id = "PRUNE";
 			break;
+		case XLOG_HEAP2_PRUNE | XLOG_HEAP2_BYVACUUM:
+			id = "PRUNE+BYVACUUM";
+			break;
 		case XLOG_HEAP2_VACUUM:
 			id = "VACUUM";
 			break;
-- 
2.39.2

From 5b33b8fa323db65f3cf3b0dcc343071a7975b649 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplage...@gmail.com>
Date: Wed, 25 Jan 2023 08:38:38 -0500
Subject: [PATCH v3 1/2] Add rmgr_desc utilities

- Begin to standardize format of rmgr_desc output, adding a new
  rmgr_utils file with comments about expected format.
- Add helper which prints arrays in a standard format and use it in
  heapdesc to print out offset arrays, relid arrays, freeze plan arrays,
  and redirect arrays.

Suggested by Andres Freund

Discussion: https://postgr.es/m/flat/20230109215842.fktuhesvayno6o4g%40awork3.anarazel.de
---
 doc/src/sgml/pgwalinspect.sgml               |  14 +-
 src/backend/access/rmgrdesc/Makefile         |   1 +
 src/backend/access/rmgrdesc/heapdesc.c       | 152 +++++++++++++++----
 src/backend/access/rmgrdesc/meson.build      |   1 +
 src/backend/access/rmgrdesc/rmgrdesc_utils.c |  82 ++++++++++
 src/bin/pg_waldump/Makefile                  |   2 +-
 src/include/access/rmgrdesc_utils.h          |  29 ++++
 7 files changed, 245 insertions(+), 36 deletions(-)
 create mode 100644 src/backend/access/rmgrdesc/rmgrdesc_utils.c
 create mode 100644 src/include/access/rmgrdesc_utils.h

diff --git a/doc/src/sgml/pgwalinspect.sgml b/doc/src/sgml/pgwalinspect.sgml
index d9ed8f0a9a..7fb089e83a 100644
--- a/doc/src/sgml/pgwalinspect.sgml
+++ b/doc/src/sgml/pgwalinspect.sgml
@@ -110,18 +110,18 @@ block_ref        | blkref #0: rel 1663/5/60221 fork main blk 2
       Returns one row per WAL record.  For example:
 <screen>
 postgres=# SELECT * FROM pg_get_wal_records_info('0/1E913618', '0/1E913740') LIMIT 1;
--[ RECORD 1 ]----+--------------------------------------------------------------
+-[ RECORD 1 ]----+------------------------------------------------------------------------------------------------------------------------
 start_lsn        | 0/1E913618
 end_lsn          | 0/1E913650
 prev_lsn         | 0/1E9135A0
 xid              | 0
-resource_manager | Standby
-record_type      | RUNNING_XACTS
-record_length    | 50
-main_data_length | 24
+resource_manager | Heap2
+record_type      | PRUNE
+record_length    | 61
+main_data_length | 9
 fpi_length       | 0
-description      | nextXid 33775 latestCompletedXid 33774 oldestRunningXid 33775
-block_ref        |
+description      | snapshotConflictHorizon: 754, nredirected: 1, ndead: 1, nunused: 0, redirected: [ 1->5, 6->5 ], dead: [ 6 ], unused: []
+block_ref        | blkref #0: rel 1663/5/1259 fork main blk 1
 </screen>
      </para>
      <para>
diff --git a/src/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile
index f88d72fd86..cd95eec37f 100644
--- a/src/backend/access/rmgrdesc/Makefile
+++ b/src/backend/access/rmgrdesc/Makefile
@@ -23,6 +23,7 @@ OBJS = \
 	nbtdesc.o \
 	relmapdesc.o \
 	replorigindesc.o \
+	rmgrdesc_utils.o \
 	seqdesc.o \
 	smgrdesc.o \
 	spgdesc.o \
diff --git a/src/backend/access/rmgrdesc/heapdesc.c b/src/backend/access/rmgrdesc/heapdesc.c
index 628f7e8215..1041db239f 100644
--- a/src/backend/access/rmgrdesc/heapdesc.c
+++ b/src/backend/access/rmgrdesc/heapdesc.c
@@ -15,20 +15,53 @@
 #include "postgres.h"
 
 #include "access/heapam_xlog.h"
+#include "access/rmgrdesc_utils.h"
+
 
 static void
 out_infobits(StringInfo buf, uint8 infobits)
 {
+	if ((infobits & XLHL_XMAX_IS_MULTI) == 0 &&
+		(infobits & XLHL_XMAX_LOCK_ONLY) == 0 &&
+		(infobits & XLHL_XMAX_EXCL_LOCK) == 0 &&
+		(infobits & XLHL_XMAX_KEYSHR_LOCK) == 0 &&
+		(infobits & XLHL_KEYS_UPDATED) == 0)
+		return;
+
+	appendStringInfoString(buf, ", infobits: [");
+
 	if (infobits & XLHL_XMAX_IS_MULTI)
-		appendStringInfoString(buf, "IS_MULTI ");
+		appendStringInfoString(buf, " IS_MULTI");
 	if (infobits & XLHL_XMAX_LOCK_ONLY)
-		appendStringInfoString(buf, "LOCK_ONLY ");
+		appendStringInfoString(buf, ", LOCK_ONLY");
 	if (infobits & XLHL_XMAX_EXCL_LOCK)
-		appendStringInfoString(buf, "EXCL_LOCK ");
+		appendStringInfoString(buf, ", EXCL_LOCK");
 	if (infobits & XLHL_XMAX_KEYSHR_LOCK)
-		appendStringInfoString(buf, "KEYSHR_LOCK ");
+		appendStringInfoString(buf, ", KEYSHR_LOCK");
 	if (infobits & XLHL_KEYS_UPDATED)
-		appendStringInfoString(buf, "KEYS_UPDATED ");
+		appendStringInfoString(buf, ", KEYS_UPDATED");
+
+	appendStringInfoString(buf, " ]");
+}
+
+static void
+plan_elem_desc(StringInfo buf, void *restrict plan, void *restrict data)
+{
+	xl_heap_freeze_plan *new_plan = (xl_heap_freeze_plan *) plan;
+	OffsetNumber **offsets = data;
+
+	appendStringInfo(buf, "{ xmax: %u, infomask: %u, infomask2: %u, ntuples: %u",
+					 new_plan->xmax, new_plan->t_infomask,
+					 new_plan->t_infomask2,
+					 new_plan->ntuples);
+
+	appendStringInfoString(buf, ", offsets:");
+	array_desc(buf, *offsets, sizeof(OffsetNumber), new_plan->ntuples,
+			   &offset_elem_desc, NULL);
+
+	*offsets += new_plan->ntuples;
+
+	appendStringInfo(buf, " }");
 }
 
 void
@@ -42,14 +75,15 @@ heap_desc(StringInfo buf, XLogReaderState *record)
 	{
 		xl_heap_insert *xlrec = (xl_heap_insert *) rec;
 
-		appendStringInfo(buf, "off %u flags 0x%02X", xlrec->offnum,
+		appendStringInfo(buf, "off: %u, flags: 0x%02X",
+						 xlrec->offnum,
 						 xlrec->flags);
 	}
 	else if (info == XLOG_HEAP_DELETE)
 	{
 		xl_heap_delete *xlrec = (xl_heap_delete *) rec;
 
-		appendStringInfo(buf, "off %u flags 0x%02X ",
+		appendStringInfo(buf, "off: %u, flags: 0x%02X",
 						 xlrec->offnum,
 						 xlrec->flags);
 		out_infobits(buf, xlrec->infobits_set);
@@ -58,12 +92,12 @@ heap_desc(StringInfo buf, XLogReaderState *record)
 	{
 		xl_heap_update *xlrec = (xl_heap_update *) rec;
 
-		appendStringInfo(buf, "off %u xmax %u flags 0x%02X ",
+		appendStringInfo(buf, "off: %u, xmax: %u, flags: 0x%02X",
 						 xlrec->old_offnum,
 						 xlrec->old_xmax,
 						 xlrec->flags);
 		out_infobits(buf, xlrec->old_infobits_set);
-		appendStringInfo(buf, "; new off %u xmax %u",
+		appendStringInfo(buf, ", new off: %u, xmax %u",
 						 xlrec->new_offnum,
 						 xlrec->new_xmax);
 	}
@@ -71,39 +105,41 @@ heap_desc(StringInfo buf, XLogReaderState *record)
 	{
 		xl_heap_update *xlrec = (xl_heap_update *) rec;
 
-		appendStringInfo(buf, "off %u xmax %u flags 0x%02X ",
+		appendStringInfo(buf, "off: %u, xmax: %u, flags: 0x%02X",
 						 xlrec->old_offnum,
 						 xlrec->old_xmax,
 						 xlrec->flags);
 		out_infobits(buf, xlrec->old_infobits_set);
-		appendStringInfo(buf, "; new off %u xmax %u",
+		appendStringInfo(buf, ", new off: %u, xmax: %u",
 						 xlrec->new_offnum,
 						 xlrec->new_xmax);
 	}
 	else if (info == XLOG_HEAP_TRUNCATE)
 	{
 		xl_heap_truncate *xlrec = (xl_heap_truncate *) rec;
-		int			i;
 
+		appendStringInfoString(buf, "flags: [");
 		if (xlrec->flags & XLH_TRUNCATE_CASCADE)
-			appendStringInfoString(buf, "cascade ");
+			appendStringInfoString(buf, " CASCADE");
 		if (xlrec->flags & XLH_TRUNCATE_RESTART_SEQS)
-			appendStringInfoString(buf, "restart_seqs ");
-		appendStringInfo(buf, "nrelids %u relids", xlrec->nrelids);
-		for (i = 0; i < xlrec->nrelids; i++)
-			appendStringInfo(buf, " %u", xlrec->relids[i]);
+			appendStringInfoString(buf, ", RESTART_SEQS");
+		appendStringInfoString(buf, " ]");
+
+		appendStringInfo(buf, ", nrelids: %u", xlrec->nrelids);
+		appendStringInfoString(buf, ", relids:");
+		array_desc(buf, xlrec->relids, sizeof(Oid), xlrec->nrelids, &relid_desc, NULL);
 	}
 	else if (info == XLOG_HEAP_CONFIRM)
 	{
 		xl_heap_confirm *xlrec = (xl_heap_confirm *) rec;
 
-		appendStringInfo(buf, "off %u", xlrec->offnum);
+		appendStringInfo(buf, "off: %u", xlrec->offnum);
 	}
 	else if (info == XLOG_HEAP_LOCK)
 	{
 		xl_heap_lock *xlrec = (xl_heap_lock *) rec;
 
-		appendStringInfo(buf, "off %u: xid %u: flags 0x%02X ",
+		appendStringInfo(buf, "off: %u, xid: %u, flags: 0x%02X",
 						 xlrec->offnum, xlrec->locking_xid, xlrec->flags);
 		out_infobits(buf, xlrec->infobits_set);
 	}
@@ -111,9 +147,10 @@ heap_desc(StringInfo buf, XLogReaderState *record)
 	{
 		xl_heap_inplace *xlrec = (xl_heap_inplace *) rec;
 
-		appendStringInfo(buf, "off %u", xlrec->offnum);
+		appendStringInfo(buf, "off: %u", xlrec->offnum);
 	}
 }
+
 void
 heap2_desc(StringInfo buf, XLogReaderState *record)
 {
@@ -125,43 +162,102 @@ heap2_desc(StringInfo buf, XLogReaderState *record)
 	{
 		xl_heap_prune *xlrec = (xl_heap_prune *) rec;
 
-		appendStringInfo(buf, "snapshotConflictHorizon %u nredirected %u ndead %u",
+		appendStringInfo(buf, "snapshotConflictHorizon: %u, nredirected: %u, ndead: %u",
 						 xlrec->snapshotConflictHorizon,
 						 xlrec->nredirected,
 						 xlrec->ndead);
+
+
+		if (!XLogRecHasBlockImage(record, 0))
+		{
+			OffsetNumber *end;
+			OffsetNumber *redirected;
+			OffsetNumber *nowdead;
+			OffsetNumber *nowunused;
+			int			nredirected;
+			int			nunused;
+			Size		datalen;
+
+			redirected = (OffsetNumber *) XLogRecGetBlockData(record, 0, &datalen);
+
+			nredirected = xlrec->nredirected;
+			end = (OffsetNumber *) ((char *) redirected + datalen);
+			nowdead = redirected + (nredirected * 2);
+			nowunused = nowdead + xlrec->ndead;
+			nunused = (end - nowunused);
+			Assert(nunused >= 0);
+
+			appendStringInfo(buf, ", nunused: %u", nunused);
+
+			appendStringInfoString(buf, ", redirected:");
+			array_desc(buf, redirected, sizeof(OffsetNumber) * 2, nredirected * 2, &redirect_elem_desc, NULL);
+			appendStringInfoString(buf, ", dead:");
+			array_desc(buf, nowdead, sizeof(OffsetNumber), xlrec->ndead, &offset_elem_desc, NULL);
+			appendStringInfoString(buf, ", unused:");
+			array_desc(buf, nowunused, sizeof(OffsetNumber), nunused, &offset_elem_desc, NULL);
+		}
 	}
 	else if (info == XLOG_HEAP2_VACUUM)
 	{
 		xl_heap_vacuum *xlrec = (xl_heap_vacuum *) rec;
 
-		appendStringInfo(buf, "nunused %u", xlrec->nunused);
+		appendStringInfo(buf, "nunused: %u", xlrec->nunused);
+
+		if (!XLogRecHasBlockImage(record, 0))
+		{
+			OffsetNumber *nowunused;
+
+			nowunused = (OffsetNumber *) XLogRecGetBlockData(record, 0, NULL);
+
+			appendStringInfoString(buf, ", unused:");
+			array_desc(buf, nowunused, sizeof(OffsetNumber), xlrec->nunused, &offset_elem_desc, NULL);
+		}
 	}
 	else if (info == XLOG_HEAP2_FREEZE_PAGE)
 	{
 		xl_heap_freeze_page *xlrec = (xl_heap_freeze_page *) rec;
 
-		appendStringInfo(buf, "snapshotConflictHorizon %u nplans %u",
+		appendStringInfo(buf, "snapshotConflictHorizon: %u, nplans: %u",
 						 xlrec->snapshotConflictHorizon, xlrec->nplans);
+
+		if (!XLogRecHasBlockImage(record, 0))
+		{
+			xl_heap_freeze_plan *plans = (xl_heap_freeze_plan *) XLogRecGetBlockData(record, 0, NULL);
+			OffsetNumber *offsets = (OffsetNumber *) &plans[xlrec->nplans];
+
+			appendStringInfoString(buf, ", plans:");
+			array_desc(buf,
+					   plans,
+					   sizeof(xl_heap_freeze_plan),
+					   xlrec->nplans,
+					   &plan_elem_desc,
+					   &offsets);
+
+		}
 	}
 	else if (info == XLOG_HEAP2_VISIBLE)
 	{
 		xl_heap_visible *xlrec = (xl_heap_visible *) rec;
 
-		appendStringInfo(buf, "snapshotConflictHorizon %u flags 0x%02X",
+		appendStringInfo(buf, "snapshotConflictHorizon: %u, flags: 0x%02X",
 						 xlrec->snapshotConflictHorizon, xlrec->flags);
 	}
 	else if (info == XLOG_HEAP2_MULTI_INSERT)
 	{
 		xl_heap_multi_insert *xlrec = (xl_heap_multi_insert *) rec;
+		bool		isinit = (XLogRecGetInfo(record) & XLOG_HEAP_INIT_PAGE) != 0;
 
-		appendStringInfo(buf, "%d tuples flags 0x%02X", xlrec->ntuples,
+		appendStringInfo(buf, "ntuples: %d, flags: 0x%02X", xlrec->ntuples,
 						 xlrec->flags);
+		appendStringInfoString(buf, ", offsets:");
+		if (!XLogRecHasBlockImage(record, 0) && !isinit)
+			array_desc(buf, xlrec->offsets, sizeof(OffsetNumber), xlrec->ntuples, &offset_elem_desc, NULL);
 	}
 	else if (info == XLOG_HEAP2_LOCK_UPDATED)
 	{
 		xl_heap_lock_updated *xlrec = (xl_heap_lock_updated *) rec;
 
-		appendStringInfo(buf, "off %u: xmax %u: flags 0x%02X ",
+		appendStringInfo(buf, "off: %u, xmax: %u, flags: 0x%02X",
 						 xlrec->offnum, xlrec->xmax, xlrec->flags);
 		out_infobits(buf, xlrec->infobits_set);
 	}
@@ -169,13 +265,13 @@ heap2_desc(StringInfo buf, XLogReaderState *record)
 	{
 		xl_heap_new_cid *xlrec = (xl_heap_new_cid *) rec;
 
-		appendStringInfo(buf, "rel %u/%u/%u; tid %u/%u",
+		appendStringInfo(buf, "rel: %u/%u/%u, tid: %u/%u",
 						 xlrec->target_locator.spcOid,
 						 xlrec->target_locator.dbOid,
 						 xlrec->target_locator.relNumber,
 						 ItemPointerGetBlockNumber(&(xlrec->target_tid)),
 						 ItemPointerGetOffsetNumber(&(xlrec->target_tid)));
-		appendStringInfo(buf, "; cmin: %u, cmax: %u, combo: %u",
+		appendStringInfo(buf, ", cmin: %u, cmax: %u, combo: %u",
 						 xlrec->cmin, xlrec->cmax, xlrec->combocid);
 	}
 }
diff --git a/src/backend/access/rmgrdesc/meson.build b/src/backend/access/rmgrdesc/meson.build
index 166cee67b6..f76e87e2d7 100644
--- a/src/backend/access/rmgrdesc/meson.build
+++ b/src/backend/access/rmgrdesc/meson.build
@@ -16,6 +16,7 @@ rmgr_desc_sources = files(
   'nbtdesc.c',
   'relmapdesc.c',
   'replorigindesc.c',
+  'rmgrdesc_utils.c',
   'seqdesc.c',
   'smgrdesc.c',
   'spgdesc.c',
diff --git a/src/backend/access/rmgrdesc/rmgrdesc_utils.c b/src/backend/access/rmgrdesc/rmgrdesc_utils.c
new file mode 100644
index 0000000000..2bfd11cb6e
--- /dev/null
+++ b/src/backend/access/rmgrdesc/rmgrdesc_utils.c
@@ -0,0 +1,82 @@
+/*-------------------------------------------------------------------------
+ *
+ * rmgrdesc_utils.c
+ *		support functions for rmgrdesc
+ *
+ * Portions Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/rmgrdesc/rmgrdesc_utils.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/rmgrdesc_utils.h"
+
+/*
+ * The expected formatting of a desc function for an xlog record is as follows:
+ * member1_name: member1_value, member2_name: member2_value
+ * If the value is a list, please use
+ * member3_name: [ member3_list_value1, member3_list_value2 ]
+ *
+ * The first item appended to the string should not be prepended by any spaces
+ * or comma, however all subsequent appends to the string are responsible for
+ * prepending themselves with a comma followed by a space.
+ *
+ * Arrays should have a space between the opening square bracket and first
+ * element and between the last element and closing brace.
+ *
+ * Flags should be in all caps.
+ *
+ * For lists/arrays of items, the number of those items should be listed at the
+ * beginning with all of the other numbers.
+ *
+ * List punctuation should still be included even if there are 0 items.
+ *
+ * Composite objects in a list should be surrounded with { }.
+ */
+
+void
+array_desc(StringInfo buf, void *array, size_t elem_size, int count,
+		   void (*elem_desc) (StringInfo buf, void *restrict elem, void *restrict data),
+		   void *restrict data)
+{
+	if (count == 0)
+	{
+		appendStringInfoString(buf, " []");
+		return;
+	}
+	appendStringInfo(buf, " [");
+	for (int i = 0; i < count; i++)
+	{
+		if (i > 0)
+			appendStringInfoString(buf, ",");
+		appendStringInfoString(buf, " ");
+
+		elem_desc(buf, (char *) array + elem_size * i, data);
+	}
+	appendStringInfoString(buf, " ]");
+}
+
+void
+offset_elem_desc(StringInfo buf, void *restrict offset, void *restrict data)
+{
+	appendStringInfo(buf, "%u", *(OffsetNumber *) offset);
+}
+
+void
+redirect_elem_desc(StringInfo buf, void *restrict offset, void *restrict data)
+{
+	OffsetNumber *new_offset = (OffsetNumber *) offset;
+
+	appendStringInfo(buf, "%u->%u", new_offset[0], new_offset[1]);
+}
+
+void
+relid_desc(StringInfo buf, void *restrict relid, void *restrict data)
+{
+	appendStringInfo(buf, "%u", *(Oid *) relid);
+}
diff --git a/src/bin/pg_waldump/Makefile b/src/bin/pg_waldump/Makefile
index d6459e17c7..61c9de470c 100644
--- a/src/bin/pg_waldump/Makefile
+++ b/src/bin/pg_waldump/Makefile
@@ -18,7 +18,7 @@ OBJS = \
 
 override CPPFLAGS := -DFRONTEND $(CPPFLAGS)
 
-RMGRDESCSOURCES = $(sort $(notdir $(wildcard $(top_srcdir)/src/backend/access/rmgrdesc/*desc.c)))
+RMGRDESCSOURCES = $(sort $(notdir $(wildcard $(top_srcdir)/src/backend/access/rmgrdesc/*desc.c) $(top_srcdir)/src/backend/access/rmgrdesc/rmgrdesc_utils.c))
 RMGRDESCOBJS = $(patsubst %.c,%.o,$(RMGRDESCSOURCES))
 
 
diff --git a/src/include/access/rmgrdesc_utils.h b/src/include/access/rmgrdesc_utils.h
new file mode 100644
index 0000000000..763ae81859
--- /dev/null
+++ b/src/include/access/rmgrdesc_utils.h
@@ -0,0 +1,29 @@
+/*-------------------------------------------------------------------------
+ *
+ * rmgrdesc_utils.h
+ *	  helper utilities for rmgrdesc
+ *
+ * Copyright (c) 2023 PostgreSQL Global Development Group
+ *
+ * src/include/access/rmgrdesc_utils.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef RMGRDESC_UTILS_H_
+#define RMGRDESC_UTILS_H_
+
+#include "storage/off.h"
+#include "access/heapam_xlog.h"
+
+extern void array_desc(StringInfo buf, void *array, size_t elem_size, int count,
+					   void (*elem_desc) (StringInfo buf, void *restrict elem, void *restrict data),
+					   void *restrict data);
+
+extern void offset_elem_desc(StringInfo buf, void *restrict offset, void *restrict data);
+
+extern void redirect_elem_desc(StringInfo buf, void *restrict offset, void *restrict data);
+
+extern void relid_desc(StringInfo buf, void *restrict relid, void *restrict data);
+
+
+#endif							/* RMGRDESC_UTILS_H */
-- 
2.37.2

Reply via email to