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-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 : :
215 tgl@sss.pgh.pa.us 33 :CBC 8 : PG_MODULE_MAGIC_EXT(
34 : : .name = "pg_walinspect",
35 : : .version = PG_VERSION
36 : : );
37 : :
962 michael@paquier.xyz 38 : 6 : PG_FUNCTION_INFO_V1(pg_get_wal_block_info);
1298 jdavis@postgresql.or 39 : 6 : PG_FUNCTION_INFO_V1(pg_get_wal_record_info);
40 : 9 : PG_FUNCTION_INFO_V1(pg_get_wal_records_info);
41 : 6 : PG_FUNCTION_INFO_V1(pg_get_wal_records_info_till_end_of_wal);
42 : 6 : PG_FUNCTION_INFO_V1(pg_get_wal_stats);
43 : 6 : 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
958 michael@paquier.xyz 73 : 30 : 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 : : */
1298 jdavis@postgresql.or 81 [ + - ]: 30 : if (!RecoveryInProgress())
958 michael@paquier.xyz 82 : 30 : curr_lsn = GetFlushRecPtr(NULL);
83 : : else
958 michael@paquier.xyz 84 :UBC 0 : curr_lsn = GetXLogReplayRecPtr(NULL);
85 : :
958 michael@paquier.xyz 86 [ - + ]:CBC 30 : Assert(!XLogRecPtrIsInvalid(curr_lsn));
87 : :
88 : 30 : return curr_lsn;
89 : : }
90 : :
91 : : /*
92 : : * Initialize WAL reader and identify first valid LSN.
93 : : */
94 : : static XLogReaderState *
1166 jdavis@postgresql.or 95 : 21 : 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 : : */
1298 106 [ + + ]: 21 : if (lsn < XLOG_BLCKSZ)
107 [ + - ]: 4 : 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 *)
1264 tgl@sss.pgh.pa.us 113 : 17 : palloc0(sizeof(ReadLocalXLogPageNoWaitPrivate));
114 : :
1298 jdavis@postgresql.or 115 : 17 : xlogreader = XLogReaderAllocate(wal_segment_size, NULL,
116 : 17 : 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 [ - + ]: 17 : if (xlogreader == NULL)
1298 jdavis@postgresql.or 122 [ # # ]:UBC 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 */
1166 jdavis@postgresql.or 128 :CBC 17 : first_valid_record = XLogFindNextRecord(xlogreader, lsn);
129 : :
130 [ - + ]: 17 : if (XLogRecPtrIsInvalid(first_valid_record))
1298 jdavis@postgresql.or 131 [ # # ]:UBC 0 : ereport(ERROR,
132 : : errmsg("could not find a valid record after %X/%08X",
133 : : LSN_FORMAT_ARGS(lsn)));
134 : :
1298 jdavis@postgresql.or 135 :CBC 17 : 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 *
1166 151 : 34419 : ReadNextXLogRecord(XLogReaderState *xlogreader)
152 : : {
153 : : XLogRecord *record;
154 : : char *errormsg;
155 : :
1298 156 : 34419 : record = XLogReadRecord(xlogreader, &errormsg);
157 : :
158 [ + + ]: 34419 : if (record == NULL)
159 : : {
160 : : ReadLocalXLogPageNoWaitPrivate *private_data;
161 : :
162 : : /* return NULL, if end of WAL is reached */
1276 163 : 10 : private_data = (ReadLocalXLogPageNoWaitPrivate *)
164 : : xlogreader->private_data;
165 : :
166 [ + - ]: 10 : if (private_data->end_of_wal)
167 : 10 : return NULL;
168 : :
1298 jdavis@postgresql.or 169 [ # # ]:UBC 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 : :
1298 jdavis@postgresql.or 181 :CBC 34409 : 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
1166 193 : 34356 : GetWALRecordInfo(XLogReaderState *record, Datum *values,
194 : : bool *nulls, uint32 ncols)
195 : : {
196 : : const char *record_type;
197 : : RmgrData desc;
1264 tgl@sss.pgh.pa.us 198 : 34356 : uint32 fpi_len = 0;
199 : : StringInfoData rec_desc;
200 : : StringInfoData rec_blk_ref;
201 : 34356 : int i = 0;
202 : :
1298 jdavis@postgresql.or 203 : 34356 : desc = GetRmgr(XLogRecGetRmid(record));
942 pg@bowt.ie 204 : 34356 : record_type = desc.rm_identify(XLogRecGetInfo(record));
205 : :
206 [ - + ]: 34356 : if (record_type == NULL)
942 pg@bowt.ie 207 :UBC 0 : record_type = psprintf("UNKNOWN (%x)", XLogRecGetInfo(record) & ~XLR_INFO_MASK);
208 : :
1298 jdavis@postgresql.or 209 :CBC 34356 : initStringInfo(&rec_desc);
210 : 34356 : desc.rm_desc(&rec_desc, record);
211 : :
944 pg@bowt.ie 212 [ + + ]: 34356 : if (XLogRecHasAnyBlockRefs(record))
213 : : {
214 : 34335 : initStringInfo(&rec_blk_ref);
215 : 34335 : XLogRecGetBlockRefInfo(record, false, true, &rec_blk_ref, &fpi_len);
216 : : }
217 : :
1166 jdavis@postgresql.or 218 : 34356 : values[i++] = LSNGetDatum(record->ReadRecPtr);
1298 219 : 34356 : values[i++] = LSNGetDatum(record->EndRecPtr);
220 : 34356 : values[i++] = LSNGetDatum(XLogRecGetPrev(record));
221 : 34356 : values[i++] = TransactionIdGetDatum(XLogRecGetXid(record));
222 : 34356 : values[i++] = CStringGetTextDatum(desc.rm_name);
942 pg@bowt.ie 223 : 34356 : values[i++] = CStringGetTextDatum(record_type);
1298 jdavis@postgresql.or 224 : 34356 : values[i++] = UInt32GetDatum(XLogRecGetTotalLen(record));
945 michael@paquier.xyz 225 : 34356 : values[i++] = UInt32GetDatum(XLogRecGetDataLen(record));
1298 jdavis@postgresql.or 226 : 34356 : values[i++] = UInt32GetDatum(fpi_len);
227 : :
945 michael@paquier.xyz 228 [ + + ]: 34356 : if (rec_desc.len > 0)
229 : 34342 : values[i++] = CStringGetTextDatum(rec_desc.data);
230 : : else
231 : 14 : nulls[i++] = true;
232 : :
233 [ + + ]: 34356 : if (XLogRecHasAnyBlockRefs(record))
234 : 34335 : values[i++] = CStringGetTextDatum(rec_blk_ref.data);
235 : : else
236 : 21 : nulls[i++] = true;
237 : :
1298 jdavis@postgresql.or 238 [ - + ]: 34356 : Assert(i == ncols);
239 : 34356 : }
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
941 pg@bowt.ie 253 : 22 : GetWALBlockInfo(FunctionCallInfo fcinfo, XLogReaderState *record,
254 : : bool show_data)
255 : : {
256 : : #define PG_GET_WAL_BLOCK_INFO_COLS 20
257 : : int block_id;
1008 michael@paquier.xyz 258 : 22 : ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
259 : : RmgrData desc;
260 : : const char *record_type;
261 : : StringInfoData rec_desc;
262 : :
942 pg@bowt.ie 263 [ - + ]: 22 : Assert(XLogRecHasAnyBlockRefs(record));
264 : :
265 : 22 : desc = GetRmgr(XLogRecGetRmid(record));
266 : 22 : record_type = desc.rm_identify(XLogRecGetInfo(record));
267 : :
268 [ - + ]: 22 : if (record_type == NULL)
942 pg@bowt.ie 269 :UBC 0 : record_type = psprintf("UNKNOWN (%x)",
270 : 0 : XLogRecGetInfo(record) & ~XLR_INFO_MASK);
271 : :
942 pg@bowt.ie 272 :CBC 22 : initStringInfo(&rec_desc);
273 : 22 : desc.rm_desc(&rec_desc, record);
274 : :
1008 michael@paquier.xyz 275 [ + + ]: 44 : for (block_id = 0; block_id <= XLogRecMaxBlockId(record); block_id++)
276 : : {
277 : : DecodedBkpBlock *blk;
278 : : BlockNumber blkno;
279 : : RelFileLocator rnode;
280 : : ForkNumber forknum;
962 281 : 22 : Datum values[PG_GET_WAL_BLOCK_INFO_COLS] = {0};
282 : 22 : bool nulls[PG_GET_WAL_BLOCK_INFO_COLS] = {0};
942 pg@bowt.ie 283 : 22 : uint32 block_data_len = 0,
284 : 22 : block_fpi_len = 0;
285 : 22 : ArrayType *block_fpi_info = NULL;
1008 michael@paquier.xyz 286 : 22 : int i = 0;
287 : :
288 [ + - - + ]: 22 : if (!XLogRecHasBlockRef(record, block_id))
1008 michael@paquier.xyz 289 :UBC 0 : continue;
290 : :
962 michael@paquier.xyz 291 :CBC 22 : blk = XLogRecGetBlock(record, block_id);
292 : :
1008 293 : 22 : (void) XLogRecGetBlockTagExtended(record, block_id,
294 : : &rnode, &forknum, &blkno, NULL);
295 : :
296 : : /* Save block_data_len */
942 pg@bowt.ie 297 [ + + ]: 22 : if (blk->has_data)
298 : 21 : block_data_len = blk->data_len;
299 : :
300 [ + + ]: 22 : if (blk->has_image)
301 : : {
302 : : /* Block reference has an FPI, so prepare relevant output */
303 : : int bitcnt;
304 : 1 : int cnt = 0;
305 : : Datum *flags;
306 : :
307 : : /* Save block_fpi_len */
308 : 1 : block_fpi_len = blk->bimg_len;
309 : :
310 : : /* Construct and save block_fpi_info */
311 : 1 : bitcnt = pg_popcount((const char *) &blk->bimg_info,
312 : : sizeof(uint8));
313 : 1 : flags = (Datum *) palloc0(sizeof(Datum) * bitcnt);
314 [ + - ]: 1 : if ((blk->bimg_info & BKPIMAGE_HAS_HOLE) != 0)
315 : 1 : flags[cnt++] = CStringGetTextDatum("HAS_HOLE");
316 [ + - ]: 1 : if (blk->apply_image)
317 : 1 : flags[cnt++] = CStringGetTextDatum("APPLY");
318 [ - + ]: 1 : if ((blk->bimg_info & BKPIMAGE_COMPRESS_PGLZ) != 0)
942 pg@bowt.ie 319 :UBC 0 : flags[cnt++] = CStringGetTextDatum("COMPRESS_PGLZ");
942 pg@bowt.ie 320 [ - + ]:CBC 1 : if ((blk->bimg_info & BKPIMAGE_COMPRESS_LZ4) != 0)
942 pg@bowt.ie 321 :UBC 0 : flags[cnt++] = CStringGetTextDatum("COMPRESS_LZ4");
942 pg@bowt.ie 322 [ - + ]:CBC 1 : if ((blk->bimg_info & BKPIMAGE_COMPRESS_ZSTD) != 0)
942 pg@bowt.ie 323 :UBC 0 : flags[cnt++] = CStringGetTextDatum("COMPRESS_ZSTD");
324 : :
942 pg@bowt.ie 325 [ - + ]:CBC 1 : Assert(cnt <= bitcnt);
326 : 1 : block_fpi_info = construct_array_builtin(flags, cnt, TEXTOID);
327 : : }
328 : :
329 : : /* start_lsn, end_lsn, prev_lsn, and blockid outputs */
1008 michael@paquier.xyz 330 : 22 : values[i++] = LSNGetDatum(record->ReadRecPtr);
942 pg@bowt.ie 331 : 22 : values[i++] = LSNGetDatum(record->EndRecPtr);
332 : 22 : values[i++] = LSNGetDatum(XLogRecGetPrev(record));
962 michael@paquier.xyz 333 : 22 : values[i++] = Int16GetDatum(block_id);
334 : :
335 : : /* relfile and block related outputs */
336 : 22 : values[i++] = ObjectIdGetDatum(blk->rlocator.spcOid);
337 : 22 : values[i++] = ObjectIdGetDatum(blk->rlocator.dbOid);
338 : 22 : values[i++] = ObjectIdGetDatum(blk->rlocator.relNumber);
942 pg@bowt.ie 339 : 22 : values[i++] = Int16GetDatum(forknum);
962 michael@paquier.xyz 340 : 22 : values[i++] = Int64GetDatum((int64) blkno);
341 : :
342 : : /* xid, resource_manager, and record_type outputs */
942 pg@bowt.ie 343 : 22 : values[i++] = TransactionIdGetDatum(XLogRecGetXid(record));
344 : 22 : values[i++] = CStringGetTextDatum(desc.rm_name);
345 : 22 : values[i++] = CStringGetTextDatum(record_type);
346 : :
347 : : /*
348 : : * record_length, main_data_length, block_data_len, and
349 : : * block_fpi_length outputs
350 : : */
351 : 22 : values[i++] = UInt32GetDatum(XLogRecGetTotalLen(record));
352 : 22 : values[i++] = UInt32GetDatum(XLogRecGetDataLen(record));
353 : 22 : values[i++] = UInt32GetDatum(block_data_len);
354 : 22 : values[i++] = UInt32GetDatum(block_fpi_len);
355 : :
356 : : /* block_fpi_info (text array) output */
357 [ + + ]: 22 : if (block_fpi_info)
358 : 1 : values[i++] = PointerGetDatum(block_fpi_info);
359 : : else
360 : 21 : nulls[i++] = true;
361 : :
362 : : /* description output (describes WAL record) */
363 [ + + ]: 22 : if (rec_desc.len > 0)
364 : 21 : values[i++] = CStringGetTextDatum(rec_desc.data);
365 : : else
366 : 1 : nulls[i++] = true;
367 : :
368 : : /* block_data output */
941 369 [ + + + - ]: 22 : if (blk->has_data && show_data)
962 michael@paquier.xyz 370 : 21 : {
371 : : bytea *block_data;
372 : :
942 pg@bowt.ie 373 : 21 : block_data = (bytea *) palloc(block_data_len + VARHDRSZ);
374 : 21 : SET_VARSIZE(block_data, block_data_len + VARHDRSZ);
375 : 21 : memcpy(VARDATA(block_data), blk->data, block_data_len);
376 : 21 : values[i++] = PointerGetDatum(block_data);
377 : : }
378 : : else
962 michael@paquier.xyz 379 : 1 : nulls[i++] = true;
380 : :
381 : : /* block_fpi_data output */
941 pg@bowt.ie 382 [ + + + - ]: 22 : if (blk->has_image && show_data)
962 michael@paquier.xyz 383 : 1 : {
384 : : PGAlignedBlock buf;
385 : : Page page;
386 : : bytea *block_fpi_data;
387 : :
388 : 1 : page = (Page) buf.data;
389 [ - + ]: 1 : if (!RestoreBlockImage(record, block_id, page))
962 michael@paquier.xyz 390 [ # # ]:UBC 0 : ereport(ERROR,
391 : : (errcode(ERRCODE_INTERNAL_ERROR),
392 : : errmsg_internal("%s", record->errormsg_buf)));
393 : :
942 pg@bowt.ie 394 :CBC 1 : block_fpi_data = (bytea *) palloc(BLCKSZ + VARHDRSZ);
395 : 1 : SET_VARSIZE(block_fpi_data, BLCKSZ + VARHDRSZ);
396 : 1 : memcpy(VARDATA(block_fpi_data), page, BLCKSZ);
397 : 1 : values[i++] = PointerGetDatum(block_fpi_data);
398 : : }
399 : : else
400 : 21 : nulls[i++] = true;
401 : :
962 michael@paquier.xyz 402 [ - + ]: 22 : Assert(i == PG_GET_WAL_BLOCK_INFO_COLS);
403 : :
404 : : /* Store a tuple for this block reference */
1008 405 : 22 : tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
406 : : values, nulls);
407 : : }
408 : :
409 : : #undef PG_GET_WAL_BLOCK_INFO_COLS
410 : 22 : }
411 : :
412 : : /*
413 : : * Get WAL record info, unnested by block reference
414 : : */
415 : : Datum
962 416 : 7 : pg_get_wal_block_info(PG_FUNCTION_ARGS)
417 : : {
958 418 : 7 : XLogRecPtr start_lsn = PG_GETARG_LSN(0);
419 : 7 : XLogRecPtr end_lsn = PG_GETARG_LSN(1);
941 pg@bowt.ie 420 : 7 : bool show_data = PG_GETARG_BOOL(2);
421 : : XLogReaderState *xlogreader;
422 : : MemoryContext old_cxt;
423 : : MemoryContext tmp_cxt;
424 : :
958 michael@paquier.xyz 425 : 7 : ValidateInputLSNs(start_lsn, &end_lsn);
426 : :
1008 427 : 5 : InitMaterializedSRF(fcinfo, 0);
428 : :
429 : 5 : xlogreader = InitXLogReaderState(start_lsn);
430 : :
980 jdavis@postgresql.or 431 : 4 : tmp_cxt = AllocSetContextCreate(CurrentMemoryContext,
432 : : "pg_get_wal_block_info temporary cxt",
433 : : ALLOCSET_DEFAULT_SIZES);
434 : :
1008 michael@paquier.xyz 435 [ + + ]: 34 : while (ReadNextXLogRecord(xlogreader) &&
436 [ + + ]: 31 : xlogreader->EndRecPtr <= end_lsn)
437 : : {
945 438 [ - + ]: 30 : CHECK_FOR_INTERRUPTS();
439 : :
440 [ + + ]: 30 : if (!XLogRecHasAnyBlockRefs(xlogreader))
441 : 8 : continue;
442 : :
443 : : /* Use the tmp context so we can clean up after each tuple is done */
980 jdavis@postgresql.or 444 : 22 : old_cxt = MemoryContextSwitchTo(tmp_cxt);
445 : :
941 pg@bowt.ie 446 : 22 : GetWALBlockInfo(fcinfo, xlogreader, show_data);
447 : :
448 : : /* clean up and switch back */
980 jdavis@postgresql.or 449 : 22 : MemoryContextSwitchTo(old_cxt);
450 : 22 : MemoryContextReset(tmp_cxt);
451 : : }
452 : :
453 : 4 : MemoryContextDelete(tmp_cxt);
1008 michael@paquier.xyz 454 : 4 : pfree(xlogreader->private_data);
455 : 4 : XLogReaderFree(xlogreader);
456 : :
457 : 4 : PG_RETURN_VOID();
458 : : }
459 : :
460 : : /*
461 : : * Get WAL record info.
462 : : */
463 : : Datum
1298 jdavis@postgresql.or 464 : 4 : pg_get_wal_record_info(PG_FUNCTION_ARGS)
465 : : {
466 : : #define PG_GET_WAL_RECORD_INFO_COLS 11
467 : : Datum result;
1199 peter@eisentraut.org 468 : 4 : Datum values[PG_GET_WAL_RECORD_INFO_COLS] = {0};
469 : 4 : 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 : :
1298 jdavis@postgresql.or 476 : 4 : lsn = PG_GETARG_LSN(0);
958 michael@paquier.xyz 477 : 4 : curr_lsn = GetCurrentLSN();
478 : :
479 [ + + ]: 4 : if (lsn > curr_lsn)
1298 jdavis@postgresql.or 480 [ + - ]: 1 : 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 [ - + ]: 3 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
1298 jdavis@postgresql.or 488 [ # # ]:UBC 0 : elog(ERROR, "return type must be a row type");
489 : :
1166 jdavis@postgresql.or 490 :CBC 3 : xlogreader = InitXLogReaderState(lsn);
491 : :
492 [ - + ]: 2 : if (!ReadNextXLogRecord(xlogreader))
1276 jdavis@postgresql.or 493 [ # # ]:UBC 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 : :
1166 jdavis@postgresql.or 498 :CBC 2 : GetWALRecordInfo(xlogreader, values, nulls, PG_GET_WAL_RECORD_INFO_COLS);
499 : :
1276 500 : 2 : pfree(xlogreader->private_data);
1298 501 : 2 : XLogReaderFree(xlogreader);
502 : :
503 : 2 : tuple = heap_form_tuple(tupdesc, values, nulls);
504 : 2 : result = HeapTupleGetDatum(tuple);
505 : :
506 : 2 : 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
958 michael@paquier.xyz 517 : 22 : ValidateInputLSNs(XLogRecPtr start_lsn, XLogRecPtr *end_lsn)
518 : : {
519 : 22 : XLogRecPtr curr_lsn = GetCurrentLSN();
520 : :
521 [ + + ]: 22 : if (start_lsn > curr_lsn)
1298 jdavis@postgresql.or 522 [ + - ]: 3 : 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 : :
958 michael@paquier.xyz 528 [ + + ]: 19 : if (start_lsn > *end_lsn)
1298 jdavis@postgresql.or 529 [ + - ]: 3 : ereport(ERROR,
530 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
531 : : errmsg("WAL start LSN must be less than end LSN")));
532 : :
958 michael@paquier.xyz 533 [ + + ]: 16 : if (*end_lsn > curr_lsn)
534 : 4 : *end_lsn = curr_lsn;
1298 jdavis@postgresql.or 535 : 16 : }
536 : :
537 : : /*
538 : : * Get info of all WAL records between start LSN and end LSN.
539 : : */
540 : : static void
541 : 9 : GetWALRecordsInfo(FunctionCallInfo fcinfo, XLogRecPtr start_lsn,
542 : : XLogRecPtr end_lsn)
543 : : {
544 : : #define PG_GET_WAL_RECORDS_INFO_COLS 11
545 : : XLogReaderState *xlogreader;
546 : 9 : ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
547 : : MemoryContext old_cxt;
548 : : MemoryContext tmp_cxt;
549 : :
958 michael@paquier.xyz 550 [ - + ]: 9 : Assert(start_lsn <= end_lsn);
551 : :
1105 552 : 9 : InitMaterializedSRF(fcinfo, 0);
553 : :
1166 jdavis@postgresql.or 554 : 9 : xlogreader = InitXLogReaderState(start_lsn);
555 : :
980 556 : 8 : tmp_cxt = AllocSetContextCreate(CurrentMemoryContext,
557 : : "GetWALRecordsInfo temporary cxt",
558 : : ALLOCSET_DEFAULT_SIZES);
559 : :
1166 560 [ + + ]: 34362 : while (ReadNextXLogRecord(xlogreader) &&
1276 561 [ + + ]: 34357 : xlogreader->EndRecPtr <= end_lsn)
562 : : {
945 michael@paquier.xyz 563 : 34354 : Datum values[PG_GET_WAL_RECORDS_INFO_COLS] = {0};
564 : 34354 : 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 */
980 jdavis@postgresql.or 567 : 34354 : old_cxt = MemoryContextSwitchTo(tmp_cxt);
568 : :
1166 569 : 34354 : GetWALRecordInfo(xlogreader, values, nulls,
570 : : PG_GET_WAL_RECORDS_INFO_COLS);
571 : :
1276 572 : 34354 : tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
573 : : values, nulls);
574 : :
575 : : /* clean up and switch back */
980 576 : 34354 : MemoryContextSwitchTo(old_cxt);
577 : 34354 : MemoryContextReset(tmp_cxt);
578 : :
1298 579 [ - + ]: 34354 : CHECK_FOR_INTERRUPTS();
580 : : }
581 : :
980 582 : 8 : MemoryContextDelete(tmp_cxt);
1276 583 : 8 : pfree(xlogreader->private_data);
1298 584 : 8 : XLogReaderFree(xlogreader);
585 : :
586 : : #undef PG_GET_WAL_RECORDS_INFO_COLS
587 : 8 : }
588 : :
589 : : /*
590 : : * Get info of all WAL records between start LSN and end LSN.
591 : : */
592 : : Datum
593 : 10 : pg_get_wal_records_info(PG_FUNCTION_ARGS)
594 : : {
958 michael@paquier.xyz 595 : 10 : XLogRecPtr start_lsn = PG_GETARG_LSN(0);
596 : 10 : XLogRecPtr end_lsn = PG_GETARG_LSN(1);
597 : :
598 : 10 : ValidateInputLSNs(start_lsn, &end_lsn);
1298 jdavis@postgresql.or 599 : 8 : GetWALRecordsInfo(fcinfo, start_lsn, end_lsn);
600 : :
601 : 7 : 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 : 66 : 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;
1264 tgl@sss.pgh.pa.us 619 : 66 : int i = 0;
620 : :
1298 jdavis@postgresql.or 621 : 66 : n_pct = 0;
622 [ + - ]: 66 : if (total_count != 0)
623 : 66 : n_pct = 100 * (double) n / total_count;
624 : :
625 : 66 : rec_len_pct = 0;
626 [ + - ]: 66 : if (total_rec_len != 0)
627 : 66 : rec_len_pct = 100 * (double) rec_len / total_rec_len;
628 : :
629 : 66 : fpi_len_pct = 0;
630 [ - + ]: 66 : if (total_fpi_len != 0)
1298 jdavis@postgresql.or 631 :UBC 0 : fpi_len_pct = 100 * (double) fpi_len / total_fpi_len;
632 : :
1298 jdavis@postgresql.or 633 :CBC 66 : tot_len_pct = 0;
634 [ + - ]: 66 : if (total_len != 0)
635 : 66 : tot_len_pct = 100 * (double) tot_len / total_len;
636 : :
637 : 66 : values[i++] = CStringGetTextDatum(name);
638 : 66 : values[i++] = Int64GetDatum(n);
1141 peter@eisentraut.org 639 : 66 : values[i++] = Float8GetDatum(n_pct);
1298 jdavis@postgresql.or 640 : 66 : values[i++] = Int64GetDatum(rec_len);
1141 peter@eisentraut.org 641 : 66 : values[i++] = Float8GetDatum(rec_len_pct);
1298 jdavis@postgresql.or 642 : 66 : values[i++] = Int64GetDatum(fpi_len);
1141 peter@eisentraut.org 643 : 66 : values[i++] = Float8GetDatum(fpi_len_pct);
1298 jdavis@postgresql.or 644 : 66 : values[i++] = Int64GetDatum(tot_len);
1141 peter@eisentraut.org 645 : 66 : values[i++] = Float8GetDatum(tot_len_pct);
646 : :
1298 jdavis@postgresql.or 647 [ - + ]: 66 : Assert(i == ncols);
648 : 66 : }
649 : :
650 : : /*
651 : : * Get summary statistics about the records seen so far.
652 : : */
653 : : static void
654 : 3 : 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;
958 michael@paquier.xyz 660 : 3 : uint64 total_count = 0;
661 : 3 : uint64 total_rec_len = 0;
662 : 3 : uint64 total_fpi_len = 0;
663 : 3 : 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 : : */
1298 jdavis@postgresql.or 670 [ + + ]: 771 : for (ri = 0; ri <= RM_MAX_ID; ri++)
671 : : {
672 [ + + + + ]: 768 : if (!RmgrIdIsValid(ri))
673 : 318 : continue;
674 : :
675 : 450 : total_count += stats->rmgr_stats[ri].count;
676 : 450 : total_rec_len += stats->rmgr_stats[ri].rec_len;
677 : 450 : total_fpi_len += stats->rmgr_stats[ri].fpi_len;
678 : : }
679 : 3 : total_len = total_rec_len + total_fpi_len;
680 : :
980 681 : 3 : tmp_cxt = AllocSetContextCreate(CurrentMemoryContext,
682 : : "GetXLogSummaryStats temporary cxt",
683 : : ALLOCSET_DEFAULT_SIZES);
684 : :
1298 685 [ + + ]: 771 : 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 [ + + + + ]: 768 : if (!RmgrIdIsValid(ri))
694 : 702 : continue;
695 : :
696 [ + + ]: 450 : if (!RmgrIdExists(ri))
697 : 384 : continue;
698 : :
699 : 66 : desc = GetRmgr(ri);
700 : :
701 [ - + ]: 66 : if (stats_per_record)
702 : : {
703 : : int rj;
704 : :
1298 jdavis@postgresql.or 705 [ # # ]:UBC 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 : :
980 718 : 0 : old_cxt = MemoryContextSwitchTo(tmp_cxt);
719 : :
720 : : /* the upper four bits in xl_info are the rmgr's */
1298 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 */
980 734 : 0 : MemoryContextSwitchTo(old_cxt);
735 : 0 : MemoryContextReset(tmp_cxt);
736 : : }
737 : : }
738 : : else
739 : : {
1298 jdavis@postgresql.or 740 :CBC 66 : count = stats->rmgr_stats[ri].count;
741 : 66 : rec_len = stats->rmgr_stats[ri].rec_len;
742 : 66 : fpi_len = stats->rmgr_stats[ri].fpi_len;
743 : 66 : tot_len = rec_len + fpi_len;
744 : :
980 745 : 66 : old_cxt = MemoryContextSwitchTo(tmp_cxt);
746 : :
1298 747 : 66 : 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 : 66 : tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
752 : : values, nulls);
753 : :
754 : : /* clean up and switch back */
980 755 : 66 : MemoryContextSwitchTo(old_cxt);
756 : 66 : MemoryContextReset(tmp_cxt);
757 : : }
758 : : }
759 : :
760 : 3 : MemoryContextDelete(tmp_cxt);
1298 761 : 3 : }
762 : :
763 : : /*
764 : : * Get WAL stats between start LSN and end LSN.
765 : : */
766 : : static void
958 michael@paquier.xyz 767 : 4 : 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;
1199 peter@eisentraut.org 772 : 4 : XLogStats stats = {0};
1298 jdavis@postgresql.or 773 : 4 : ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
1199 peter@eisentraut.org 774 : 4 : Datum values[PG_GET_WAL_STATS_COLS] = {0};
775 : 4 : bool nulls[PG_GET_WAL_STATS_COLS] = {0};
776 : :
958 michael@paquier.xyz 777 [ - + ]: 4 : Assert(start_lsn <= end_lsn);
778 : :
1105 779 : 4 : InitMaterializedSRF(fcinfo, 0);
780 : :
1166 jdavis@postgresql.or 781 : 4 : xlogreader = InitXLogReaderState(start_lsn);
782 : :
783 [ + + ]: 24 : while (ReadNextXLogRecord(xlogreader) &&
1276 784 [ + + ]: 19 : xlogreader->EndRecPtr <= end_lsn)
785 : : {
786 : 18 : XLogRecStoreStats(&stats, xlogreader);
787 : :
1298 788 [ - + ]: 18 : CHECK_FOR_INTERRUPTS();
789 : : }
790 : :
1276 791 : 3 : pfree(xlogreader->private_data);
1298 792 : 3 : XLogReaderFree(xlogreader);
793 : :
794 : 3 : GetXLogSummaryStats(&stats, rsinfo, values, nulls,
795 : : PG_GET_WAL_STATS_COLS,
796 : : stats_per_record);
797 : :
798 : : #undef PG_GET_WAL_STATS_COLS
799 : 3 : }
800 : :
801 : : /*
802 : : * Get stats of all WAL records between start LSN and end LSN.
803 : : */
804 : : Datum
805 : 5 : pg_get_wal_stats(PG_FUNCTION_ARGS)
806 : : {
958 michael@paquier.xyz 807 : 5 : XLogRecPtr start_lsn = PG_GETARG_LSN(0);
808 : 5 : XLogRecPtr end_lsn = PG_GETARG_LSN(1);
809 : 5 : bool stats_per_record = PG_GETARG_BOOL(2);
810 : :
811 : 5 : ValidateInputLSNs(start_lsn, &end_lsn);
1298 jdavis@postgresql.or 812 : 3 : GetWalStats(fcinfo, start_lsn, end_lsn, stats_per_record);
813 : :
814 : 2 : 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
958 michael@paquier.xyz 822 : 2 : pg_get_wal_records_info_till_end_of_wal(PG_FUNCTION_ARGS)
823 : : {
824 : 2 : XLogRecPtr start_lsn = PG_GETARG_LSN(0);
825 : 2 : XLogRecPtr end_lsn = GetCurrentLSN();
826 : :
827 [ + + ]: 2 : if (start_lsn > end_lsn)
828 [ + - ]: 1 : 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 : 1 : GetWALRecordsInfo(fcinfo, start_lsn, end_lsn);
835 : :
836 : 1 : PG_RETURN_VOID();
837 : : }
838 : :
839 : : Datum
840 : 2 : pg_get_wal_stats_till_end_of_wal(PG_FUNCTION_ARGS)
841 : : {
842 : 2 : XLogRecPtr start_lsn = PG_GETARG_LSN(0);
843 : 2 : XLogRecPtr end_lsn = GetCurrentLSN();
844 : 2 : bool stats_per_record = PG_GETARG_BOOL(1);
845 : :
846 [ + + ]: 2 : if (start_lsn > end_lsn)
847 [ + - ]: 1 : 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 : :
1298 jdavis@postgresql.or 853 : 1 : GetWalStats(fcinfo, start_lsn, end_lsn, stats_per_record);
854 : :
855 : 1 : PG_RETURN_VOID();
856 : : }
|