/*------------------------------------------------------------------------- * * 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(); }