/src/postgres/src/backend/access/rmgrdesc/xlogdesc.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * xlogdesc.c |
4 | | * rmgr descriptor routines for access/transam/xlog.c |
5 | | * |
6 | | * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group |
7 | | * Portions Copyright (c) 1994, Regents of the University of California |
8 | | * |
9 | | * |
10 | | * IDENTIFICATION |
11 | | * src/backend/access/rmgrdesc/xlogdesc.c |
12 | | * |
13 | | *------------------------------------------------------------------------- |
14 | | */ |
15 | | #include "postgres.h" |
16 | | |
17 | | #include "access/transam.h" |
18 | | #include "access/xlog.h" |
19 | | #include "access/xlog_internal.h" |
20 | | #include "catalog/pg_control.h" |
21 | | #include "storage/checksum.h" |
22 | | #include "utils/guc.h" |
23 | | #include "utils/timestamp.h" |
24 | | |
25 | | /* |
26 | | * GUC support |
27 | | */ |
28 | | const struct config_enum_entry wal_level_options[] = { |
29 | | {"minimal", WAL_LEVEL_MINIMAL, false}, |
30 | | {"replica", WAL_LEVEL_REPLICA, false}, |
31 | | {"archive", WAL_LEVEL_REPLICA, true}, /* deprecated */ |
32 | | {"hot_standby", WAL_LEVEL_REPLICA, true}, /* deprecated */ |
33 | | {"logical", WAL_LEVEL_LOGICAL, false}, |
34 | | {NULL, 0, false} |
35 | | }; |
36 | | |
37 | | /* |
38 | | * Find a string representation for wal_level |
39 | | */ |
40 | | static const char * |
41 | | get_wal_level_string(int wal_level) |
42 | 0 | { |
43 | 0 | const struct config_enum_entry *entry; |
44 | 0 | const char *wal_level_str = "?"; |
45 | |
|
46 | 0 | for (entry = wal_level_options; entry->name; entry++) |
47 | 0 | { |
48 | 0 | if (entry->val == wal_level) |
49 | 0 | { |
50 | 0 | wal_level_str = entry->name; |
51 | 0 | break; |
52 | 0 | } |
53 | 0 | } |
54 | |
|
55 | 0 | return wal_level_str; |
56 | 0 | } |
57 | | |
58 | | const char * |
59 | | get_checksum_state_string(uint32 state) |
60 | 0 | { |
61 | 0 | switch (state) |
62 | 0 | { |
63 | 0 | case PG_DATA_CHECKSUM_VERSION: |
64 | 0 | return "on"; |
65 | 0 | case PG_DATA_CHECKSUM_INPROGRESS_OFF: |
66 | 0 | return "inprogress-off"; |
67 | 0 | case PG_DATA_CHECKSUM_INPROGRESS_ON: |
68 | 0 | return "inprogress-on"; |
69 | 0 | case PG_DATA_CHECKSUM_OFF: |
70 | 0 | return "off"; |
71 | 0 | } |
72 | | |
73 | 0 | Assert(false); |
74 | 0 | return "?"; |
75 | 0 | } |
76 | | |
77 | | void |
78 | | xlog2_desc(StringInfo buf, XLogReaderState *record) |
79 | 0 | { |
80 | 0 | char *rec = XLogRecGetData(record); |
81 | 0 | uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; |
82 | |
|
83 | 0 | if (info == XLOG2_CHECKSUMS) |
84 | 0 | { |
85 | 0 | xl_checksum_state xlrec; |
86 | |
|
87 | 0 | memcpy(&xlrec, rec, sizeof(xl_checksum_state)); |
88 | 0 | appendStringInfoString(buf, get_checksum_state_string(xlrec.new_checksum_state)); |
89 | 0 | } |
90 | 0 | } |
91 | | |
92 | | void |
93 | | xlog_desc(StringInfo buf, XLogReaderState *record) |
94 | 0 | { |
95 | 0 | char *rec = XLogRecGetData(record); |
96 | 0 | uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; |
97 | |
|
98 | 0 | if (info == XLOG_CHECKPOINT_SHUTDOWN || |
99 | 0 | info == XLOG_CHECKPOINT_ONLINE) |
100 | 0 | { |
101 | 0 | CheckPoint *checkpoint = (CheckPoint *) rec; |
102 | |
|
103 | 0 | appendStringInfo(buf, "redo %X/%08X; " |
104 | 0 | "tli %u; prev tli %u; fpw %s; wal_level %s; logical decoding %s; xid %u:%u; oid %u; multi %u; offset %" PRIu64 "; " |
105 | 0 | "oldest xid %u in DB %u; oldest multi %u in DB %u; " |
106 | 0 | "oldest/newest commit timestamp xid: %u/%u; " |
107 | 0 | "oldest running xid %u; " |
108 | 0 | "checksums %s; %s", |
109 | 0 | LSN_FORMAT_ARGS(checkpoint->redo), |
110 | 0 | checkpoint->ThisTimeLineID, |
111 | 0 | checkpoint->PrevTimeLineID, |
112 | 0 | checkpoint->fullPageWrites ? "true" : "false", |
113 | 0 | get_wal_level_string(checkpoint->wal_level), |
114 | 0 | checkpoint->logicalDecodingEnabled ? "true" : "false", |
115 | 0 | EpochFromFullTransactionId(checkpoint->nextXid), |
116 | 0 | XidFromFullTransactionId(checkpoint->nextXid), |
117 | 0 | checkpoint->nextOid, |
118 | 0 | checkpoint->nextMulti, |
119 | 0 | checkpoint->nextMultiOffset, |
120 | 0 | checkpoint->oldestXid, |
121 | 0 | checkpoint->oldestXidDB, |
122 | 0 | checkpoint->oldestMulti, |
123 | 0 | checkpoint->oldestMultiDB, |
124 | 0 | checkpoint->oldestCommitTsXid, |
125 | 0 | checkpoint->newestCommitTsXid, |
126 | 0 | checkpoint->oldestActiveXid, |
127 | 0 | get_checksum_state_string(checkpoint->dataChecksumState), |
128 | 0 | (info == XLOG_CHECKPOINT_SHUTDOWN) ? "shutdown" : "online"); |
129 | 0 | } |
130 | 0 | else if (info == XLOG_NEXTOID) |
131 | 0 | { |
132 | 0 | Oid nextOid; |
133 | |
|
134 | 0 | memcpy(&nextOid, rec, sizeof(Oid)); |
135 | 0 | appendStringInfo(buf, "%u", nextOid); |
136 | 0 | } |
137 | 0 | else if (info == XLOG_RESTORE_POINT) |
138 | 0 | { |
139 | 0 | xl_restore_point *xlrec = (xl_restore_point *) rec; |
140 | |
|
141 | 0 | appendStringInfoString(buf, xlrec->rp_name); |
142 | 0 | } |
143 | 0 | else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) |
144 | 0 | { |
145 | | /* no further information to print */ |
146 | 0 | } |
147 | 0 | else if (info == XLOG_BACKUP_END) |
148 | 0 | { |
149 | 0 | XLogRecPtr startpoint; |
150 | |
|
151 | 0 | memcpy(&startpoint, rec, sizeof(XLogRecPtr)); |
152 | 0 | appendStringInfo(buf, "%X/%08X", LSN_FORMAT_ARGS(startpoint)); |
153 | 0 | } |
154 | 0 | else if (info == XLOG_PARAMETER_CHANGE) |
155 | 0 | { |
156 | 0 | xl_parameter_change xlrec; |
157 | 0 | const char *wal_level_str; |
158 | |
|
159 | 0 | memcpy(&xlrec, rec, sizeof(xl_parameter_change)); |
160 | 0 | wal_level_str = get_wal_level_string(xlrec.wal_level); |
161 | |
|
162 | 0 | appendStringInfo(buf, "max_connections=%d max_worker_processes=%d " |
163 | 0 | "max_wal_senders=%d max_prepared_xacts=%d " |
164 | 0 | "max_locks_per_xact=%d wal_level=%s " |
165 | 0 | "wal_log_hints=%s track_commit_timestamp=%s", |
166 | 0 | xlrec.MaxConnections, |
167 | 0 | xlrec.max_worker_processes, |
168 | 0 | xlrec.max_wal_senders, |
169 | 0 | xlrec.max_prepared_xacts, |
170 | 0 | xlrec.max_locks_per_xact, |
171 | 0 | wal_level_str, |
172 | 0 | xlrec.wal_log_hints ? "on" : "off", |
173 | 0 | xlrec.track_commit_timestamp ? "on" : "off"); |
174 | 0 | } |
175 | 0 | else if (info == XLOG_FPW_CHANGE) |
176 | 0 | { |
177 | 0 | bool fpw; |
178 | |
|
179 | 0 | memcpy(&fpw, rec, sizeof(bool)); |
180 | 0 | appendStringInfoString(buf, fpw ? "true" : "false"); |
181 | 0 | } |
182 | 0 | else if (info == XLOG_END_OF_RECOVERY) |
183 | 0 | { |
184 | 0 | xl_end_of_recovery xlrec; |
185 | |
|
186 | 0 | memcpy(&xlrec, rec, sizeof(xl_end_of_recovery)); |
187 | 0 | appendStringInfo(buf, "tli %u; prev tli %u; time %s; wal_level %s", |
188 | 0 | xlrec.ThisTimeLineID, xlrec.PrevTimeLineID, |
189 | 0 | timestamptz_to_str(xlrec.end_time), |
190 | 0 | get_wal_level_string(xlrec.wal_level)); |
191 | 0 | } |
192 | 0 | else if (info == XLOG_OVERWRITE_CONTRECORD) |
193 | 0 | { |
194 | 0 | xl_overwrite_contrecord xlrec; |
195 | |
|
196 | 0 | memcpy(&xlrec, rec, sizeof(xl_overwrite_contrecord)); |
197 | 0 | appendStringInfo(buf, "lsn %X/%08X; time %s", |
198 | 0 | LSN_FORMAT_ARGS(xlrec.overwritten_lsn), |
199 | 0 | timestamptz_to_str(xlrec.overwrite_time)); |
200 | 0 | } |
201 | 0 | else if (info == XLOG_CHECKPOINT_REDO) |
202 | 0 | { |
203 | 0 | xl_checkpoint_redo xlrec; |
204 | |
|
205 | 0 | memcpy(&xlrec, rec, sizeof(xl_checkpoint_redo)); |
206 | 0 | appendStringInfo(buf, "wal_level %s; checksums %s", |
207 | 0 | get_wal_level_string(xlrec.wal_level), |
208 | 0 | get_checksum_state_string(xlrec.data_checksum_version)); |
209 | 0 | } |
210 | 0 | else if (info == XLOG_LOGICAL_DECODING_STATUS_CHANGE) |
211 | 0 | { |
212 | 0 | bool enabled; |
213 | |
|
214 | 0 | memcpy(&enabled, rec, sizeof(bool)); |
215 | 0 | appendStringInfoString(buf, enabled ? "true" : "false"); |
216 | 0 | } |
217 | 0 | else if (info == XLOG_ASSIGN_LSN) |
218 | 0 | { |
219 | | /* no further information to print */ |
220 | 0 | } |
221 | 0 | } |
222 | | |
223 | | const char * |
224 | | xlog_identify(uint8 info) |
225 | 0 | { |
226 | 0 | const char *id = NULL; |
227 | |
|
228 | 0 | switch (info & ~XLR_INFO_MASK) |
229 | 0 | { |
230 | 0 | case XLOG_CHECKPOINT_SHUTDOWN: |
231 | 0 | id = "CHECKPOINT_SHUTDOWN"; |
232 | 0 | break; |
233 | 0 | case XLOG_CHECKPOINT_ONLINE: |
234 | 0 | id = "CHECKPOINT_ONLINE"; |
235 | 0 | break; |
236 | 0 | case XLOG_NOOP: |
237 | 0 | id = "NOOP"; |
238 | 0 | break; |
239 | 0 | case XLOG_NEXTOID: |
240 | 0 | id = "NEXTOID"; |
241 | 0 | break; |
242 | 0 | case XLOG_SWITCH: |
243 | 0 | id = "SWITCH"; |
244 | 0 | break; |
245 | 0 | case XLOG_BACKUP_END: |
246 | 0 | id = "BACKUP_END"; |
247 | 0 | break; |
248 | 0 | case XLOG_PARAMETER_CHANGE: |
249 | 0 | id = "PARAMETER_CHANGE"; |
250 | 0 | break; |
251 | 0 | case XLOG_RESTORE_POINT: |
252 | 0 | id = "RESTORE_POINT"; |
253 | 0 | break; |
254 | 0 | case XLOG_FPW_CHANGE: |
255 | 0 | id = "FPW_CHANGE"; |
256 | 0 | break; |
257 | 0 | case XLOG_END_OF_RECOVERY: |
258 | 0 | id = "END_OF_RECOVERY"; |
259 | 0 | break; |
260 | 0 | case XLOG_OVERWRITE_CONTRECORD: |
261 | 0 | id = "OVERWRITE_CONTRECORD"; |
262 | 0 | break; |
263 | 0 | case XLOG_FPI: |
264 | 0 | id = "FPI"; |
265 | 0 | break; |
266 | 0 | case XLOG_FPI_FOR_HINT: |
267 | 0 | id = "FPI_FOR_HINT"; |
268 | 0 | break; |
269 | 0 | case XLOG_CHECKPOINT_REDO: |
270 | 0 | id = "CHECKPOINT_REDO"; |
271 | 0 | break; |
272 | 0 | case XLOG_LOGICAL_DECODING_STATUS_CHANGE: |
273 | 0 | id = "LOGICAL_DECODING_STATUS_CHANGE"; |
274 | 0 | break; |
275 | 0 | case XLOG_ASSIGN_LSN: |
276 | 0 | id = "ASSIGN_LSN"; |
277 | 0 | break; |
278 | 0 | } |
279 | | |
280 | 0 | return id; |
281 | 0 | } |
282 | | |
283 | | const char * |
284 | | xlog2_identify(uint8 info) |
285 | 0 | { |
286 | 0 | const char *id = NULL; |
287 | |
|
288 | 0 | switch (info & ~XLR_INFO_MASK) |
289 | 0 | { |
290 | 0 | case XLOG2_CHECKSUMS: |
291 | 0 | id = "CHECKSUMS"; |
292 | 0 | break; |
293 | 0 | } |
294 | | |
295 | 0 | return id; |
296 | 0 | } |
297 | | |
298 | | /* |
299 | | * Returns a string giving information about all the blocks in an |
300 | | * XLogRecord. |
301 | | */ |
302 | | void |
303 | | XLogRecGetBlockRefInfo(XLogReaderState *record, bool pretty, |
304 | | bool detailed_format, StringInfo buf, |
305 | | uint32 *fpi_len) |
306 | 0 | { |
307 | 0 | int block_id; |
308 | |
|
309 | 0 | Assert(record != NULL); |
310 | |
|
311 | 0 | if (detailed_format && pretty) |
312 | 0 | appendStringInfoChar(buf, '\n'); |
313 | |
|
314 | 0 | for (block_id = 0; block_id <= XLogRecMaxBlockId(record); block_id++) |
315 | 0 | { |
316 | 0 | RelFileLocator rlocator; |
317 | 0 | ForkNumber forknum; |
318 | 0 | BlockNumber blk; |
319 | |
|
320 | 0 | if (!XLogRecGetBlockTagExtended(record, block_id, |
321 | 0 | &rlocator, &forknum, &blk, NULL)) |
322 | 0 | continue; |
323 | | |
324 | 0 | if (detailed_format) |
325 | 0 | { |
326 | | /* Get block references in detailed format. */ |
327 | |
|
328 | 0 | if (pretty) |
329 | 0 | appendStringInfoChar(buf, '\t'); |
330 | 0 | else if (block_id > 0) |
331 | 0 | appendStringInfoChar(buf, ' '); |
332 | |
|
333 | 0 | appendStringInfo(buf, |
334 | 0 | "blkref #%d: rel %u/%u/%u fork %s blk %u", |
335 | 0 | block_id, |
336 | 0 | rlocator.spcOid, rlocator.dbOid, rlocator.relNumber, |
337 | 0 | forkNames[forknum], |
338 | 0 | blk); |
339 | |
|
340 | 0 | if (XLogRecHasBlockImage(record, block_id)) |
341 | 0 | { |
342 | 0 | uint8 bimg_info = XLogRecGetBlock(record, block_id)->bimg_info; |
343 | | |
344 | | /* Calculate the amount of FPI data in the record. */ |
345 | 0 | if (fpi_len) |
346 | 0 | *fpi_len += XLogRecGetBlock(record, block_id)->bimg_len; |
347 | |
|
348 | 0 | if (BKPIMAGE_COMPRESSED(bimg_info)) |
349 | 0 | { |
350 | 0 | const char *method; |
351 | |
|
352 | 0 | if ((bimg_info & BKPIMAGE_COMPRESS_PGLZ) != 0) |
353 | 0 | method = "pglz"; |
354 | 0 | else if ((bimg_info & BKPIMAGE_COMPRESS_LZ4) != 0) |
355 | 0 | method = "lz4"; |
356 | 0 | else if ((bimg_info & BKPIMAGE_COMPRESS_ZSTD) != 0) |
357 | 0 | method = "zstd"; |
358 | 0 | else |
359 | 0 | method = "unknown"; |
360 | |
|
361 | 0 | appendStringInfo(buf, |
362 | 0 | " (FPW%s); hole: offset: %u, length: %u, " |
363 | 0 | "compression saved: %u, method: %s", |
364 | 0 | XLogRecBlockImageApply(record, block_id) ? |
365 | 0 | "" : " for WAL verification", |
366 | 0 | XLogRecGetBlock(record, block_id)->hole_offset, |
367 | 0 | XLogRecGetBlock(record, block_id)->hole_length, |
368 | 0 | BLCKSZ - |
369 | 0 | XLogRecGetBlock(record, block_id)->hole_length - |
370 | 0 | XLogRecGetBlock(record, block_id)->bimg_len, |
371 | 0 | method); |
372 | 0 | } |
373 | 0 | else |
374 | 0 | { |
375 | 0 | appendStringInfo(buf, |
376 | 0 | " (FPW%s); hole: offset: %u, length: %u", |
377 | 0 | XLogRecBlockImageApply(record, block_id) ? |
378 | 0 | "" : " for WAL verification", |
379 | 0 | XLogRecGetBlock(record, block_id)->hole_offset, |
380 | 0 | XLogRecGetBlock(record, block_id)->hole_length); |
381 | 0 | } |
382 | 0 | } |
383 | |
|
384 | 0 | if (pretty) |
385 | 0 | appendStringInfoChar(buf, '\n'); |
386 | 0 | } |
387 | 0 | else |
388 | 0 | { |
389 | | /* Get block references in short format. */ |
390 | |
|
391 | 0 | if (forknum != MAIN_FORKNUM) |
392 | 0 | { |
393 | 0 | appendStringInfo(buf, |
394 | 0 | ", blkref #%d: rel %u/%u/%u fork %s blk %u", |
395 | 0 | block_id, |
396 | 0 | rlocator.spcOid, rlocator.dbOid, rlocator.relNumber, |
397 | 0 | forkNames[forknum], |
398 | 0 | blk); |
399 | 0 | } |
400 | 0 | else |
401 | 0 | { |
402 | 0 | appendStringInfo(buf, |
403 | 0 | ", blkref #%d: rel %u/%u/%u blk %u", |
404 | 0 | block_id, |
405 | 0 | rlocator.spcOid, rlocator.dbOid, rlocator.relNumber, |
406 | 0 | blk); |
407 | 0 | } |
408 | |
|
409 | 0 | if (XLogRecHasBlockImage(record, block_id)) |
410 | 0 | { |
411 | | /* Calculate the amount of FPI data in the record. */ |
412 | 0 | if (fpi_len) |
413 | 0 | *fpi_len += XLogRecGetBlock(record, block_id)->bimg_len; |
414 | |
|
415 | 0 | if (XLogRecBlockImageApply(record, block_id)) |
416 | 0 | appendStringInfoString(buf, " FPW"); |
417 | 0 | else |
418 | 0 | appendStringInfoString(buf, " FPW for WAL verification"); |
419 | 0 | } |
420 | 0 | } |
421 | 0 | } |
422 | |
|
423 | 0 | if (!detailed_format && pretty) |
424 | 0 | appendStringInfoChar(buf, '\n'); |
425 | 0 | } |