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

Generated by: LCOV version 1.14