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