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