1 /*-------------------------------------------------------------------------
4 * Functions to inspect contents of PostgreSQL Write-Ahead Log
6 * Copyright (c) 2022-2025, PostgreSQL Global Development Group
9 * contrib/pg_walinspect/pg_walinspect.c
11 *-------------------------------------------------------------------------
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"
22 #include "miscadmin.h"
23 #include "utils/array.h"
24 #include "utils/builtins.h"
25 #include "utils/pg_lsn.h"
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.
34 PG_FUNCTION_INFO_V1(pg_get_wal_block_info
);
35 PG_FUNCTION_INFO_V1(pg_get_wal_record_info
);
36 PG_FUNCTION_INFO_V1(pg_get_wal_records_info
);
37 PG_FUNCTION_INFO_V1(pg_get_wal_records_info_till_end_of_wal
);
38 PG_FUNCTION_INFO_V1(pg_get_wal_stats
);
39 PG_FUNCTION_INFO_V1(pg_get_wal_stats_till_end_of_wal
);
41 static void ValidateInputLSNs(XLogRecPtr start_lsn
, XLogRecPtr
*end_lsn
);
42 static XLogRecPtr
GetCurrentLSN(void);
43 static XLogReaderState
*InitXLogReaderState(XLogRecPtr lsn
);
44 static XLogRecord
*ReadNextXLogRecord(XLogReaderState
*xlogreader
);
45 static void GetWALRecordInfo(XLogReaderState
*record
, Datum
*values
,
46 bool *nulls
, uint32 ncols
);
47 static void GetWALRecordsInfo(FunctionCallInfo fcinfo
,
50 static void GetXLogSummaryStats(XLogStats
*stats
, ReturnSetInfo
*rsinfo
,
51 Datum
*values
, bool *nulls
, uint32 ncols
,
52 bool stats_per_record
);
53 static void FillXLogStatsRow(const char *name
, uint64 n
, uint64 total_count
,
54 uint64 rec_len
, uint64 total_rec_len
,
55 uint64 fpi_len
, uint64 total_fpi_len
,
56 uint64 tot_len
, uint64 total_len
,
57 Datum
*values
, bool *nulls
, uint32 ncols
);
58 static void GetWalStats(FunctionCallInfo fcinfo
,
61 bool stats_per_record
);
62 static void GetWALBlockInfo(FunctionCallInfo fcinfo
, XLogReaderState
*record
,
66 * Return the LSN up to which the server has WAL.
74 * We determine the current LSN of the server similar to how page_read
75 * callback read_local_xlog_page_no_wait does.
77 if (!RecoveryInProgress())
78 curr_lsn
= GetFlushRecPtr(NULL
);
80 curr_lsn
= GetXLogReplayRecPtr(NULL
);
82 Assert(!XLogRecPtrIsInvalid(curr_lsn
));
88 * Initialize WAL reader and identify first valid LSN.
90 static XLogReaderState
*
91 InitXLogReaderState(XLogRecPtr lsn
)
93 XLogReaderState
*xlogreader
;
94 ReadLocalXLogPageNoWaitPrivate
*private_data
;
95 XLogRecPtr first_valid_record
;
98 * Reading WAL below the first page of the first segments isn't allowed.
99 * This is a bootstrap WAL page and the page_read callback fails to read
102 if (lsn
< XLOG_BLCKSZ
)
104 (errcode(ERRCODE_INVALID_PARAMETER_VALUE
),
105 errmsg("could not read WAL at LSN %X/%X",
106 LSN_FORMAT_ARGS(lsn
))));
108 private_data
= (ReadLocalXLogPageNoWaitPrivate
*)
109 palloc0(sizeof(ReadLocalXLogPageNoWaitPrivate
));
111 xlogreader
= XLogReaderAllocate(wal_segment_size
, NULL
,
112 XL_ROUTINE(.page_read
= &read_local_xlog_page_no_wait
,
113 .segment_open
= &wal_segment_open
,
114 .segment_close
= &wal_segment_close
),
117 if (xlogreader
== NULL
)
119 (errcode(ERRCODE_OUT_OF_MEMORY
),
120 errmsg("out of memory"),
121 errdetail("Failed while allocating a WAL reading processor.")));
123 /* first find a valid recptr to start from */
124 first_valid_record
= XLogFindNextRecord(xlogreader
, lsn
);
126 if (XLogRecPtrIsInvalid(first_valid_record
))
128 (errmsg("could not find a valid record after %X/%X",
129 LSN_FORMAT_ARGS(lsn
))));
135 * Read next WAL record.
137 * By design, to be less intrusive in a running system, no slot is allocated
138 * to reserve the WAL we're about to read. Therefore this function can
139 * encounter read errors for historical WAL.
141 * We guard against ordinary errors trying to read WAL that hasn't been
142 * written yet by limiting end_lsn to the flushed WAL, but that can also
143 * encounter errors if the flush pointer falls in the middle of a record. In
144 * that case we'll return NULL.
147 ReadNextXLogRecord(XLogReaderState
*xlogreader
)
152 record
= XLogReadRecord(xlogreader
, &errormsg
);
156 ReadLocalXLogPageNoWaitPrivate
*private_data
;
158 /* return NULL, if end of WAL is reached */
159 private_data
= (ReadLocalXLogPageNoWaitPrivate
*)
160 xlogreader
->private_data
;
162 if (private_data
->end_of_wal
)
167 (errcode_for_file_access(),
168 errmsg("could not read WAL at %X/%X: %s",
169 LSN_FORMAT_ARGS(xlogreader
->EndRecPtr
), errormsg
)));
172 (errcode_for_file_access(),
173 errmsg("could not read WAL at %X/%X",
174 LSN_FORMAT_ARGS(xlogreader
->EndRecPtr
))));
181 * Output values that make up a row describing caller's WAL record.
183 * This function leaks memory. Caller may need to use its own custom memory
186 * Keep this in sync with GetWALBlockInfo.
189 GetWALRecordInfo(XLogReaderState
*record
, Datum
*values
,
190 bool *nulls
, uint32 ncols
)
192 const char *record_type
;
195 StringInfoData rec_desc
;
196 StringInfoData rec_blk_ref
;
199 desc
= GetRmgr(XLogRecGetRmid(record
));
200 record_type
= desc
.rm_identify(XLogRecGetInfo(record
));
202 if (record_type
== NULL
)
203 record_type
= psprintf("UNKNOWN (%x)", XLogRecGetInfo(record
) & ~XLR_INFO_MASK
);
205 initStringInfo(&rec_desc
);
206 desc
.rm_desc(&rec_desc
, record
);
208 if (XLogRecHasAnyBlockRefs(record
))
210 initStringInfo(&rec_blk_ref
);
211 XLogRecGetBlockRefInfo(record
, false, true, &rec_blk_ref
, &fpi_len
);
214 values
[i
++] = LSNGetDatum(record
->ReadRecPtr
);
215 values
[i
++] = LSNGetDatum(record
->EndRecPtr
);
216 values
[i
++] = LSNGetDatum(XLogRecGetPrev(record
));
217 values
[i
++] = TransactionIdGetDatum(XLogRecGetXid(record
));
218 values
[i
++] = CStringGetTextDatum(desc
.rm_name
);
219 values
[i
++] = CStringGetTextDatum(record_type
);
220 values
[i
++] = UInt32GetDatum(XLogRecGetTotalLen(record
));
221 values
[i
++] = UInt32GetDatum(XLogRecGetDataLen(record
));
222 values
[i
++] = UInt32GetDatum(fpi_len
);
224 if (rec_desc
.len
> 0)
225 values
[i
++] = CStringGetTextDatum(rec_desc
.data
);
229 if (XLogRecHasAnyBlockRefs(record
))
230 values
[i
++] = CStringGetTextDatum(rec_blk_ref
.data
);
239 * Output one or more rows in rsinfo tuple store, each describing a single
240 * block reference from caller's WAL record. (Should only be called with
241 * records that have block references.)
243 * This function leaks memory. Caller may need to use its own custom memory
246 * Keep this in sync with GetWALRecordInfo.
249 GetWALBlockInfo(FunctionCallInfo fcinfo
, XLogReaderState
*record
,
252 #define PG_GET_WAL_BLOCK_INFO_COLS 20
254 ReturnSetInfo
*rsinfo
= (ReturnSetInfo
*) fcinfo
->resultinfo
;
256 const char *record_type
;
257 StringInfoData rec_desc
;
259 Assert(XLogRecHasAnyBlockRefs(record
));
261 desc
= GetRmgr(XLogRecGetRmid(record
));
262 record_type
= desc
.rm_identify(XLogRecGetInfo(record
));
264 if (record_type
== NULL
)
265 record_type
= psprintf("UNKNOWN (%x)",
266 XLogRecGetInfo(record
) & ~XLR_INFO_MASK
);
268 initStringInfo(&rec_desc
);
269 desc
.rm_desc(&rec_desc
, record
);
271 for (block_id
= 0; block_id
<= XLogRecMaxBlockId(record
); block_id
++)
273 DecodedBkpBlock
*blk
;
275 RelFileLocator rnode
;
277 Datum values
[PG_GET_WAL_BLOCK_INFO_COLS
] = {0};
278 bool nulls
[PG_GET_WAL_BLOCK_INFO_COLS
] = {0};
279 uint32 block_data_len
= 0,
281 ArrayType
*block_fpi_info
= NULL
;
284 if (!XLogRecHasBlockRef(record
, block_id
))
287 blk
= XLogRecGetBlock(record
, block_id
);
289 (void) XLogRecGetBlockTagExtended(record
, block_id
,
290 &rnode
, &forknum
, &blkno
, NULL
);
292 /* Save block_data_len */
294 block_data_len
= blk
->data_len
;
298 /* Block reference has an FPI, so prepare relevant output */
303 /* Save block_fpi_len */
304 block_fpi_len
= blk
->bimg_len
;
306 /* Construct and save block_fpi_info */
307 bitcnt
= pg_popcount((const char *) &blk
->bimg_info
,
309 flags
= (Datum
*) palloc0(sizeof(Datum
) * bitcnt
);
310 if ((blk
->bimg_info
& BKPIMAGE_HAS_HOLE
) != 0)
311 flags
[cnt
++] = CStringGetTextDatum("HAS_HOLE");
312 if (blk
->apply_image
)
313 flags
[cnt
++] = CStringGetTextDatum("APPLY");
314 if ((blk
->bimg_info
& BKPIMAGE_COMPRESS_PGLZ
) != 0)
315 flags
[cnt
++] = CStringGetTextDatum("COMPRESS_PGLZ");
316 if ((blk
->bimg_info
& BKPIMAGE_COMPRESS_LZ4
) != 0)
317 flags
[cnt
++] = CStringGetTextDatum("COMPRESS_LZ4");
318 if ((blk
->bimg_info
& BKPIMAGE_COMPRESS_ZSTD
) != 0)
319 flags
[cnt
++] = CStringGetTextDatum("COMPRESS_ZSTD");
321 Assert(cnt
<= bitcnt
);
322 block_fpi_info
= construct_array_builtin(flags
, cnt
, TEXTOID
);
325 /* start_lsn, end_lsn, prev_lsn, and blockid outputs */
326 values
[i
++] = LSNGetDatum(record
->ReadRecPtr
);
327 values
[i
++] = LSNGetDatum(record
->EndRecPtr
);
328 values
[i
++] = LSNGetDatum(XLogRecGetPrev(record
));
329 values
[i
++] = Int16GetDatum(block_id
);
331 /* relfile and block related outputs */
332 values
[i
++] = ObjectIdGetDatum(blk
->rlocator
.spcOid
);
333 values
[i
++] = ObjectIdGetDatum(blk
->rlocator
.dbOid
);
334 values
[i
++] = ObjectIdGetDatum(blk
->rlocator
.relNumber
);
335 values
[i
++] = Int16GetDatum(forknum
);
336 values
[i
++] = Int64GetDatum((int64
) blkno
);
338 /* xid, resource_manager, and record_type outputs */
339 values
[i
++] = TransactionIdGetDatum(XLogRecGetXid(record
));
340 values
[i
++] = CStringGetTextDatum(desc
.rm_name
);
341 values
[i
++] = CStringGetTextDatum(record_type
);
344 * record_length, main_data_length, block_data_len, and
345 * block_fpi_length outputs
347 values
[i
++] = UInt32GetDatum(XLogRecGetTotalLen(record
));
348 values
[i
++] = UInt32GetDatum(XLogRecGetDataLen(record
));
349 values
[i
++] = UInt32GetDatum(block_data_len
);
350 values
[i
++] = UInt32GetDatum(block_fpi_len
);
352 /* block_fpi_info (text array) output */
354 values
[i
++] = PointerGetDatum(block_fpi_info
);
358 /* description output (describes WAL record) */
359 if (rec_desc
.len
> 0)
360 values
[i
++] = CStringGetTextDatum(rec_desc
.data
);
364 /* block_data output */
365 if (blk
->has_data
&& show_data
)
369 block_data
= (bytea
*) palloc(block_data_len
+ VARHDRSZ
);
370 SET_VARSIZE(block_data
, block_data_len
+ VARHDRSZ
);
371 memcpy(VARDATA(block_data
), blk
->data
, block_data_len
);
372 values
[i
++] = PointerGetDatum(block_data
);
377 /* block_fpi_data output */
378 if (blk
->has_image
&& show_data
)
382 bytea
*block_fpi_data
;
384 page
= (Page
) buf
.data
;
385 if (!RestoreBlockImage(record
, block_id
, page
))
387 (errcode(ERRCODE_INTERNAL_ERROR
),
388 errmsg_internal("%s", record
->errormsg_buf
)));
390 block_fpi_data
= (bytea
*) palloc(BLCKSZ
+ VARHDRSZ
);
391 SET_VARSIZE(block_fpi_data
, BLCKSZ
+ VARHDRSZ
);
392 memcpy(VARDATA(block_fpi_data
), page
, BLCKSZ
);
393 values
[i
++] = PointerGetDatum(block_fpi_data
);
398 Assert(i
== PG_GET_WAL_BLOCK_INFO_COLS
);
400 /* Store a tuple for this block reference */
401 tuplestore_putvalues(rsinfo
->setResult
, rsinfo
->setDesc
,
405 #undef PG_GET_WAL_BLOCK_INFO_COLS
409 * Get WAL record info, unnested by block reference
412 pg_get_wal_block_info(PG_FUNCTION_ARGS
)
414 XLogRecPtr start_lsn
= PG_GETARG_LSN(0);
415 XLogRecPtr end_lsn
= PG_GETARG_LSN(1);
416 bool show_data
= PG_GETARG_BOOL(2);
417 XLogReaderState
*xlogreader
;
418 MemoryContext old_cxt
;
419 MemoryContext tmp_cxt
;
421 ValidateInputLSNs(start_lsn
, &end_lsn
);
423 InitMaterializedSRF(fcinfo
, 0);
425 xlogreader
= InitXLogReaderState(start_lsn
);
427 tmp_cxt
= AllocSetContextCreate(CurrentMemoryContext
,
428 "pg_get_wal_block_info temporary cxt",
429 ALLOCSET_DEFAULT_SIZES
);
431 while (ReadNextXLogRecord(xlogreader
) &&
432 xlogreader
->EndRecPtr
<= end_lsn
)
434 CHECK_FOR_INTERRUPTS();
436 if (!XLogRecHasAnyBlockRefs(xlogreader
))
439 /* Use the tmp context so we can clean up after each tuple is done */
440 old_cxt
= MemoryContextSwitchTo(tmp_cxt
);
442 GetWALBlockInfo(fcinfo
, xlogreader
, show_data
);
444 /* clean up and switch back */
445 MemoryContextSwitchTo(old_cxt
);
446 MemoryContextReset(tmp_cxt
);
449 MemoryContextDelete(tmp_cxt
);
450 pfree(xlogreader
->private_data
);
451 XLogReaderFree(xlogreader
);
457 * Get WAL record info.
460 pg_get_wal_record_info(PG_FUNCTION_ARGS
)
462 #define PG_GET_WAL_RECORD_INFO_COLS 11
464 Datum values
[PG_GET_WAL_RECORD_INFO_COLS
] = {0};
465 bool nulls
[PG_GET_WAL_RECORD_INFO_COLS
] = {0};
468 XLogReaderState
*xlogreader
;
472 lsn
= PG_GETARG_LSN(0);
473 curr_lsn
= GetCurrentLSN();
477 (errcode(ERRCODE_INVALID_PARAMETER_VALUE
),
478 errmsg("WAL input LSN must be less than current LSN"),
479 errdetail("Current WAL LSN on the database system is at %X/%X.",
480 LSN_FORMAT_ARGS(curr_lsn
))));
482 /* Build a tuple descriptor for our result type. */
483 if (get_call_result_type(fcinfo
, NULL
, &tupdesc
) != TYPEFUNC_COMPOSITE
)
484 elog(ERROR
, "return type must be a row type");
486 xlogreader
= InitXLogReaderState(lsn
);
488 if (!ReadNextXLogRecord(xlogreader
))
490 (errcode(ERRCODE_INVALID_PARAMETER_VALUE
),
491 errmsg("could not read WAL at %X/%X",
492 LSN_FORMAT_ARGS(xlogreader
->EndRecPtr
))));
494 GetWALRecordInfo(xlogreader
, values
, nulls
, PG_GET_WAL_RECORD_INFO_COLS
);
496 pfree(xlogreader
->private_data
);
497 XLogReaderFree(xlogreader
);
499 tuple
= heap_form_tuple(tupdesc
, values
, nulls
);
500 result
= HeapTupleGetDatum(tuple
);
502 PG_RETURN_DATUM(result
);
503 #undef PG_GET_WAL_RECORD_INFO_COLS
507 * Validate start and end LSNs coming from the function inputs.
509 * If end_lsn is found to be higher than the current LSN reported by the
510 * cluster, use the current LSN as the upper bound.
513 ValidateInputLSNs(XLogRecPtr start_lsn
, XLogRecPtr
*end_lsn
)
515 XLogRecPtr curr_lsn
= GetCurrentLSN();
517 if (start_lsn
> curr_lsn
)
519 (errcode(ERRCODE_INVALID_PARAMETER_VALUE
),
520 errmsg("WAL start LSN must be less than current LSN"),
521 errdetail("Current WAL LSN on the database system is at %X/%X.",
522 LSN_FORMAT_ARGS(curr_lsn
))));
524 if (start_lsn
> *end_lsn
)
526 (errcode(ERRCODE_INVALID_PARAMETER_VALUE
),
527 errmsg("WAL start LSN must be less than end LSN")));
529 if (*end_lsn
> curr_lsn
)
534 * Get info of all WAL records between start LSN and end LSN.
537 GetWALRecordsInfo(FunctionCallInfo fcinfo
, XLogRecPtr start_lsn
,
540 #define PG_GET_WAL_RECORDS_INFO_COLS 11
541 XLogReaderState
*xlogreader
;
542 ReturnSetInfo
*rsinfo
= (ReturnSetInfo
*) fcinfo
->resultinfo
;
543 MemoryContext old_cxt
;
544 MemoryContext tmp_cxt
;
546 Assert(start_lsn
<= end_lsn
);
548 InitMaterializedSRF(fcinfo
, 0);
550 xlogreader
= InitXLogReaderState(start_lsn
);
552 tmp_cxt
= AllocSetContextCreate(CurrentMemoryContext
,
553 "GetWALRecordsInfo temporary cxt",
554 ALLOCSET_DEFAULT_SIZES
);
556 while (ReadNextXLogRecord(xlogreader
) &&
557 xlogreader
->EndRecPtr
<= end_lsn
)
559 Datum values
[PG_GET_WAL_RECORDS_INFO_COLS
] = {0};
560 bool nulls
[PG_GET_WAL_RECORDS_INFO_COLS
] = {0};
562 /* Use the tmp context so we can clean up after each tuple is done */
563 old_cxt
= MemoryContextSwitchTo(tmp_cxt
);
565 GetWALRecordInfo(xlogreader
, values
, nulls
,
566 PG_GET_WAL_RECORDS_INFO_COLS
);
568 tuplestore_putvalues(rsinfo
->setResult
, rsinfo
->setDesc
,
571 /* clean up and switch back */
572 MemoryContextSwitchTo(old_cxt
);
573 MemoryContextReset(tmp_cxt
);
575 CHECK_FOR_INTERRUPTS();
578 MemoryContextDelete(tmp_cxt
);
579 pfree(xlogreader
->private_data
);
580 XLogReaderFree(xlogreader
);
582 #undef PG_GET_WAL_RECORDS_INFO_COLS
586 * Get info of all WAL records between start LSN and end LSN.
589 pg_get_wal_records_info(PG_FUNCTION_ARGS
)
591 XLogRecPtr start_lsn
= PG_GETARG_LSN(0);
592 XLogRecPtr end_lsn
= PG_GETARG_LSN(1);
594 ValidateInputLSNs(start_lsn
, &end_lsn
);
595 GetWALRecordsInfo(fcinfo
, start_lsn
, end_lsn
);
601 * Fill single row of record counts and sizes for an rmgr or record.
604 FillXLogStatsRow(const char *name
,
605 uint64 n
, uint64 total_count
,
606 uint64 rec_len
, uint64 total_rec_len
,
607 uint64 fpi_len
, uint64 total_fpi_len
,
608 uint64 tot_len
, uint64 total_len
,
609 Datum
*values
, bool *nulls
, uint32 ncols
)
618 if (total_count
!= 0)
619 n_pct
= 100 * (double) n
/ total_count
;
622 if (total_rec_len
!= 0)
623 rec_len_pct
= 100 * (double) rec_len
/ total_rec_len
;
626 if (total_fpi_len
!= 0)
627 fpi_len_pct
= 100 * (double) fpi_len
/ total_fpi_len
;
631 tot_len_pct
= 100 * (double) tot_len
/ total_len
;
633 values
[i
++] = CStringGetTextDatum(name
);
634 values
[i
++] = Int64GetDatum(n
);
635 values
[i
++] = Float8GetDatum(n_pct
);
636 values
[i
++] = Int64GetDatum(rec_len
);
637 values
[i
++] = Float8GetDatum(rec_len_pct
);
638 values
[i
++] = Int64GetDatum(fpi_len
);
639 values
[i
++] = Float8GetDatum(fpi_len_pct
);
640 values
[i
++] = Int64GetDatum(tot_len
);
641 values
[i
++] = Float8GetDatum(tot_len_pct
);
647 * Get summary statistics about the records seen so far.
650 GetXLogSummaryStats(XLogStats
*stats
, ReturnSetInfo
*rsinfo
,
651 Datum
*values
, bool *nulls
, uint32 ncols
,
652 bool stats_per_record
)
654 MemoryContext old_cxt
;
655 MemoryContext tmp_cxt
;
656 uint64 total_count
= 0;
657 uint64 total_rec_len
= 0;
658 uint64 total_fpi_len
= 0;
659 uint64 total_len
= 0;
663 * Each row shows its percentages of the total, so make a first pass to
664 * calculate column totals.
666 for (ri
= 0; ri
<= RM_MAX_ID
; ri
++)
668 if (!RmgrIdIsValid(ri
))
671 total_count
+= stats
->rmgr_stats
[ri
].count
;
672 total_rec_len
+= stats
->rmgr_stats
[ri
].rec_len
;
673 total_fpi_len
+= stats
->rmgr_stats
[ri
].fpi_len
;
675 total_len
= total_rec_len
+ total_fpi_len
;
677 tmp_cxt
= AllocSetContextCreate(CurrentMemoryContext
,
678 "GetXLogSummaryStats temporary cxt",
679 ALLOCSET_DEFAULT_SIZES
);
681 for (ri
= 0; ri
<= RM_MAX_ID
; ri
++)
689 if (!RmgrIdIsValid(ri
))
692 if (!RmgrIdExists(ri
))
697 if (stats_per_record
)
701 for (rj
= 0; rj
< MAX_XLINFO_TYPES
; rj
++)
705 count
= stats
->record_stats
[ri
][rj
].count
;
706 rec_len
= stats
->record_stats
[ri
][rj
].rec_len
;
707 fpi_len
= stats
->record_stats
[ri
][rj
].fpi_len
;
708 tot_len
= rec_len
+ fpi_len
;
710 /* Skip undefined combinations and ones that didn't occur */
714 old_cxt
= MemoryContextSwitchTo(tmp_cxt
);
716 /* the upper four bits in xl_info are the rmgr's */
717 id
= desc
.rm_identify(rj
<< 4);
719 id
= psprintf("UNKNOWN (%x)", rj
<< 4);
721 FillXLogStatsRow(psprintf("%s/%s", desc
.rm_name
, id
), count
,
722 total_count
, rec_len
, total_rec_len
, fpi_len
,
723 total_fpi_len
, tot_len
, total_len
,
724 values
, nulls
, ncols
);
726 tuplestore_putvalues(rsinfo
->setResult
, rsinfo
->setDesc
,
729 /* clean up and switch back */
730 MemoryContextSwitchTo(old_cxt
);
731 MemoryContextReset(tmp_cxt
);
736 count
= stats
->rmgr_stats
[ri
].count
;
737 rec_len
= stats
->rmgr_stats
[ri
].rec_len
;
738 fpi_len
= stats
->rmgr_stats
[ri
].fpi_len
;
739 tot_len
= rec_len
+ fpi_len
;
741 old_cxt
= MemoryContextSwitchTo(tmp_cxt
);
743 FillXLogStatsRow(desc
.rm_name
, count
, total_count
, rec_len
,
744 total_rec_len
, fpi_len
, total_fpi_len
, tot_len
,
745 total_len
, values
, nulls
, ncols
);
747 tuplestore_putvalues(rsinfo
->setResult
, rsinfo
->setDesc
,
750 /* clean up and switch back */
751 MemoryContextSwitchTo(old_cxt
);
752 MemoryContextReset(tmp_cxt
);
756 MemoryContextDelete(tmp_cxt
);
760 * Get WAL stats between start LSN and end LSN.
763 GetWalStats(FunctionCallInfo fcinfo
, XLogRecPtr start_lsn
, XLogRecPtr end_lsn
,
764 bool stats_per_record
)
766 #define PG_GET_WAL_STATS_COLS 9
767 XLogReaderState
*xlogreader
;
768 XLogStats stats
= {0};
769 ReturnSetInfo
*rsinfo
= (ReturnSetInfo
*) fcinfo
->resultinfo
;
770 Datum values
[PG_GET_WAL_STATS_COLS
] = {0};
771 bool nulls
[PG_GET_WAL_STATS_COLS
] = {0};
773 Assert(start_lsn
<= end_lsn
);
775 InitMaterializedSRF(fcinfo
, 0);
777 xlogreader
= InitXLogReaderState(start_lsn
);
779 while (ReadNextXLogRecord(xlogreader
) &&
780 xlogreader
->EndRecPtr
<= end_lsn
)
782 XLogRecStoreStats(&stats
, xlogreader
);
784 CHECK_FOR_INTERRUPTS();
787 pfree(xlogreader
->private_data
);
788 XLogReaderFree(xlogreader
);
790 GetXLogSummaryStats(&stats
, rsinfo
, values
, nulls
,
791 PG_GET_WAL_STATS_COLS
,
794 #undef PG_GET_WAL_STATS_COLS
798 * Get stats of all WAL records between start LSN and end LSN.
801 pg_get_wal_stats(PG_FUNCTION_ARGS
)
803 XLogRecPtr start_lsn
= PG_GETARG_LSN(0);
804 XLogRecPtr end_lsn
= PG_GETARG_LSN(1);
805 bool stats_per_record
= PG_GETARG_BOOL(2);
807 ValidateInputLSNs(start_lsn
, &end_lsn
);
808 GetWalStats(fcinfo
, start_lsn
, end_lsn
, stats_per_record
);
814 * The following functions have been removed in newer versions in 1.1, but
815 * they are kept around for compatibility.
818 pg_get_wal_records_info_till_end_of_wal(PG_FUNCTION_ARGS
)
820 XLogRecPtr start_lsn
= PG_GETARG_LSN(0);
821 XLogRecPtr end_lsn
= GetCurrentLSN();
823 if (start_lsn
> end_lsn
)
825 (errcode(ERRCODE_INVALID_PARAMETER_VALUE
),
826 errmsg("WAL start LSN must be less than current LSN"),
827 errdetail("Current WAL LSN on the database system is at %X/%X.",
828 LSN_FORMAT_ARGS(end_lsn
))));
830 GetWALRecordsInfo(fcinfo
, start_lsn
, end_lsn
);
836 pg_get_wal_stats_till_end_of_wal(PG_FUNCTION_ARGS
)
838 XLogRecPtr start_lsn
= PG_GETARG_LSN(0);
839 XLogRecPtr end_lsn
= GetCurrentLSN();
840 bool stats_per_record
= PG_GETARG_BOOL(1);
842 if (start_lsn
> end_lsn
)
844 (errcode(ERRCODE_INVALID_PARAMETER_VALUE
),
845 errmsg("WAL start LSN must be less than current LSN"),
846 errdetail("Current WAL LSN on the database system is at %X/%X.",
847 LSN_FORMAT_ARGS(end_lsn
))));
849 GetWalStats(fcinfo
, start_lsn
, end_lsn
, stats_per_record
);