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