mirror of
				https://github.com/postgres/postgres.git
				synced 2025-11-03 09:13:20 +03:00 
			
		
		
		
	It seems potentially useful to label our shared libraries with version information, now that a facility exists for retrieving that. This patch labels them with the PG_VERSION string. There was some discussion about using semantic versioning conventions, but that doesn't seem terribly helpful for modules with no SQL-level presence; and for those that do have SQL objects, we typically expect them to support multiple revisions of the SQL definitions, so it'd still not be very helpful. I did not label any of src/test/modules/. It seems unnecessary since we don't install those, and besides there ought to be someplace that still provides test coverage for the original PG_MODULE_MAGIC macro. Author: Tom Lane <tgl@sss.pgh.pa.us> Discussion: https://postgr.es/m/dd4d1b59-d0fe-49d5-b28f-1e463b68fa32@gmail.com
		
			
				
	
	
		
			856 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			856 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*-------------------------------------------------------------------------
 | 
						|
 *
 | 
						|
 * pg_walinspect.c
 | 
						|
 *		  Functions to inspect contents of PostgreSQL Write-Ahead Log
 | 
						|
 *
 | 
						|
 * Copyright (c) 2022-2025, PostgreSQL Global Development Group
 | 
						|
 *
 | 
						|
 * IDENTIFICATION
 | 
						|
 *		  contrib/pg_walinspect/pg_walinspect.c
 | 
						|
 *
 | 
						|
 *-------------------------------------------------------------------------
 | 
						|
 */
 | 
						|
#include "postgres.h"
 | 
						|
 | 
						|
#include "access/xlog.h"
 | 
						|
#include "access/xlog_internal.h"
 | 
						|
#include "access/xlogreader.h"
 | 
						|
#include "access/xlogrecovery.h"
 | 
						|
#include "access/xlogstats.h"
 | 
						|
#include "access/xlogutils.h"
 | 
						|
#include "funcapi.h"
 | 
						|
#include "miscadmin.h"
 | 
						|
#include "utils/array.h"
 | 
						|
#include "utils/builtins.h"
 | 
						|
#include "utils/pg_lsn.h"
 | 
						|
 | 
						|
/*
 | 
						|
 * NOTE: For any code change or issue fix here, it is highly recommended to
 | 
						|
 * give a thought about doing the same in pg_waldump tool as well.
 | 
						|
 */
 | 
						|
 | 
						|
PG_MODULE_MAGIC_EXT(
 | 
						|
					.name = "pg_walinspect",
 | 
						|
					.version = PG_VERSION
 | 
						|
);
 | 
						|
 | 
						|
PG_FUNCTION_INFO_V1(pg_get_wal_block_info);
 | 
						|
PG_FUNCTION_INFO_V1(pg_get_wal_record_info);
 | 
						|
PG_FUNCTION_INFO_V1(pg_get_wal_records_info);
 | 
						|
PG_FUNCTION_INFO_V1(pg_get_wal_records_info_till_end_of_wal);
 | 
						|
PG_FUNCTION_INFO_V1(pg_get_wal_stats);
 | 
						|
PG_FUNCTION_INFO_V1(pg_get_wal_stats_till_end_of_wal);
 | 
						|
 | 
						|
static void ValidateInputLSNs(XLogRecPtr start_lsn, XLogRecPtr *end_lsn);
 | 
						|
static XLogRecPtr GetCurrentLSN(void);
 | 
						|
static XLogReaderState *InitXLogReaderState(XLogRecPtr lsn);
 | 
						|
static XLogRecord *ReadNextXLogRecord(XLogReaderState *xlogreader);
 | 
						|
static void GetWALRecordInfo(XLogReaderState *record, Datum *values,
 | 
						|
							 bool *nulls, uint32 ncols);
 | 
						|
static void GetWALRecordsInfo(FunctionCallInfo fcinfo,
 | 
						|
							  XLogRecPtr start_lsn,
 | 
						|
							  XLogRecPtr end_lsn);
 | 
						|
static void GetXLogSummaryStats(XLogStats *stats, ReturnSetInfo *rsinfo,
 | 
						|
								Datum *values, bool *nulls, uint32 ncols,
 | 
						|
								bool stats_per_record);
 | 
						|
static void FillXLogStatsRow(const char *name, uint64 n, uint64 total_count,
 | 
						|
							 uint64 rec_len, uint64 total_rec_len,
 | 
						|
							 uint64 fpi_len, uint64 total_fpi_len,
 | 
						|
							 uint64 tot_len, uint64 total_len,
 | 
						|
							 Datum *values, bool *nulls, uint32 ncols);
 | 
						|
static void GetWalStats(FunctionCallInfo fcinfo,
 | 
						|
						XLogRecPtr start_lsn,
 | 
						|
						XLogRecPtr end_lsn,
 | 
						|
						bool stats_per_record);
 | 
						|
static void GetWALBlockInfo(FunctionCallInfo fcinfo, XLogReaderState *record,
 | 
						|
							bool show_data);
 | 
						|
 | 
						|
/*
 | 
						|
 * Return the LSN up to which the server has WAL.
 | 
						|
 */
 | 
						|
static XLogRecPtr
 | 
						|
GetCurrentLSN(void)
 | 
						|
{
 | 
						|
	XLogRecPtr	curr_lsn;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * We determine the current LSN of the server similar to how page_read
 | 
						|
	 * callback read_local_xlog_page_no_wait does.
 | 
						|
	 */
 | 
						|
	if (!RecoveryInProgress())
 | 
						|
		curr_lsn = GetFlushRecPtr(NULL);
 | 
						|
	else
 | 
						|
		curr_lsn = GetXLogReplayRecPtr(NULL);
 | 
						|
 | 
						|
	Assert(!XLogRecPtrIsInvalid(curr_lsn));
 | 
						|
 | 
						|
	return curr_lsn;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Initialize WAL reader and identify first valid LSN.
 | 
						|
 */
 | 
						|
static XLogReaderState *
 | 
						|
InitXLogReaderState(XLogRecPtr lsn)
 | 
						|
{
 | 
						|
	XLogReaderState *xlogreader;
 | 
						|
	ReadLocalXLogPageNoWaitPrivate *private_data;
 | 
						|
	XLogRecPtr	first_valid_record;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Reading WAL below the first page of the first segments isn't allowed.
 | 
						|
	 * This is a bootstrap WAL page and the page_read callback fails to read
 | 
						|
	 * it.
 | 
						|
	 */
 | 
						|
	if (lsn < XLOG_BLCKSZ)
 | 
						|
		ereport(ERROR,
 | 
						|
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 | 
						|
				 errmsg("could not read WAL at LSN %X/%X",
 | 
						|
						LSN_FORMAT_ARGS(lsn))));
 | 
						|
 | 
						|
	private_data = (ReadLocalXLogPageNoWaitPrivate *)
 | 
						|
		palloc0(sizeof(ReadLocalXLogPageNoWaitPrivate));
 | 
						|
 | 
						|
	xlogreader = XLogReaderAllocate(wal_segment_size, NULL,
 | 
						|
									XL_ROUTINE(.page_read = &read_local_xlog_page_no_wait,
 | 
						|
											   .segment_open = &wal_segment_open,
 | 
						|
											   .segment_close = &wal_segment_close),
 | 
						|
									private_data);
 | 
						|
 | 
						|
	if (xlogreader == NULL)
 | 
						|
		ereport(ERROR,
 | 
						|
				(errcode(ERRCODE_OUT_OF_MEMORY),
 | 
						|
				 errmsg("out of memory"),
 | 
						|
				 errdetail("Failed while allocating a WAL reading processor.")));
 | 
						|
 | 
						|
	/* first find a valid recptr to start from */
 | 
						|
	first_valid_record = XLogFindNextRecord(xlogreader, lsn);
 | 
						|
 | 
						|
	if (XLogRecPtrIsInvalid(first_valid_record))
 | 
						|
		ereport(ERROR,
 | 
						|
				(errmsg("could not find a valid record after %X/%X",
 | 
						|
						LSN_FORMAT_ARGS(lsn))));
 | 
						|
 | 
						|
	return xlogreader;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Read next WAL record.
 | 
						|
 *
 | 
						|
 * By design, to be less intrusive in a running system, no slot is allocated
 | 
						|
 * to reserve the WAL we're about to read. Therefore this function can
 | 
						|
 * encounter read errors for historical WAL.
 | 
						|
 *
 | 
						|
 * We guard against ordinary errors trying to read WAL that hasn't been
 | 
						|
 * written yet by limiting end_lsn to the flushed WAL, but that can also
 | 
						|
 * encounter errors if the flush pointer falls in the middle of a record. In
 | 
						|
 * that case we'll return NULL.
 | 
						|
 */
 | 
						|
static XLogRecord *
 | 
						|
ReadNextXLogRecord(XLogReaderState *xlogreader)
 | 
						|
{
 | 
						|
	XLogRecord *record;
 | 
						|
	char	   *errormsg;
 | 
						|
 | 
						|
	record = XLogReadRecord(xlogreader, &errormsg);
 | 
						|
 | 
						|
	if (record == NULL)
 | 
						|
	{
 | 
						|
		ReadLocalXLogPageNoWaitPrivate *private_data;
 | 
						|
 | 
						|
		/* return NULL, if end of WAL is reached */
 | 
						|
		private_data = (ReadLocalXLogPageNoWaitPrivate *)
 | 
						|
			xlogreader->private_data;
 | 
						|
 | 
						|
		if (private_data->end_of_wal)
 | 
						|
			return NULL;
 | 
						|
 | 
						|
		if (errormsg)
 | 
						|
			ereport(ERROR,
 | 
						|
					(errcode_for_file_access(),
 | 
						|
					 errmsg("could not read WAL at %X/%X: %s",
 | 
						|
							LSN_FORMAT_ARGS(xlogreader->EndRecPtr), errormsg)));
 | 
						|
		else
 | 
						|
			ereport(ERROR,
 | 
						|
					(errcode_for_file_access(),
 | 
						|
					 errmsg("could not read WAL at %X/%X",
 | 
						|
							LSN_FORMAT_ARGS(xlogreader->EndRecPtr))));
 | 
						|
	}
 | 
						|
 | 
						|
	return record;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Output values that make up a row describing caller's WAL record.
 | 
						|
 *
 | 
						|
 * This function leaks memory.  Caller may need to use its own custom memory
 | 
						|
 * context.
 | 
						|
 *
 | 
						|
 * Keep this in sync with GetWALBlockInfo.
 | 
						|
 */
 | 
						|
static void
 | 
						|
GetWALRecordInfo(XLogReaderState *record, Datum *values,
 | 
						|
				 bool *nulls, uint32 ncols)
 | 
						|
{
 | 
						|
	const char *record_type;
 | 
						|
	RmgrData	desc;
 | 
						|
	uint32		fpi_len = 0;
 | 
						|
	StringInfoData rec_desc;
 | 
						|
	StringInfoData rec_blk_ref;
 | 
						|
	int			i = 0;
 | 
						|
 | 
						|
	desc = GetRmgr(XLogRecGetRmid(record));
 | 
						|
	record_type = desc.rm_identify(XLogRecGetInfo(record));
 | 
						|
 | 
						|
	if (record_type == NULL)
 | 
						|
		record_type = psprintf("UNKNOWN (%x)", XLogRecGetInfo(record) & ~XLR_INFO_MASK);
 | 
						|
 | 
						|
	initStringInfo(&rec_desc);
 | 
						|
	desc.rm_desc(&rec_desc, record);
 | 
						|
 | 
						|
	if (XLogRecHasAnyBlockRefs(record))
 | 
						|
	{
 | 
						|
		initStringInfo(&rec_blk_ref);
 | 
						|
		XLogRecGetBlockRefInfo(record, false, true, &rec_blk_ref, &fpi_len);
 | 
						|
	}
 | 
						|
 | 
						|
	values[i++] = LSNGetDatum(record->ReadRecPtr);
 | 
						|
	values[i++] = LSNGetDatum(record->EndRecPtr);
 | 
						|
	values[i++] = LSNGetDatum(XLogRecGetPrev(record));
 | 
						|
	values[i++] = TransactionIdGetDatum(XLogRecGetXid(record));
 | 
						|
	values[i++] = CStringGetTextDatum(desc.rm_name);
 | 
						|
	values[i++] = CStringGetTextDatum(record_type);
 | 
						|
	values[i++] = UInt32GetDatum(XLogRecGetTotalLen(record));
 | 
						|
	values[i++] = UInt32GetDatum(XLogRecGetDataLen(record));
 | 
						|
	values[i++] = UInt32GetDatum(fpi_len);
 | 
						|
 | 
						|
	if (rec_desc.len > 0)
 | 
						|
		values[i++] = CStringGetTextDatum(rec_desc.data);
 | 
						|
	else
 | 
						|
		nulls[i++] = true;
 | 
						|
 | 
						|
	if (XLogRecHasAnyBlockRefs(record))
 | 
						|
		values[i++] = CStringGetTextDatum(rec_blk_ref.data);
 | 
						|
	else
 | 
						|
		nulls[i++] = true;
 | 
						|
 | 
						|
	Assert(i == ncols);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
 * Output one or more rows in rsinfo tuple store, each describing a single
 | 
						|
 * block reference from caller's WAL record. (Should only be called with
 | 
						|
 * records that have block references.)
 | 
						|
 *
 | 
						|
 * This function leaks memory.  Caller may need to use its own custom memory
 | 
						|
 * context.
 | 
						|
 *
 | 
						|
 * Keep this in sync with GetWALRecordInfo.
 | 
						|
 */
 | 
						|
static void
 | 
						|
GetWALBlockInfo(FunctionCallInfo fcinfo, XLogReaderState *record,
 | 
						|
				bool show_data)
 | 
						|
{
 | 
						|
#define PG_GET_WAL_BLOCK_INFO_COLS 20
 | 
						|
	int			block_id;
 | 
						|
	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 | 
						|
	RmgrData	desc;
 | 
						|
	const char *record_type;
 | 
						|
	StringInfoData rec_desc;
 | 
						|
 | 
						|
	Assert(XLogRecHasAnyBlockRefs(record));
 | 
						|
 | 
						|
	desc = GetRmgr(XLogRecGetRmid(record));
 | 
						|
	record_type = desc.rm_identify(XLogRecGetInfo(record));
 | 
						|
 | 
						|
	if (record_type == NULL)
 | 
						|
		record_type = psprintf("UNKNOWN (%x)",
 | 
						|
							   XLogRecGetInfo(record) & ~XLR_INFO_MASK);
 | 
						|
 | 
						|
	initStringInfo(&rec_desc);
 | 
						|
	desc.rm_desc(&rec_desc, record);
 | 
						|
 | 
						|
	for (block_id = 0; block_id <= XLogRecMaxBlockId(record); block_id++)
 | 
						|
	{
 | 
						|
		DecodedBkpBlock *blk;
 | 
						|
		BlockNumber blkno;
 | 
						|
		RelFileLocator rnode;
 | 
						|
		ForkNumber	forknum;
 | 
						|
		Datum		values[PG_GET_WAL_BLOCK_INFO_COLS] = {0};
 | 
						|
		bool		nulls[PG_GET_WAL_BLOCK_INFO_COLS] = {0};
 | 
						|
		uint32		block_data_len = 0,
 | 
						|
					block_fpi_len = 0;
 | 
						|
		ArrayType  *block_fpi_info = NULL;
 | 
						|
		int			i = 0;
 | 
						|
 | 
						|
		if (!XLogRecHasBlockRef(record, block_id))
 | 
						|
			continue;
 | 
						|
 | 
						|
		blk = XLogRecGetBlock(record, block_id);
 | 
						|
 | 
						|
		(void) XLogRecGetBlockTagExtended(record, block_id,
 | 
						|
										  &rnode, &forknum, &blkno, NULL);
 | 
						|
 | 
						|
		/* Save block_data_len */
 | 
						|
		if (blk->has_data)
 | 
						|
			block_data_len = blk->data_len;
 | 
						|
 | 
						|
		if (blk->has_image)
 | 
						|
		{
 | 
						|
			/* Block reference has an FPI, so prepare relevant output */
 | 
						|
			int			bitcnt;
 | 
						|
			int			cnt = 0;
 | 
						|
			Datum	   *flags;
 | 
						|
 | 
						|
			/* Save block_fpi_len */
 | 
						|
			block_fpi_len = blk->bimg_len;
 | 
						|
 | 
						|
			/* Construct and save block_fpi_info */
 | 
						|
			bitcnt = pg_popcount((const char *) &blk->bimg_info,
 | 
						|
								 sizeof(uint8));
 | 
						|
			flags = (Datum *) palloc0(sizeof(Datum) * bitcnt);
 | 
						|
			if ((blk->bimg_info & BKPIMAGE_HAS_HOLE) != 0)
 | 
						|
				flags[cnt++] = CStringGetTextDatum("HAS_HOLE");
 | 
						|
			if (blk->apply_image)
 | 
						|
				flags[cnt++] = CStringGetTextDatum("APPLY");
 | 
						|
			if ((blk->bimg_info & BKPIMAGE_COMPRESS_PGLZ) != 0)
 | 
						|
				flags[cnt++] = CStringGetTextDatum("COMPRESS_PGLZ");
 | 
						|
			if ((blk->bimg_info & BKPIMAGE_COMPRESS_LZ4) != 0)
 | 
						|
				flags[cnt++] = CStringGetTextDatum("COMPRESS_LZ4");
 | 
						|
			if ((blk->bimg_info & BKPIMAGE_COMPRESS_ZSTD) != 0)
 | 
						|
				flags[cnt++] = CStringGetTextDatum("COMPRESS_ZSTD");
 | 
						|
 | 
						|
			Assert(cnt <= bitcnt);
 | 
						|
			block_fpi_info = construct_array_builtin(flags, cnt, TEXTOID);
 | 
						|
		}
 | 
						|
 | 
						|
		/* start_lsn, end_lsn, prev_lsn, and blockid outputs */
 | 
						|
		values[i++] = LSNGetDatum(record->ReadRecPtr);
 | 
						|
		values[i++] = LSNGetDatum(record->EndRecPtr);
 | 
						|
		values[i++] = LSNGetDatum(XLogRecGetPrev(record));
 | 
						|
		values[i++] = Int16GetDatum(block_id);
 | 
						|
 | 
						|
		/* relfile and block related outputs */
 | 
						|
		values[i++] = ObjectIdGetDatum(blk->rlocator.spcOid);
 | 
						|
		values[i++] = ObjectIdGetDatum(blk->rlocator.dbOid);
 | 
						|
		values[i++] = ObjectIdGetDatum(blk->rlocator.relNumber);
 | 
						|
		values[i++] = Int16GetDatum(forknum);
 | 
						|
		values[i++] = Int64GetDatum((int64) blkno);
 | 
						|
 | 
						|
		/* xid, resource_manager, and record_type outputs */
 | 
						|
		values[i++] = TransactionIdGetDatum(XLogRecGetXid(record));
 | 
						|
		values[i++] = CStringGetTextDatum(desc.rm_name);
 | 
						|
		values[i++] = CStringGetTextDatum(record_type);
 | 
						|
 | 
						|
		/*
 | 
						|
		 * record_length, main_data_length, block_data_len, and
 | 
						|
		 * block_fpi_length outputs
 | 
						|
		 */
 | 
						|
		values[i++] = UInt32GetDatum(XLogRecGetTotalLen(record));
 | 
						|
		values[i++] = UInt32GetDatum(XLogRecGetDataLen(record));
 | 
						|
		values[i++] = UInt32GetDatum(block_data_len);
 | 
						|
		values[i++] = UInt32GetDatum(block_fpi_len);
 | 
						|
 | 
						|
		/* block_fpi_info (text array) output */
 | 
						|
		if (block_fpi_info)
 | 
						|
			values[i++] = PointerGetDatum(block_fpi_info);
 | 
						|
		else
 | 
						|
			nulls[i++] = true;
 | 
						|
 | 
						|
		/* description output (describes WAL record) */
 | 
						|
		if (rec_desc.len > 0)
 | 
						|
			values[i++] = CStringGetTextDatum(rec_desc.data);
 | 
						|
		else
 | 
						|
			nulls[i++] = true;
 | 
						|
 | 
						|
		/* block_data output */
 | 
						|
		if (blk->has_data && show_data)
 | 
						|
		{
 | 
						|
			bytea	   *block_data;
 | 
						|
 | 
						|
			block_data = (bytea *) palloc(block_data_len + VARHDRSZ);
 | 
						|
			SET_VARSIZE(block_data, block_data_len + VARHDRSZ);
 | 
						|
			memcpy(VARDATA(block_data), blk->data, block_data_len);
 | 
						|
			values[i++] = PointerGetDatum(block_data);
 | 
						|
		}
 | 
						|
		else
 | 
						|
			nulls[i++] = true;
 | 
						|
 | 
						|
		/* block_fpi_data output */
 | 
						|
		if (blk->has_image && show_data)
 | 
						|
		{
 | 
						|
			PGAlignedBlock buf;
 | 
						|
			Page		page;
 | 
						|
			bytea	   *block_fpi_data;
 | 
						|
 | 
						|
			page = (Page) buf.data;
 | 
						|
			if (!RestoreBlockImage(record, block_id, page))
 | 
						|
				ereport(ERROR,
 | 
						|
						(errcode(ERRCODE_INTERNAL_ERROR),
 | 
						|
						 errmsg_internal("%s", record->errormsg_buf)));
 | 
						|
 | 
						|
			block_fpi_data = (bytea *) palloc(BLCKSZ + VARHDRSZ);
 | 
						|
			SET_VARSIZE(block_fpi_data, BLCKSZ + VARHDRSZ);
 | 
						|
			memcpy(VARDATA(block_fpi_data), page, BLCKSZ);
 | 
						|
			values[i++] = PointerGetDatum(block_fpi_data);
 | 
						|
		}
 | 
						|
		else
 | 
						|
			nulls[i++] = true;
 | 
						|
 | 
						|
		Assert(i == PG_GET_WAL_BLOCK_INFO_COLS);
 | 
						|
 | 
						|
		/* Store a tuple for this block reference */
 | 
						|
		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
 | 
						|
							 values, nulls);
 | 
						|
	}
 | 
						|
 | 
						|
#undef PG_GET_WAL_BLOCK_INFO_COLS
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Get WAL record info, unnested by block reference
 | 
						|
 */
 | 
						|
Datum
 | 
						|
pg_get_wal_block_info(PG_FUNCTION_ARGS)
 | 
						|
{
 | 
						|
	XLogRecPtr	start_lsn = PG_GETARG_LSN(0);
 | 
						|
	XLogRecPtr	end_lsn = PG_GETARG_LSN(1);
 | 
						|
	bool		show_data = PG_GETARG_BOOL(2);
 | 
						|
	XLogReaderState *xlogreader;
 | 
						|
	MemoryContext old_cxt;
 | 
						|
	MemoryContext tmp_cxt;
 | 
						|
 | 
						|
	ValidateInputLSNs(start_lsn, &end_lsn);
 | 
						|
 | 
						|
	InitMaterializedSRF(fcinfo, 0);
 | 
						|
 | 
						|
	xlogreader = InitXLogReaderState(start_lsn);
 | 
						|
 | 
						|
	tmp_cxt = AllocSetContextCreate(CurrentMemoryContext,
 | 
						|
									"pg_get_wal_block_info temporary cxt",
 | 
						|
									ALLOCSET_DEFAULT_SIZES);
 | 
						|
 | 
						|
	while (ReadNextXLogRecord(xlogreader) &&
 | 
						|
		   xlogreader->EndRecPtr <= end_lsn)
 | 
						|
	{
 | 
						|
		CHECK_FOR_INTERRUPTS();
 | 
						|
 | 
						|
		if (!XLogRecHasAnyBlockRefs(xlogreader))
 | 
						|
			continue;
 | 
						|
 | 
						|
		/* Use the tmp context so we can clean up after each tuple is done */
 | 
						|
		old_cxt = MemoryContextSwitchTo(tmp_cxt);
 | 
						|
 | 
						|
		GetWALBlockInfo(fcinfo, xlogreader, show_data);
 | 
						|
 | 
						|
		/* clean up and switch back */
 | 
						|
		MemoryContextSwitchTo(old_cxt);
 | 
						|
		MemoryContextReset(tmp_cxt);
 | 
						|
	}
 | 
						|
 | 
						|
	MemoryContextDelete(tmp_cxt);
 | 
						|
	pfree(xlogreader->private_data);
 | 
						|
	XLogReaderFree(xlogreader);
 | 
						|
 | 
						|
	PG_RETURN_VOID();
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Get WAL record info.
 | 
						|
 */
 | 
						|
Datum
 | 
						|
pg_get_wal_record_info(PG_FUNCTION_ARGS)
 | 
						|
{
 | 
						|
#define PG_GET_WAL_RECORD_INFO_COLS 11
 | 
						|
	Datum		result;
 | 
						|
	Datum		values[PG_GET_WAL_RECORD_INFO_COLS] = {0};
 | 
						|
	bool		nulls[PG_GET_WAL_RECORD_INFO_COLS] = {0};
 | 
						|
	XLogRecPtr	lsn;
 | 
						|
	XLogRecPtr	curr_lsn;
 | 
						|
	XLogReaderState *xlogreader;
 | 
						|
	TupleDesc	tupdesc;
 | 
						|
	HeapTuple	tuple;
 | 
						|
 | 
						|
	lsn = PG_GETARG_LSN(0);
 | 
						|
	curr_lsn = GetCurrentLSN();
 | 
						|
 | 
						|
	if (lsn > curr_lsn)
 | 
						|
		ereport(ERROR,
 | 
						|
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 | 
						|
				 errmsg("WAL input LSN must be less than current LSN"),
 | 
						|
				 errdetail("Current WAL LSN on the database system is at %X/%X.",
 | 
						|
						   LSN_FORMAT_ARGS(curr_lsn))));
 | 
						|
 | 
						|
	/* Build a tuple descriptor for our result type. */
 | 
						|
	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
 | 
						|
		elog(ERROR, "return type must be a row type");
 | 
						|
 | 
						|
	xlogreader = InitXLogReaderState(lsn);
 | 
						|
 | 
						|
	if (!ReadNextXLogRecord(xlogreader))
 | 
						|
		ereport(ERROR,
 | 
						|
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 | 
						|
				 errmsg("could not read WAL at %X/%X",
 | 
						|
						LSN_FORMAT_ARGS(xlogreader->EndRecPtr))));
 | 
						|
 | 
						|
	GetWALRecordInfo(xlogreader, values, nulls, PG_GET_WAL_RECORD_INFO_COLS);
 | 
						|
 | 
						|
	pfree(xlogreader->private_data);
 | 
						|
	XLogReaderFree(xlogreader);
 | 
						|
 | 
						|
	tuple = heap_form_tuple(tupdesc, values, nulls);
 | 
						|
	result = HeapTupleGetDatum(tuple);
 | 
						|
 | 
						|
	PG_RETURN_DATUM(result);
 | 
						|
#undef PG_GET_WAL_RECORD_INFO_COLS
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Validate start and end LSNs coming from the function inputs.
 | 
						|
 *
 | 
						|
 * If end_lsn is found to be higher than the current LSN reported by the
 | 
						|
 * cluster, use the current LSN as the upper bound.
 | 
						|
 */
 | 
						|
static void
 | 
						|
ValidateInputLSNs(XLogRecPtr start_lsn, XLogRecPtr *end_lsn)
 | 
						|
{
 | 
						|
	XLogRecPtr	curr_lsn = GetCurrentLSN();
 | 
						|
 | 
						|
	if (start_lsn > curr_lsn)
 | 
						|
		ereport(ERROR,
 | 
						|
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 | 
						|
				 errmsg("WAL start LSN must be less than current LSN"),
 | 
						|
				 errdetail("Current WAL LSN on the database system is at %X/%X.",
 | 
						|
						   LSN_FORMAT_ARGS(curr_lsn))));
 | 
						|
 | 
						|
	if (start_lsn > *end_lsn)
 | 
						|
		ereport(ERROR,
 | 
						|
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 | 
						|
				 errmsg("WAL start LSN must be less than end LSN")));
 | 
						|
 | 
						|
	if (*end_lsn > curr_lsn)
 | 
						|
		*end_lsn = curr_lsn;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Get info of all WAL records between start LSN and end LSN.
 | 
						|
 */
 | 
						|
static void
 | 
						|
GetWALRecordsInfo(FunctionCallInfo fcinfo, XLogRecPtr start_lsn,
 | 
						|
				  XLogRecPtr end_lsn)
 | 
						|
{
 | 
						|
#define PG_GET_WAL_RECORDS_INFO_COLS 11
 | 
						|
	XLogReaderState *xlogreader;
 | 
						|
	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 | 
						|
	MemoryContext old_cxt;
 | 
						|
	MemoryContext tmp_cxt;
 | 
						|
 | 
						|
	Assert(start_lsn <= end_lsn);
 | 
						|
 | 
						|
	InitMaterializedSRF(fcinfo, 0);
 | 
						|
 | 
						|
	xlogreader = InitXLogReaderState(start_lsn);
 | 
						|
 | 
						|
	tmp_cxt = AllocSetContextCreate(CurrentMemoryContext,
 | 
						|
									"GetWALRecordsInfo temporary cxt",
 | 
						|
									ALLOCSET_DEFAULT_SIZES);
 | 
						|
 | 
						|
	while (ReadNextXLogRecord(xlogreader) &&
 | 
						|
		   xlogreader->EndRecPtr <= end_lsn)
 | 
						|
	{
 | 
						|
		Datum		values[PG_GET_WAL_RECORDS_INFO_COLS] = {0};
 | 
						|
		bool		nulls[PG_GET_WAL_RECORDS_INFO_COLS] = {0};
 | 
						|
 | 
						|
		/* Use the tmp context so we can clean up after each tuple is done */
 | 
						|
		old_cxt = MemoryContextSwitchTo(tmp_cxt);
 | 
						|
 | 
						|
		GetWALRecordInfo(xlogreader, values, nulls,
 | 
						|
						 PG_GET_WAL_RECORDS_INFO_COLS);
 | 
						|
 | 
						|
		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
 | 
						|
							 values, nulls);
 | 
						|
 | 
						|
		/* clean up and switch back */
 | 
						|
		MemoryContextSwitchTo(old_cxt);
 | 
						|
		MemoryContextReset(tmp_cxt);
 | 
						|
 | 
						|
		CHECK_FOR_INTERRUPTS();
 | 
						|
	}
 | 
						|
 | 
						|
	MemoryContextDelete(tmp_cxt);
 | 
						|
	pfree(xlogreader->private_data);
 | 
						|
	XLogReaderFree(xlogreader);
 | 
						|
 | 
						|
#undef PG_GET_WAL_RECORDS_INFO_COLS
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Get info of all WAL records between start LSN and end LSN.
 | 
						|
 */
 | 
						|
Datum
 | 
						|
pg_get_wal_records_info(PG_FUNCTION_ARGS)
 | 
						|
{
 | 
						|
	XLogRecPtr	start_lsn = PG_GETARG_LSN(0);
 | 
						|
	XLogRecPtr	end_lsn = PG_GETARG_LSN(1);
 | 
						|
 | 
						|
	ValidateInputLSNs(start_lsn, &end_lsn);
 | 
						|
	GetWALRecordsInfo(fcinfo, start_lsn, end_lsn);
 | 
						|
 | 
						|
	PG_RETURN_VOID();
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Fill single row of record counts and sizes for an rmgr or record.
 | 
						|
 */
 | 
						|
static void
 | 
						|
FillXLogStatsRow(const char *name,
 | 
						|
				 uint64 n, uint64 total_count,
 | 
						|
				 uint64 rec_len, uint64 total_rec_len,
 | 
						|
				 uint64 fpi_len, uint64 total_fpi_len,
 | 
						|
				 uint64 tot_len, uint64 total_len,
 | 
						|
				 Datum *values, bool *nulls, uint32 ncols)
 | 
						|
{
 | 
						|
	double		n_pct,
 | 
						|
				rec_len_pct,
 | 
						|
				fpi_len_pct,
 | 
						|
				tot_len_pct;
 | 
						|
	int			i = 0;
 | 
						|
 | 
						|
	n_pct = 0;
 | 
						|
	if (total_count != 0)
 | 
						|
		n_pct = 100 * (double) n / total_count;
 | 
						|
 | 
						|
	rec_len_pct = 0;
 | 
						|
	if (total_rec_len != 0)
 | 
						|
		rec_len_pct = 100 * (double) rec_len / total_rec_len;
 | 
						|
 | 
						|
	fpi_len_pct = 0;
 | 
						|
	if (total_fpi_len != 0)
 | 
						|
		fpi_len_pct = 100 * (double) fpi_len / total_fpi_len;
 | 
						|
 | 
						|
	tot_len_pct = 0;
 | 
						|
	if (total_len != 0)
 | 
						|
		tot_len_pct = 100 * (double) tot_len / total_len;
 | 
						|
 | 
						|
	values[i++] = CStringGetTextDatum(name);
 | 
						|
	values[i++] = Int64GetDatum(n);
 | 
						|
	values[i++] = Float8GetDatum(n_pct);
 | 
						|
	values[i++] = Int64GetDatum(rec_len);
 | 
						|
	values[i++] = Float8GetDatum(rec_len_pct);
 | 
						|
	values[i++] = Int64GetDatum(fpi_len);
 | 
						|
	values[i++] = Float8GetDatum(fpi_len_pct);
 | 
						|
	values[i++] = Int64GetDatum(tot_len);
 | 
						|
	values[i++] = Float8GetDatum(tot_len_pct);
 | 
						|
 | 
						|
	Assert(i == ncols);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Get summary statistics about the records seen so far.
 | 
						|
 */
 | 
						|
static void
 | 
						|
GetXLogSummaryStats(XLogStats *stats, ReturnSetInfo *rsinfo,
 | 
						|
					Datum *values, bool *nulls, uint32 ncols,
 | 
						|
					bool stats_per_record)
 | 
						|
{
 | 
						|
	MemoryContext old_cxt;
 | 
						|
	MemoryContext tmp_cxt;
 | 
						|
	uint64		total_count = 0;
 | 
						|
	uint64		total_rec_len = 0;
 | 
						|
	uint64		total_fpi_len = 0;
 | 
						|
	uint64		total_len = 0;
 | 
						|
	int			ri;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Each row shows its percentages of the total, so make a first pass to
 | 
						|
	 * calculate column totals.
 | 
						|
	 */
 | 
						|
	for (ri = 0; ri <= RM_MAX_ID; ri++)
 | 
						|
	{
 | 
						|
		if (!RmgrIdIsValid(ri))
 | 
						|
			continue;
 | 
						|
 | 
						|
		total_count += stats->rmgr_stats[ri].count;
 | 
						|
		total_rec_len += stats->rmgr_stats[ri].rec_len;
 | 
						|
		total_fpi_len += stats->rmgr_stats[ri].fpi_len;
 | 
						|
	}
 | 
						|
	total_len = total_rec_len + total_fpi_len;
 | 
						|
 | 
						|
	tmp_cxt = AllocSetContextCreate(CurrentMemoryContext,
 | 
						|
									"GetXLogSummaryStats temporary cxt",
 | 
						|
									ALLOCSET_DEFAULT_SIZES);
 | 
						|
 | 
						|
	for (ri = 0; ri <= RM_MAX_ID; ri++)
 | 
						|
	{
 | 
						|
		uint64		count;
 | 
						|
		uint64		rec_len;
 | 
						|
		uint64		fpi_len;
 | 
						|
		uint64		tot_len;
 | 
						|
		RmgrData	desc;
 | 
						|
 | 
						|
		if (!RmgrIdIsValid(ri))
 | 
						|
			continue;
 | 
						|
 | 
						|
		if (!RmgrIdExists(ri))
 | 
						|
			continue;
 | 
						|
 | 
						|
		desc = GetRmgr(ri);
 | 
						|
 | 
						|
		if (stats_per_record)
 | 
						|
		{
 | 
						|
			int			rj;
 | 
						|
 | 
						|
			for (rj = 0; rj < MAX_XLINFO_TYPES; rj++)
 | 
						|
			{
 | 
						|
				const char *id;
 | 
						|
 | 
						|
				count = stats->record_stats[ri][rj].count;
 | 
						|
				rec_len = stats->record_stats[ri][rj].rec_len;
 | 
						|
				fpi_len = stats->record_stats[ri][rj].fpi_len;
 | 
						|
				tot_len = rec_len + fpi_len;
 | 
						|
 | 
						|
				/* Skip undefined combinations and ones that didn't occur */
 | 
						|
				if (count == 0)
 | 
						|
					continue;
 | 
						|
 | 
						|
				old_cxt = MemoryContextSwitchTo(tmp_cxt);
 | 
						|
 | 
						|
				/* the upper four bits in xl_info are the rmgr's */
 | 
						|
				id = desc.rm_identify(rj << 4);
 | 
						|
				if (id == NULL)
 | 
						|
					id = psprintf("UNKNOWN (%x)", rj << 4);
 | 
						|
 | 
						|
				FillXLogStatsRow(psprintf("%s/%s", desc.rm_name, id), count,
 | 
						|
								 total_count, rec_len, total_rec_len, fpi_len,
 | 
						|
								 total_fpi_len, tot_len, total_len,
 | 
						|
								 values, nulls, ncols);
 | 
						|
 | 
						|
				tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
 | 
						|
									 values, nulls);
 | 
						|
 | 
						|
				/* clean up and switch back */
 | 
						|
				MemoryContextSwitchTo(old_cxt);
 | 
						|
				MemoryContextReset(tmp_cxt);
 | 
						|
			}
 | 
						|
		}
 | 
						|
		else
 | 
						|
		{
 | 
						|
			count = stats->rmgr_stats[ri].count;
 | 
						|
			rec_len = stats->rmgr_stats[ri].rec_len;
 | 
						|
			fpi_len = stats->rmgr_stats[ri].fpi_len;
 | 
						|
			tot_len = rec_len + fpi_len;
 | 
						|
 | 
						|
			old_cxt = MemoryContextSwitchTo(tmp_cxt);
 | 
						|
 | 
						|
			FillXLogStatsRow(desc.rm_name, count, total_count, rec_len,
 | 
						|
							 total_rec_len, fpi_len, total_fpi_len, tot_len,
 | 
						|
							 total_len, values, nulls, ncols);
 | 
						|
 | 
						|
			tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
 | 
						|
								 values, nulls);
 | 
						|
 | 
						|
			/* clean up and switch back */
 | 
						|
			MemoryContextSwitchTo(old_cxt);
 | 
						|
			MemoryContextReset(tmp_cxt);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	MemoryContextDelete(tmp_cxt);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Get WAL stats between start LSN and end LSN.
 | 
						|
 */
 | 
						|
static void
 | 
						|
GetWalStats(FunctionCallInfo fcinfo, XLogRecPtr start_lsn, XLogRecPtr end_lsn,
 | 
						|
			bool stats_per_record)
 | 
						|
{
 | 
						|
#define PG_GET_WAL_STATS_COLS 9
 | 
						|
	XLogReaderState *xlogreader;
 | 
						|
	XLogStats	stats = {0};
 | 
						|
	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 | 
						|
	Datum		values[PG_GET_WAL_STATS_COLS] = {0};
 | 
						|
	bool		nulls[PG_GET_WAL_STATS_COLS] = {0};
 | 
						|
 | 
						|
	Assert(start_lsn <= end_lsn);
 | 
						|
 | 
						|
	InitMaterializedSRF(fcinfo, 0);
 | 
						|
 | 
						|
	xlogreader = InitXLogReaderState(start_lsn);
 | 
						|
 | 
						|
	while (ReadNextXLogRecord(xlogreader) &&
 | 
						|
		   xlogreader->EndRecPtr <= end_lsn)
 | 
						|
	{
 | 
						|
		XLogRecStoreStats(&stats, xlogreader);
 | 
						|
 | 
						|
		CHECK_FOR_INTERRUPTS();
 | 
						|
	}
 | 
						|
 | 
						|
	pfree(xlogreader->private_data);
 | 
						|
	XLogReaderFree(xlogreader);
 | 
						|
 | 
						|
	GetXLogSummaryStats(&stats, rsinfo, values, nulls,
 | 
						|
						PG_GET_WAL_STATS_COLS,
 | 
						|
						stats_per_record);
 | 
						|
 | 
						|
#undef PG_GET_WAL_STATS_COLS
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Get stats of all WAL records between start LSN and end LSN.
 | 
						|
 */
 | 
						|
Datum
 | 
						|
pg_get_wal_stats(PG_FUNCTION_ARGS)
 | 
						|
{
 | 
						|
	XLogRecPtr	start_lsn = PG_GETARG_LSN(0);
 | 
						|
	XLogRecPtr	end_lsn = PG_GETARG_LSN(1);
 | 
						|
	bool		stats_per_record = PG_GETARG_BOOL(2);
 | 
						|
 | 
						|
	ValidateInputLSNs(start_lsn, &end_lsn);
 | 
						|
	GetWalStats(fcinfo, start_lsn, end_lsn, stats_per_record);
 | 
						|
 | 
						|
	PG_RETURN_VOID();
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * The following functions have been removed in newer versions in 1.1, but
 | 
						|
 * they are kept around for compatibility.
 | 
						|
 */
 | 
						|
Datum
 | 
						|
pg_get_wal_records_info_till_end_of_wal(PG_FUNCTION_ARGS)
 | 
						|
{
 | 
						|
	XLogRecPtr	start_lsn = PG_GETARG_LSN(0);
 | 
						|
	XLogRecPtr	end_lsn = GetCurrentLSN();
 | 
						|
 | 
						|
	if (start_lsn > end_lsn)
 | 
						|
		ereport(ERROR,
 | 
						|
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 | 
						|
				 errmsg("WAL start LSN must be less than current LSN"),
 | 
						|
				 errdetail("Current WAL LSN on the database system is at %X/%X.",
 | 
						|
						   LSN_FORMAT_ARGS(end_lsn))));
 | 
						|
 | 
						|
	GetWALRecordsInfo(fcinfo, start_lsn, end_lsn);
 | 
						|
 | 
						|
	PG_RETURN_VOID();
 | 
						|
}
 | 
						|
 | 
						|
Datum
 | 
						|
pg_get_wal_stats_till_end_of_wal(PG_FUNCTION_ARGS)
 | 
						|
{
 | 
						|
	XLogRecPtr	start_lsn = PG_GETARG_LSN(0);
 | 
						|
	XLogRecPtr	end_lsn = GetCurrentLSN();
 | 
						|
	bool		stats_per_record = PG_GETARG_BOOL(1);
 | 
						|
 | 
						|
	if (start_lsn > end_lsn)
 | 
						|
		ereport(ERROR,
 | 
						|
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 | 
						|
				 errmsg("WAL start LSN must be less than current LSN"),
 | 
						|
				 errdetail("Current WAL LSN on the database system is at %X/%X.",
 | 
						|
						   LSN_FORMAT_ARGS(end_lsn))));
 | 
						|
 | 
						|
	GetWalStats(fcinfo, start_lsn, end_lsn, stats_per_record);
 | 
						|
 | 
						|
	PG_RETURN_VOID();
 | 
						|
}
 |