postgresql/contrib/pg_walinspect/pg_walinspect.c

852 lines
23 KiB
C

/*-------------------------------------------------------------------------
*
* pg_walinspect.c
* Functions to inspect contents of PostgreSQL Write-Ahead Log
*
* Copyright (c) 2022-2023, 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;
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,
(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();
}