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 : }
|