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