LCOV - code coverage report
Current view: top level - contrib/pg_walinspect - pg_walinspect.c (source / functions) Hit Total Coverage
Test: PostgreSQL 19devel Lines: 289 321 90.0 %
Date: 2025-10-10 19:18:08 Functions: 23 23 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * pg_walinspect.c
       4             :  *        Functions to inspect contents of PostgreSQL Write-Ahead Log
       5             :  *
       6             :  * Copyright (c) 2022-2025, PostgreSQL Global Development Group
       7             :  *
       8             :  * IDENTIFICATION
       9             :  *        contrib/pg_walinspect/pg_walinspect.c
      10             :  *
      11             :  *-------------------------------------------------------------------------
      12             :  */
      13             : #include "postgres.h"
      14             : 
      15             : #include "access/htup_details.h"
      16             : #include "access/xlog.h"
      17             : #include "access/xlog_internal.h"
      18             : #include "access/xlogreader.h"
      19             : #include "access/xlogrecovery.h"
      20             : #include "access/xlogstats.h"
      21             : #include "access/xlogutils.h"
      22             : #include "funcapi.h"
      23             : #include "miscadmin.h"
      24             : #include "utils/array.h"
      25             : #include "utils/builtins.h"
      26             : #include "utils/pg_lsn.h"
      27             : 
      28             : /*
      29             :  * NOTE: For any code change or issue fix here, it is highly recommended to
      30             :  * give a thought about doing the same in pg_waldump tool as well.
      31             :  */
      32             : 
      33          16 : PG_MODULE_MAGIC_EXT(
      34             :                     .name = "pg_walinspect",
      35             :                     .version = PG_VERSION
      36             : );
      37             : 
      38          12 : PG_FUNCTION_INFO_V1(pg_get_wal_block_info);
      39          12 : PG_FUNCTION_INFO_V1(pg_get_wal_record_info);
      40          18 : PG_FUNCTION_INFO_V1(pg_get_wal_records_info);
      41          12 : PG_FUNCTION_INFO_V1(pg_get_wal_records_info_till_end_of_wal);
      42          12 : PG_FUNCTION_INFO_V1(pg_get_wal_stats);
      43          12 : PG_FUNCTION_INFO_V1(pg_get_wal_stats_till_end_of_wal);
      44             : 
      45             : static void ValidateInputLSNs(XLogRecPtr start_lsn, XLogRecPtr *end_lsn);
      46             : static XLogRecPtr GetCurrentLSN(void);
      47             : static XLogReaderState *InitXLogReaderState(XLogRecPtr lsn);
      48             : static XLogRecord *ReadNextXLogRecord(XLogReaderState *xlogreader);
      49             : static void GetWALRecordInfo(XLogReaderState *record, Datum *values,
      50             :                              bool *nulls, uint32 ncols);
      51             : static void GetWALRecordsInfo(FunctionCallInfo fcinfo,
      52             :                               XLogRecPtr start_lsn,
      53             :                               XLogRecPtr end_lsn);
      54             : static void GetXLogSummaryStats(XLogStats *stats, ReturnSetInfo *rsinfo,
      55             :                                 Datum *values, bool *nulls, uint32 ncols,
      56             :                                 bool stats_per_record);
      57             : static void FillXLogStatsRow(const char *name, uint64 n, uint64 total_count,
      58             :                              uint64 rec_len, uint64 total_rec_len,
      59             :                              uint64 fpi_len, uint64 total_fpi_len,
      60             :                              uint64 tot_len, uint64 total_len,
      61             :                              Datum *values, bool *nulls, uint32 ncols);
      62             : static void GetWalStats(FunctionCallInfo fcinfo,
      63             :                         XLogRecPtr start_lsn,
      64             :                         XLogRecPtr end_lsn,
      65             :                         bool stats_per_record);
      66             : static void GetWALBlockInfo(FunctionCallInfo fcinfo, XLogReaderState *record,
      67             :                             bool show_data);
      68             : 
      69             : /*
      70             :  * Return the LSN up to which the server has WAL.
      71             :  */
      72             : static XLogRecPtr
      73          60 : GetCurrentLSN(void)
      74             : {
      75             :     XLogRecPtr  curr_lsn;
      76             : 
      77             :     /*
      78             :      * We determine the current LSN of the server similar to how page_read
      79             :      * callback read_local_xlog_page_no_wait does.
      80             :      */
      81          60 :     if (!RecoveryInProgress())
      82          60 :         curr_lsn = GetFlushRecPtr(NULL);
      83             :     else
      84           0 :         curr_lsn = GetXLogReplayRecPtr(NULL);
      85             : 
      86             :     Assert(!XLogRecPtrIsInvalid(curr_lsn));
      87             : 
      88          60 :     return curr_lsn;
      89             : }
      90             : 
      91             : /*
      92             :  * Initialize WAL reader and identify first valid LSN.
      93             :  */
      94             : static XLogReaderState *
      95          42 : InitXLogReaderState(XLogRecPtr lsn)
      96             : {
      97             :     XLogReaderState *xlogreader;
      98             :     ReadLocalXLogPageNoWaitPrivate *private_data;
      99             :     XLogRecPtr  first_valid_record;
     100             : 
     101             :     /*
     102             :      * Reading WAL below the first page of the first segments isn't allowed.
     103             :      * This is a bootstrap WAL page and the page_read callback fails to read
     104             :      * it.
     105             :      */
     106          42 :     if (lsn < XLOG_BLCKSZ)
     107           8 :         ereport(ERROR,
     108             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     109             :                  errmsg("could not read WAL at LSN %X/%08X",
     110             :                         LSN_FORMAT_ARGS(lsn))));
     111             : 
     112             :     private_data = (ReadLocalXLogPageNoWaitPrivate *)
     113          34 :         palloc0(sizeof(ReadLocalXLogPageNoWaitPrivate));
     114             : 
     115          34 :     xlogreader = XLogReaderAllocate(wal_segment_size, NULL,
     116          34 :                                     XL_ROUTINE(.page_read = &read_local_xlog_page_no_wait,
     117             :                                                .segment_open = &wal_segment_open,
     118             :                                                .segment_close = &wal_segment_close),
     119             :                                     private_data);
     120             : 
     121          34 :     if (xlogreader == NULL)
     122           0 :         ereport(ERROR,
     123             :                 (errcode(ERRCODE_OUT_OF_MEMORY),
     124             :                  errmsg("out of memory"),
     125             :                  errdetail("Failed while allocating a WAL reading processor.")));
     126             : 
     127             :     /* first find a valid recptr to start from */
     128          34 :     first_valid_record = XLogFindNextRecord(xlogreader, lsn);
     129             : 
     130          34 :     if (XLogRecPtrIsInvalid(first_valid_record))
     131           0 :         ereport(ERROR,
     132             :                 errmsg("could not find a valid record after %X/%08X",
     133             :                        LSN_FORMAT_ARGS(lsn)));
     134             : 
     135          34 :     return xlogreader;
     136             : }
     137             : 
     138             : /*
     139             :  * Read next WAL record.
     140             :  *
     141             :  * By design, to be less intrusive in a running system, no slot is allocated
     142             :  * to reserve the WAL we're about to read. Therefore this function can
     143             :  * encounter read errors for historical WAL.
     144             :  *
     145             :  * We guard against ordinary errors trying to read WAL that hasn't been
     146             :  * written yet by limiting end_lsn to the flushed WAL, but that can also
     147             :  * encounter errors if the flush pointer falls in the middle of a record. In
     148             :  * that case we'll return NULL.
     149             :  */
     150             : static XLogRecord *
     151       68830 : ReadNextXLogRecord(XLogReaderState *xlogreader)
     152             : {
     153             :     XLogRecord *record;
     154             :     char       *errormsg;
     155             : 
     156       68830 :     record = XLogReadRecord(xlogreader, &errormsg);
     157             : 
     158       68830 :     if (record == NULL)
     159             :     {
     160             :         ReadLocalXLogPageNoWaitPrivate *private_data;
     161             : 
     162             :         /* return NULL, if end of WAL is reached */
     163          20 :         private_data = (ReadLocalXLogPageNoWaitPrivate *)
     164             :             xlogreader->private_data;
     165             : 
     166          20 :         if (private_data->end_of_wal)
     167          20 :             return NULL;
     168             : 
     169           0 :         if (errormsg)
     170           0 :             ereport(ERROR,
     171             :                     (errcode_for_file_access(),
     172             :                      errmsg("could not read WAL at %X/%08X: %s",
     173             :                             LSN_FORMAT_ARGS(xlogreader->EndRecPtr), errormsg)));
     174             :         else
     175           0 :             ereport(ERROR,
     176             :                     (errcode_for_file_access(),
     177             :                      errmsg("could not read WAL at %X/%08X",
     178             :                             LSN_FORMAT_ARGS(xlogreader->EndRecPtr))));
     179             :     }
     180             : 
     181       68810 :     return record;
     182             : }
     183             : 
     184             : /*
     185             :  * Output values that make up a row describing caller's WAL record.
     186             :  *
     187             :  * This function leaks memory.  Caller may need to use its own custom memory
     188             :  * context.
     189             :  *
     190             :  * Keep this in sync with GetWALBlockInfo.
     191             :  */
     192             : static void
     193       68704 : GetWALRecordInfo(XLogReaderState *record, Datum *values,
     194             :                  bool *nulls, uint32 ncols)
     195             : {
     196             :     const char *record_type;
     197             :     RmgrData    desc;
     198       68704 :     uint32      fpi_len = 0;
     199             :     StringInfoData rec_desc;
     200             :     StringInfoData rec_blk_ref;
     201       68704 :     int         i = 0;
     202             : 
     203       68704 :     desc = GetRmgr(XLogRecGetRmid(record));
     204       68704 :     record_type = desc.rm_identify(XLogRecGetInfo(record));
     205             : 
     206       68704 :     if (record_type == NULL)
     207           0 :         record_type = psprintf("UNKNOWN (%x)", XLogRecGetInfo(record) & ~XLR_INFO_MASK);
     208             : 
     209       68704 :     initStringInfo(&rec_desc);
     210       68704 :     desc.rm_desc(&rec_desc, record);
     211             : 
     212       68704 :     if (XLogRecHasAnyBlockRefs(record))
     213             :     {
     214       68662 :         initStringInfo(&rec_blk_ref);
     215       68662 :         XLogRecGetBlockRefInfo(record, false, true, &rec_blk_ref, &fpi_len);
     216             :     }
     217             : 
     218       68704 :     values[i++] = LSNGetDatum(record->ReadRecPtr);
     219       68704 :     values[i++] = LSNGetDatum(record->EndRecPtr);
     220       68704 :     values[i++] = LSNGetDatum(XLogRecGetPrev(record));
     221       68704 :     values[i++] = TransactionIdGetDatum(XLogRecGetXid(record));
     222       68704 :     values[i++] = CStringGetTextDatum(desc.rm_name);
     223       68704 :     values[i++] = CStringGetTextDatum(record_type);
     224       68704 :     values[i++] = UInt32GetDatum(XLogRecGetTotalLen(record));
     225       68704 :     values[i++] = UInt32GetDatum(XLogRecGetDataLen(record));
     226       68704 :     values[i++] = UInt32GetDatum(fpi_len);
     227             : 
     228       68704 :     if (rec_desc.len > 0)
     229       68672 :         values[i++] = CStringGetTextDatum(rec_desc.data);
     230             :     else
     231          32 :         nulls[i++] = true;
     232             : 
     233       68704 :     if (XLogRecHasAnyBlockRefs(record))
     234       68662 :         values[i++] = CStringGetTextDatum(rec_blk_ref.data);
     235             :     else
     236          42 :         nulls[i++] = true;
     237             : 
     238             :     Assert(i == ncols);
     239       68704 : }
     240             : 
     241             : 
     242             : /*
     243             :  * Output one or more rows in rsinfo tuple store, each describing a single
     244             :  * block reference from caller's WAL record. (Should only be called with
     245             :  * records that have block references.)
     246             :  *
     247             :  * This function leaks memory.  Caller may need to use its own custom memory
     248             :  * context.
     249             :  *
     250             :  * Keep this in sync with GetWALRecordInfo.
     251             :  */
     252             : static void
     253          44 : GetWALBlockInfo(FunctionCallInfo fcinfo, XLogReaderState *record,
     254             :                 bool show_data)
     255             : {
     256             : #define PG_GET_WAL_BLOCK_INFO_COLS 20
     257             :     int         block_id;
     258          44 :     ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
     259             :     RmgrData    desc;
     260             :     const char *record_type;
     261             :     StringInfoData rec_desc;
     262             : 
     263             :     Assert(XLogRecHasAnyBlockRefs(record));
     264             : 
     265          44 :     desc = GetRmgr(XLogRecGetRmid(record));
     266          44 :     record_type = desc.rm_identify(XLogRecGetInfo(record));
     267             : 
     268          44 :     if (record_type == NULL)
     269           0 :         record_type = psprintf("UNKNOWN (%x)",
     270           0 :                                XLogRecGetInfo(record) & ~XLR_INFO_MASK);
     271             : 
     272          44 :     initStringInfo(&rec_desc);
     273          44 :     desc.rm_desc(&rec_desc, record);
     274             : 
     275          88 :     for (block_id = 0; block_id <= XLogRecMaxBlockId(record); block_id++)
     276             :     {
     277             :         DecodedBkpBlock *blk;
     278             :         BlockNumber blkno;
     279             :         RelFileLocator rnode;
     280             :         ForkNumber  forknum;
     281          44 :         Datum       values[PG_GET_WAL_BLOCK_INFO_COLS] = {0};
     282          44 :         bool        nulls[PG_GET_WAL_BLOCK_INFO_COLS] = {0};
     283          44 :         uint32      block_data_len = 0,
     284          44 :                     block_fpi_len = 0;
     285          44 :         ArrayType  *block_fpi_info = NULL;
     286          44 :         int         i = 0;
     287             : 
     288          44 :         if (!XLogRecHasBlockRef(record, block_id))
     289           0 :             continue;
     290             : 
     291          44 :         blk = XLogRecGetBlock(record, block_id);
     292             : 
     293          44 :         (void) XLogRecGetBlockTagExtended(record, block_id,
     294             :                                           &rnode, &forknum, &blkno, NULL);
     295             : 
     296             :         /* Save block_data_len */
     297          44 :         if (blk->has_data)
     298          42 :             block_data_len = blk->data_len;
     299             : 
     300          44 :         if (blk->has_image)
     301             :         {
     302             :             /* Block reference has an FPI, so prepare relevant output */
     303             :             int         bitcnt;
     304           2 :             int         cnt = 0;
     305             :             Datum      *flags;
     306             : 
     307             :             /* Save block_fpi_len */
     308           2 :             block_fpi_len = blk->bimg_len;
     309             : 
     310             :             /* Construct and save block_fpi_info */
     311           2 :             bitcnt = pg_popcount((const char *) &blk->bimg_info,
     312             :                                  sizeof(uint8));
     313           2 :             flags = (Datum *) palloc0(sizeof(Datum) * bitcnt);
     314           2 :             if ((blk->bimg_info & BKPIMAGE_HAS_HOLE) != 0)
     315           2 :                 flags[cnt++] = CStringGetTextDatum("HAS_HOLE");
     316           2 :             if (blk->apply_image)
     317           2 :                 flags[cnt++] = CStringGetTextDatum("APPLY");
     318           2 :             if ((blk->bimg_info & BKPIMAGE_COMPRESS_PGLZ) != 0)
     319           0 :                 flags[cnt++] = CStringGetTextDatum("COMPRESS_PGLZ");
     320           2 :             if ((blk->bimg_info & BKPIMAGE_COMPRESS_LZ4) != 0)
     321           0 :                 flags[cnt++] = CStringGetTextDatum("COMPRESS_LZ4");
     322           2 :             if ((blk->bimg_info & BKPIMAGE_COMPRESS_ZSTD) != 0)
     323           0 :                 flags[cnt++] = CStringGetTextDatum("COMPRESS_ZSTD");
     324             : 
     325             :             Assert(cnt <= bitcnt);
     326           2 :             block_fpi_info = construct_array_builtin(flags, cnt, TEXTOID);
     327             :         }
     328             : 
     329             :         /* start_lsn, end_lsn, prev_lsn, and blockid outputs */
     330          44 :         values[i++] = LSNGetDatum(record->ReadRecPtr);
     331          44 :         values[i++] = LSNGetDatum(record->EndRecPtr);
     332          44 :         values[i++] = LSNGetDatum(XLogRecGetPrev(record));
     333          44 :         values[i++] = Int16GetDatum(block_id);
     334             : 
     335             :         /* relfile and block related outputs */
     336          44 :         values[i++] = ObjectIdGetDatum(blk->rlocator.spcOid);
     337          44 :         values[i++] = ObjectIdGetDatum(blk->rlocator.dbOid);
     338          44 :         values[i++] = ObjectIdGetDatum(blk->rlocator.relNumber);
     339          44 :         values[i++] = Int16GetDatum(forknum);
     340          44 :         values[i++] = Int64GetDatum((int64) blkno);
     341             : 
     342             :         /* xid, resource_manager, and record_type outputs */
     343          44 :         values[i++] = TransactionIdGetDatum(XLogRecGetXid(record));
     344          44 :         values[i++] = CStringGetTextDatum(desc.rm_name);
     345          44 :         values[i++] = CStringGetTextDatum(record_type);
     346             : 
     347             :         /*
     348             :          * record_length, main_data_length, block_data_len, and
     349             :          * block_fpi_length outputs
     350             :          */
     351          44 :         values[i++] = UInt32GetDatum(XLogRecGetTotalLen(record));
     352          44 :         values[i++] = UInt32GetDatum(XLogRecGetDataLen(record));
     353          44 :         values[i++] = UInt32GetDatum(block_data_len);
     354          44 :         values[i++] = UInt32GetDatum(block_fpi_len);
     355             : 
     356             :         /* block_fpi_info (text array) output */
     357          44 :         if (block_fpi_info)
     358           2 :             values[i++] = PointerGetDatum(block_fpi_info);
     359             :         else
     360          42 :             nulls[i++] = true;
     361             : 
     362             :         /* description output (describes WAL record) */
     363          44 :         if (rec_desc.len > 0)
     364          42 :             values[i++] = CStringGetTextDatum(rec_desc.data);
     365             :         else
     366           2 :             nulls[i++] = true;
     367             : 
     368             :         /* block_data output */
     369          44 :         if (blk->has_data && show_data)
     370          42 :         {
     371             :             bytea      *block_data;
     372             : 
     373          42 :             block_data = (bytea *) palloc(block_data_len + VARHDRSZ);
     374          42 :             SET_VARSIZE(block_data, block_data_len + VARHDRSZ);
     375          42 :             memcpy(VARDATA(block_data), blk->data, block_data_len);
     376          42 :             values[i++] = PointerGetDatum(block_data);
     377             :         }
     378             :         else
     379           2 :             nulls[i++] = true;
     380             : 
     381             :         /* block_fpi_data output */
     382          44 :         if (blk->has_image && show_data)
     383           2 :         {
     384             :             PGAlignedBlock buf;
     385             :             Page        page;
     386             :             bytea      *block_fpi_data;
     387             : 
     388           2 :             page = (Page) buf.data;
     389           2 :             if (!RestoreBlockImage(record, block_id, page))
     390           0 :                 ereport(ERROR,
     391             :                         (errcode(ERRCODE_INTERNAL_ERROR),
     392             :                          errmsg_internal("%s", record->errormsg_buf)));
     393             : 
     394           2 :             block_fpi_data = (bytea *) palloc(BLCKSZ + VARHDRSZ);
     395           2 :             SET_VARSIZE(block_fpi_data, BLCKSZ + VARHDRSZ);
     396           2 :             memcpy(VARDATA(block_fpi_data), page, BLCKSZ);
     397           2 :             values[i++] = PointerGetDatum(block_fpi_data);
     398             :         }
     399             :         else
     400          42 :             nulls[i++] = true;
     401             : 
     402             :         Assert(i == PG_GET_WAL_BLOCK_INFO_COLS);
     403             : 
     404             :         /* Store a tuple for this block reference */
     405          44 :         tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
     406             :                              values, nulls);
     407             :     }
     408             : 
     409             : #undef PG_GET_WAL_BLOCK_INFO_COLS
     410          44 : }
     411             : 
     412             : /*
     413             :  * Get WAL record info, unnested by block reference
     414             :  */
     415             : Datum
     416          14 : pg_get_wal_block_info(PG_FUNCTION_ARGS)
     417             : {
     418          14 :     XLogRecPtr  start_lsn = PG_GETARG_LSN(0);
     419          14 :     XLogRecPtr  end_lsn = PG_GETARG_LSN(1);
     420          14 :     bool        show_data = PG_GETARG_BOOL(2);
     421             :     XLogReaderState *xlogreader;
     422             :     MemoryContext old_cxt;
     423             :     MemoryContext tmp_cxt;
     424             : 
     425          14 :     ValidateInputLSNs(start_lsn, &end_lsn);
     426             : 
     427          10 :     InitMaterializedSRF(fcinfo, 0);
     428             : 
     429          10 :     xlogreader = InitXLogReaderState(start_lsn);
     430             : 
     431           8 :     tmp_cxt = AllocSetContextCreate(CurrentMemoryContext,
     432             :                                     "pg_get_wal_block_info temporary cxt",
     433             :                                     ALLOCSET_DEFAULT_SIZES);
     434             : 
     435          68 :     while (ReadNextXLogRecord(xlogreader) &&
     436          62 :            xlogreader->EndRecPtr <= end_lsn)
     437             :     {
     438          60 :         CHECK_FOR_INTERRUPTS();
     439             : 
     440          60 :         if (!XLogRecHasAnyBlockRefs(xlogreader))
     441          16 :             continue;
     442             : 
     443             :         /* Use the tmp context so we can clean up after each tuple is done */
     444          44 :         old_cxt = MemoryContextSwitchTo(tmp_cxt);
     445             : 
     446          44 :         GetWALBlockInfo(fcinfo, xlogreader, show_data);
     447             : 
     448             :         /* clean up and switch back */
     449          44 :         MemoryContextSwitchTo(old_cxt);
     450          44 :         MemoryContextReset(tmp_cxt);
     451             :     }
     452             : 
     453           8 :     MemoryContextDelete(tmp_cxt);
     454           8 :     pfree(xlogreader->private_data);
     455           8 :     XLogReaderFree(xlogreader);
     456             : 
     457           8 :     PG_RETURN_VOID();
     458             : }
     459             : 
     460             : /*
     461             :  * Get WAL record info.
     462             :  */
     463             : Datum
     464           8 : pg_get_wal_record_info(PG_FUNCTION_ARGS)
     465             : {
     466             : #define PG_GET_WAL_RECORD_INFO_COLS 11
     467             :     Datum       result;
     468           8 :     Datum       values[PG_GET_WAL_RECORD_INFO_COLS] = {0};
     469           8 :     bool        nulls[PG_GET_WAL_RECORD_INFO_COLS] = {0};
     470             :     XLogRecPtr  lsn;
     471             :     XLogRecPtr  curr_lsn;
     472             :     XLogReaderState *xlogreader;
     473             :     TupleDesc   tupdesc;
     474             :     HeapTuple   tuple;
     475             : 
     476           8 :     lsn = PG_GETARG_LSN(0);
     477           8 :     curr_lsn = GetCurrentLSN();
     478             : 
     479           8 :     if (lsn > curr_lsn)
     480           2 :         ereport(ERROR,
     481             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     482             :                  errmsg("WAL input LSN must be less than current LSN"),
     483             :                  errdetail("Current WAL LSN on the database system is at %X/%08X.",
     484             :                            LSN_FORMAT_ARGS(curr_lsn))));
     485             : 
     486             :     /* Build a tuple descriptor for our result type. */
     487           6 :     if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
     488           0 :         elog(ERROR, "return type must be a row type");
     489             : 
     490           6 :     xlogreader = InitXLogReaderState(lsn);
     491             : 
     492           4 :     if (!ReadNextXLogRecord(xlogreader))
     493           0 :         ereport(ERROR,
     494             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     495             :                  errmsg("could not read WAL at %X/%08X",
     496             :                         LSN_FORMAT_ARGS(xlogreader->EndRecPtr))));
     497             : 
     498           4 :     GetWALRecordInfo(xlogreader, values, nulls, PG_GET_WAL_RECORD_INFO_COLS);
     499             : 
     500           4 :     pfree(xlogreader->private_data);
     501           4 :     XLogReaderFree(xlogreader);
     502             : 
     503           4 :     tuple = heap_form_tuple(tupdesc, values, nulls);
     504           4 :     result = HeapTupleGetDatum(tuple);
     505             : 
     506           4 :     PG_RETURN_DATUM(result);
     507             : #undef PG_GET_WAL_RECORD_INFO_COLS
     508             : }
     509             : 
     510             : /*
     511             :  * Validate start and end LSNs coming from the function inputs.
     512             :  *
     513             :  * If end_lsn is found to be higher than the current LSN reported by the
     514             :  * cluster, use the current LSN as the upper bound.
     515             :  */
     516             : static void
     517          44 : ValidateInputLSNs(XLogRecPtr start_lsn, XLogRecPtr *end_lsn)
     518             : {
     519          44 :     XLogRecPtr  curr_lsn = GetCurrentLSN();
     520             : 
     521          44 :     if (start_lsn > curr_lsn)
     522           6 :         ereport(ERROR,
     523             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     524             :                  errmsg("WAL start LSN must be less than current LSN"),
     525             :                  errdetail("Current WAL LSN on the database system is at %X/%08X.",
     526             :                            LSN_FORMAT_ARGS(curr_lsn))));
     527             : 
     528          38 :     if (start_lsn > *end_lsn)
     529           6 :         ereport(ERROR,
     530             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     531             :                  errmsg("WAL start LSN must be less than end LSN")));
     532             : 
     533          32 :     if (*end_lsn > curr_lsn)
     534           8 :         *end_lsn = curr_lsn;
     535          32 : }
     536             : 
     537             : /*
     538             :  * Get info of all WAL records between start LSN and end LSN.
     539             :  */
     540             : static void
     541          18 : GetWALRecordsInfo(FunctionCallInfo fcinfo, XLogRecPtr start_lsn,
     542             :                   XLogRecPtr end_lsn)
     543             : {
     544             : #define PG_GET_WAL_RECORDS_INFO_COLS 11
     545             :     XLogReaderState *xlogreader;
     546          18 :     ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
     547             :     MemoryContext old_cxt;
     548             :     MemoryContext tmp_cxt;
     549             : 
     550             :     Assert(start_lsn <= end_lsn);
     551             : 
     552          18 :     InitMaterializedSRF(fcinfo, 0);
     553             : 
     554          18 :     xlogreader = InitXLogReaderState(start_lsn);
     555             : 
     556          16 :     tmp_cxt = AllocSetContextCreate(CurrentMemoryContext,
     557             :                                     "GetWALRecordsInfo temporary cxt",
     558             :                                     ALLOCSET_DEFAULT_SIZES);
     559             : 
     560       68716 :     while (ReadNextXLogRecord(xlogreader) &&
     561       68706 :            xlogreader->EndRecPtr <= end_lsn)
     562             :     {
     563       68700 :         Datum       values[PG_GET_WAL_RECORDS_INFO_COLS] = {0};
     564       68700 :         bool        nulls[PG_GET_WAL_RECORDS_INFO_COLS] = {0};
     565             : 
     566             :         /* Use the tmp context so we can clean up after each tuple is done */
     567       68700 :         old_cxt = MemoryContextSwitchTo(tmp_cxt);
     568             : 
     569       68700 :         GetWALRecordInfo(xlogreader, values, nulls,
     570             :                          PG_GET_WAL_RECORDS_INFO_COLS);
     571             : 
     572       68700 :         tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
     573             :                              values, nulls);
     574             : 
     575             :         /* clean up and switch back */
     576       68700 :         MemoryContextSwitchTo(old_cxt);
     577       68700 :         MemoryContextReset(tmp_cxt);
     578             : 
     579       68700 :         CHECK_FOR_INTERRUPTS();
     580             :     }
     581             : 
     582          16 :     MemoryContextDelete(tmp_cxt);
     583          16 :     pfree(xlogreader->private_data);
     584          16 :     XLogReaderFree(xlogreader);
     585             : 
     586             : #undef PG_GET_WAL_RECORDS_INFO_COLS
     587          16 : }
     588             : 
     589             : /*
     590             :  * Get info of all WAL records between start LSN and end LSN.
     591             :  */
     592             : Datum
     593          20 : pg_get_wal_records_info(PG_FUNCTION_ARGS)
     594             : {
     595          20 :     XLogRecPtr  start_lsn = PG_GETARG_LSN(0);
     596          20 :     XLogRecPtr  end_lsn = PG_GETARG_LSN(1);
     597             : 
     598          20 :     ValidateInputLSNs(start_lsn, &end_lsn);
     599          16 :     GetWALRecordsInfo(fcinfo, start_lsn, end_lsn);
     600             : 
     601          14 :     PG_RETURN_VOID();
     602             : }
     603             : 
     604             : /*
     605             :  * Fill single row of record counts and sizes for an rmgr or record.
     606             :  */
     607             : static void
     608         132 : FillXLogStatsRow(const char *name,
     609             :                  uint64 n, uint64 total_count,
     610             :                  uint64 rec_len, uint64 total_rec_len,
     611             :                  uint64 fpi_len, uint64 total_fpi_len,
     612             :                  uint64 tot_len, uint64 total_len,
     613             :                  Datum *values, bool *nulls, uint32 ncols)
     614             : {
     615             :     double      n_pct,
     616             :                 rec_len_pct,
     617             :                 fpi_len_pct,
     618             :                 tot_len_pct;
     619         132 :     int         i = 0;
     620             : 
     621         132 :     n_pct = 0;
     622         132 :     if (total_count != 0)
     623         132 :         n_pct = 100 * (double) n / total_count;
     624             : 
     625         132 :     rec_len_pct = 0;
     626         132 :     if (total_rec_len != 0)
     627         132 :         rec_len_pct = 100 * (double) rec_len / total_rec_len;
     628             : 
     629         132 :     fpi_len_pct = 0;
     630         132 :     if (total_fpi_len != 0)
     631           0 :         fpi_len_pct = 100 * (double) fpi_len / total_fpi_len;
     632             : 
     633         132 :     tot_len_pct = 0;
     634         132 :     if (total_len != 0)
     635         132 :         tot_len_pct = 100 * (double) tot_len / total_len;
     636             : 
     637         132 :     values[i++] = CStringGetTextDatum(name);
     638         132 :     values[i++] = Int64GetDatum(n);
     639         132 :     values[i++] = Float8GetDatum(n_pct);
     640         132 :     values[i++] = Int64GetDatum(rec_len);
     641         132 :     values[i++] = Float8GetDatum(rec_len_pct);
     642         132 :     values[i++] = Int64GetDatum(fpi_len);
     643         132 :     values[i++] = Float8GetDatum(fpi_len_pct);
     644         132 :     values[i++] = Int64GetDatum(tot_len);
     645         132 :     values[i++] = Float8GetDatum(tot_len_pct);
     646             : 
     647             :     Assert(i == ncols);
     648         132 : }
     649             : 
     650             : /*
     651             :  * Get summary statistics about the records seen so far.
     652             :  */
     653             : static void
     654           6 : GetXLogSummaryStats(XLogStats *stats, ReturnSetInfo *rsinfo,
     655             :                     Datum *values, bool *nulls, uint32 ncols,
     656             :                     bool stats_per_record)
     657             : {
     658             :     MemoryContext old_cxt;
     659             :     MemoryContext tmp_cxt;
     660           6 :     uint64      total_count = 0;
     661           6 :     uint64      total_rec_len = 0;
     662           6 :     uint64      total_fpi_len = 0;
     663           6 :     uint64      total_len = 0;
     664             :     int         ri;
     665             : 
     666             :     /*
     667             :      * Each row shows its percentages of the total, so make a first pass to
     668             :      * calculate column totals.
     669             :      */
     670        1542 :     for (ri = 0; ri <= RM_MAX_ID; ri++)
     671             :     {
     672        1536 :         if (!RmgrIdIsValid(ri))
     673         636 :             continue;
     674             : 
     675         900 :         total_count += stats->rmgr_stats[ri].count;
     676         900 :         total_rec_len += stats->rmgr_stats[ri].rec_len;
     677         900 :         total_fpi_len += stats->rmgr_stats[ri].fpi_len;
     678             :     }
     679           6 :     total_len = total_rec_len + total_fpi_len;
     680             : 
     681           6 :     tmp_cxt = AllocSetContextCreate(CurrentMemoryContext,
     682             :                                     "GetXLogSummaryStats temporary cxt",
     683             :                                     ALLOCSET_DEFAULT_SIZES);
     684             : 
     685        1542 :     for (ri = 0; ri <= RM_MAX_ID; ri++)
     686             :     {
     687             :         uint64      count;
     688             :         uint64      rec_len;
     689             :         uint64      fpi_len;
     690             :         uint64      tot_len;
     691             :         RmgrData    desc;
     692             : 
     693        1536 :         if (!RmgrIdIsValid(ri))
     694        1404 :             continue;
     695             : 
     696         900 :         if (!RmgrIdExists(ri))
     697         768 :             continue;
     698             : 
     699         132 :         desc = GetRmgr(ri);
     700             : 
     701         132 :         if (stats_per_record)
     702             :         {
     703             :             int         rj;
     704             : 
     705           0 :             for (rj = 0; rj < MAX_XLINFO_TYPES; rj++)
     706             :             {
     707             :                 const char *id;
     708             : 
     709           0 :                 count = stats->record_stats[ri][rj].count;
     710           0 :                 rec_len = stats->record_stats[ri][rj].rec_len;
     711           0 :                 fpi_len = stats->record_stats[ri][rj].fpi_len;
     712           0 :                 tot_len = rec_len + fpi_len;
     713             : 
     714             :                 /* Skip undefined combinations and ones that didn't occur */
     715           0 :                 if (count == 0)
     716           0 :                     continue;
     717             : 
     718           0 :                 old_cxt = MemoryContextSwitchTo(tmp_cxt);
     719             : 
     720             :                 /* the upper four bits in xl_info are the rmgr's */
     721           0 :                 id = desc.rm_identify(rj << 4);
     722           0 :                 if (id == NULL)
     723           0 :                     id = psprintf("UNKNOWN (%x)", rj << 4);
     724             : 
     725           0 :                 FillXLogStatsRow(psprintf("%s/%s", desc.rm_name, id), count,
     726             :                                  total_count, rec_len, total_rec_len, fpi_len,
     727             :                                  total_fpi_len, tot_len, total_len,
     728             :                                  values, nulls, ncols);
     729             : 
     730           0 :                 tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
     731             :                                      values, nulls);
     732             : 
     733             :                 /* clean up and switch back */
     734           0 :                 MemoryContextSwitchTo(old_cxt);
     735           0 :                 MemoryContextReset(tmp_cxt);
     736             :             }
     737             :         }
     738             :         else
     739             :         {
     740         132 :             count = stats->rmgr_stats[ri].count;
     741         132 :             rec_len = stats->rmgr_stats[ri].rec_len;
     742         132 :             fpi_len = stats->rmgr_stats[ri].fpi_len;
     743         132 :             tot_len = rec_len + fpi_len;
     744             : 
     745         132 :             old_cxt = MemoryContextSwitchTo(tmp_cxt);
     746             : 
     747         132 :             FillXLogStatsRow(desc.rm_name, count, total_count, rec_len,
     748             :                              total_rec_len, fpi_len, total_fpi_len, tot_len,
     749             :                              total_len, values, nulls, ncols);
     750             : 
     751         132 :             tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
     752             :                                  values, nulls);
     753             : 
     754             :             /* clean up and switch back */
     755         132 :             MemoryContextSwitchTo(old_cxt);
     756         132 :             MemoryContextReset(tmp_cxt);
     757             :         }
     758             :     }
     759             : 
     760           6 :     MemoryContextDelete(tmp_cxt);
     761           6 : }
     762             : 
     763             : /*
     764             :  * Get WAL stats between start LSN and end LSN.
     765             :  */
     766             : static void
     767           8 : GetWalStats(FunctionCallInfo fcinfo, XLogRecPtr start_lsn, XLogRecPtr end_lsn,
     768             :             bool stats_per_record)
     769             : {
     770             : #define PG_GET_WAL_STATS_COLS 9
     771             :     XLogReaderState *xlogreader;
     772           8 :     XLogStats   stats = {0};
     773           8 :     ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
     774           8 :     Datum       values[PG_GET_WAL_STATS_COLS] = {0};
     775           8 :     bool        nulls[PG_GET_WAL_STATS_COLS] = {0};
     776             : 
     777             :     Assert(start_lsn <= end_lsn);
     778             : 
     779           8 :     InitMaterializedSRF(fcinfo, 0);
     780             : 
     781           8 :     xlogreader = InitXLogReaderState(start_lsn);
     782             : 
     783          48 :     while (ReadNextXLogRecord(xlogreader) &&
     784          38 :            xlogreader->EndRecPtr <= end_lsn)
     785             :     {
     786          36 :         XLogRecStoreStats(&stats, xlogreader);
     787             : 
     788          36 :         CHECK_FOR_INTERRUPTS();
     789             :     }
     790             : 
     791           6 :     pfree(xlogreader->private_data);
     792           6 :     XLogReaderFree(xlogreader);
     793             : 
     794           6 :     GetXLogSummaryStats(&stats, rsinfo, values, nulls,
     795             :                         PG_GET_WAL_STATS_COLS,
     796             :                         stats_per_record);
     797             : 
     798             : #undef PG_GET_WAL_STATS_COLS
     799           6 : }
     800             : 
     801             : /*
     802             :  * Get stats of all WAL records between start LSN and end LSN.
     803             :  */
     804             : Datum
     805          10 : pg_get_wal_stats(PG_FUNCTION_ARGS)
     806             : {
     807          10 :     XLogRecPtr  start_lsn = PG_GETARG_LSN(0);
     808          10 :     XLogRecPtr  end_lsn = PG_GETARG_LSN(1);
     809          10 :     bool        stats_per_record = PG_GETARG_BOOL(2);
     810             : 
     811          10 :     ValidateInputLSNs(start_lsn, &end_lsn);
     812           6 :     GetWalStats(fcinfo, start_lsn, end_lsn, stats_per_record);
     813             : 
     814           4 :     PG_RETURN_VOID();
     815             : }
     816             : 
     817             : /*
     818             :  * The following functions have been removed in newer versions in 1.1, but
     819             :  * they are kept around for compatibility.
     820             :  */
     821             : Datum
     822           4 : pg_get_wal_records_info_till_end_of_wal(PG_FUNCTION_ARGS)
     823             : {
     824           4 :     XLogRecPtr  start_lsn = PG_GETARG_LSN(0);
     825           4 :     XLogRecPtr  end_lsn = GetCurrentLSN();
     826             : 
     827           4 :     if (start_lsn > end_lsn)
     828           2 :         ereport(ERROR,
     829             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     830             :                  errmsg("WAL start LSN must be less than current LSN"),
     831             :                  errdetail("Current WAL LSN on the database system is at %X/%08X.",
     832             :                            LSN_FORMAT_ARGS(end_lsn))));
     833             : 
     834           2 :     GetWALRecordsInfo(fcinfo, start_lsn, end_lsn);
     835             : 
     836           2 :     PG_RETURN_VOID();
     837             : }
     838             : 
     839             : Datum
     840           4 : pg_get_wal_stats_till_end_of_wal(PG_FUNCTION_ARGS)
     841             : {
     842           4 :     XLogRecPtr  start_lsn = PG_GETARG_LSN(0);
     843           4 :     XLogRecPtr  end_lsn = GetCurrentLSN();
     844           4 :     bool        stats_per_record = PG_GETARG_BOOL(1);
     845             : 
     846           4 :     if (start_lsn > end_lsn)
     847           2 :         ereport(ERROR,
     848             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     849             :                  errmsg("WAL start LSN must be less than current LSN"),
     850             :                  errdetail("Current WAL LSN on the database system is at %X/%08X.",
     851             :                            LSN_FORMAT_ARGS(end_lsn))));
     852             : 
     853           2 :     GetWalStats(fcinfo, start_lsn, end_lsn, stats_per_record);
     854             : 
     855           2 :     PG_RETURN_VOID();
     856             : }

Generated by: LCOV version 1.16