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