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