Add circular WAL decoding buffer, take II.
[pgsql.git] / src / bin / pg_waldump / pg_waldump.c
blobfc081adfb8c0fef12c57b7bf7d62ccb6a5723d98
1 /*-------------------------------------------------------------------------
3 * pg_waldump.c - decode and display WAL
5 * Copyright (c) 2013-2022, PostgreSQL Global Development Group
7 * IDENTIFICATION
8 * src/bin/pg_waldump/pg_waldump.c
9 *-------------------------------------------------------------------------
12 #define FRONTEND 1
13 #include "postgres.h"
15 #include <dirent.h>
16 #include <signal.h>
17 #include <sys/stat.h>
18 #include <unistd.h>
20 #include "access/transam.h"
21 #include "access/xlog_internal.h"
22 #include "access/xlogreader.h"
23 #include "access/xlogrecord.h"
24 #include "common/fe_memutils.h"
25 #include "common/logging.h"
26 #include "getopt_long.h"
27 #include "rmgrdesc.h"
29 static const char *progname;
31 static int WalSegSz;
32 static volatile sig_atomic_t time_to_stop = false;
34 typedef struct XLogDumpPrivate
36 TimeLineID timeline;
37 XLogRecPtr startptr;
38 XLogRecPtr endptr;
39 bool endptr_reached;
40 } XLogDumpPrivate;
42 typedef struct XLogDumpConfig
44 /* display options */
45 bool quiet;
46 bool bkp_details;
47 int stop_after_records;
48 int already_displayed_records;
49 bool follow;
50 bool stats;
51 bool stats_per_record;
53 /* filter options */
54 bool filter_by_rmgr[RM_MAX_ID + 1];
55 bool filter_by_rmgr_enabled;
56 TransactionId filter_by_xid;
57 bool filter_by_xid_enabled;
58 } XLogDumpConfig;
60 typedef struct Stats
62 uint64 count;
63 uint64 rec_len;
64 uint64 fpi_len;
65 } Stats;
67 #define MAX_XLINFO_TYPES 16
69 typedef struct XLogDumpStats
71 uint64 count;
72 XLogRecPtr startptr;
73 XLogRecPtr endptr;
74 Stats rmgr_stats[RM_NEXT_ID];
75 Stats record_stats[RM_NEXT_ID][MAX_XLINFO_TYPES];
76 } XLogDumpStats;
78 #define fatal_error(...) do { pg_log_fatal(__VA_ARGS__); exit(EXIT_FAILURE); } while(0)
81 * When sigint is called, just tell the system to exit at the next possible
82 * moment.
84 #ifndef WIN32
86 static void
87 sigint_handler(int signum)
89 time_to_stop = true;
91 #endif
93 static void
94 print_rmgr_list(void)
96 int i;
98 for (i = 0; i <= RM_MAX_ID; i++)
100 printf("%s\n", RmgrDescTable[i].rm_name);
105 * Check whether directory exists and whether we can open it. Keep errno set so
106 * that the caller can report errors somewhat more accurately.
108 static bool
109 verify_directory(const char *directory)
111 DIR *dir = opendir(directory);
113 if (dir == NULL)
114 return false;
115 closedir(dir);
116 return true;
120 * Split a pathname as dirname(1) and basename(1) would.
122 * XXX this probably doesn't do very well on Windows. We probably need to
123 * apply canonicalize_path(), at the very least.
125 static void
126 split_path(const char *path, char **dir, char **fname)
128 char *sep;
130 /* split filepath into directory & filename */
131 sep = strrchr(path, '/');
133 /* directory path */
134 if (sep != NULL)
136 *dir = pnstrdup(path, sep - path);
137 *fname = pg_strdup(sep + 1);
139 /* local directory */
140 else
142 *dir = NULL;
143 *fname = pg_strdup(path);
148 * Open the file in the valid target directory.
150 * return a read only fd
152 static int
153 open_file_in_directory(const char *directory, const char *fname)
155 int fd = -1;
156 char fpath[MAXPGPATH];
158 Assert(directory != NULL);
160 snprintf(fpath, MAXPGPATH, "%s/%s", directory, fname);
161 fd = open(fpath, O_RDONLY | PG_BINARY, 0);
163 if (fd < 0 && errno != ENOENT)
164 fatal_error("could not open file \"%s\": %m", fname);
165 return fd;
169 * Try to find fname in the given directory. Returns true if it is found,
170 * false otherwise. If fname is NULL, search the complete directory for any
171 * file with a valid WAL file name. If file is successfully opened, set the
172 * wal segment size.
174 static bool
175 search_directory(const char *directory, const char *fname)
177 int fd = -1;
178 DIR *xldir;
180 /* open file if valid filename is provided */
181 if (fname != NULL)
182 fd = open_file_in_directory(directory, fname);
185 * A valid file name is not passed, so search the complete directory. If
186 * we find any file whose name is a valid WAL file name then try to open
187 * it. If we cannot open it, bail out.
189 else if ((xldir = opendir(directory)) != NULL)
191 struct dirent *xlde;
193 while ((xlde = readdir(xldir)) != NULL)
195 if (IsXLogFileName(xlde->d_name))
197 fd = open_file_in_directory(directory, xlde->d_name);
198 fname = xlde->d_name;
199 break;
203 closedir(xldir);
206 /* set WalSegSz if file is successfully opened */
207 if (fd >= 0)
209 PGAlignedXLogBlock buf;
210 int r;
212 r = read(fd, buf.data, XLOG_BLCKSZ);
213 if (r == XLOG_BLCKSZ)
215 XLogLongPageHeader longhdr = (XLogLongPageHeader) buf.data;
217 WalSegSz = longhdr->xlp_seg_size;
219 if (!IsValidWalSegSize(WalSegSz))
220 fatal_error(ngettext("WAL segment size must be a power of two between 1 MB and 1 GB, but the WAL file \"%s\" header specifies %d byte",
221 "WAL segment size must be a power of two between 1 MB and 1 GB, but the WAL file \"%s\" header specifies %d bytes",
222 WalSegSz),
223 fname, WalSegSz);
225 else if (r < 0)
226 fatal_error("could not read file \"%s\": %m",
227 fname);
228 else
229 fatal_error("could not read file \"%s\": read %d of %d",
230 fname, r, XLOG_BLCKSZ);
231 close(fd);
232 return true;
235 return false;
239 * Identify the target directory.
241 * Try to find the file in several places:
242 * if directory != NULL:
243 * directory /
244 * directory / XLOGDIR /
245 * else
247 * XLOGDIR /
248 * $PGDATA / XLOGDIR /
250 * The valid target directory is returned.
252 static char *
253 identify_target_directory(char *directory, char *fname)
255 char fpath[MAXPGPATH];
257 if (directory != NULL)
259 if (search_directory(directory, fname))
260 return pg_strdup(directory);
262 /* directory / XLOGDIR */
263 snprintf(fpath, MAXPGPATH, "%s/%s", directory, XLOGDIR);
264 if (search_directory(fpath, fname))
265 return pg_strdup(fpath);
267 else
269 const char *datadir;
271 /* current directory */
272 if (search_directory(".", fname))
273 return pg_strdup(".");
274 /* XLOGDIR */
275 if (search_directory(XLOGDIR, fname))
276 return pg_strdup(XLOGDIR);
278 datadir = getenv("PGDATA");
279 /* $PGDATA / XLOGDIR */
280 if (datadir != NULL)
282 snprintf(fpath, MAXPGPATH, "%s/%s", datadir, XLOGDIR);
283 if (search_directory(fpath, fname))
284 return pg_strdup(fpath);
288 /* could not locate WAL file */
289 if (fname)
290 fatal_error("could not locate WAL file \"%s\"", fname);
291 else
292 fatal_error("could not find any WAL file");
294 return NULL; /* not reached */
297 /* pg_waldump's XLogReaderRoutine->segment_open callback */
298 static void
299 WALDumpOpenSegment(XLogReaderState *state, XLogSegNo nextSegNo,
300 TimeLineID *tli_p)
302 TimeLineID tli = *tli_p;
303 char fname[MAXPGPATH];
304 int tries;
306 XLogFileName(fname, tli, nextSegNo, state->segcxt.ws_segsize);
309 * In follow mode there is a short period of time after the server has
310 * written the end of the previous file before the new file is available.
311 * So we loop for 5 seconds looking for the file to appear before giving
312 * up.
314 for (tries = 0; tries < 10; tries++)
316 state->seg.ws_file = open_file_in_directory(state->segcxt.ws_dir, fname);
317 if (state->seg.ws_file >= 0)
318 return;
319 if (errno == ENOENT)
321 int save_errno = errno;
323 /* File not there yet, try again */
324 pg_usleep(500 * 1000);
326 errno = save_errno;
327 continue;
329 /* Any other error, fall through and fail */
330 break;
333 fatal_error("could not find file \"%s\": %m", fname);
337 * pg_waldump's XLogReaderRoutine->segment_close callback. Same as
338 * wal_segment_close
340 static void
341 WALDumpCloseSegment(XLogReaderState *state)
343 close(state->seg.ws_file);
344 /* need to check errno? */
345 state->seg.ws_file = -1;
348 /* pg_waldump's XLogReaderRoutine->page_read callback */
349 static int
350 WALDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
351 XLogRecPtr targetPtr, char *readBuff)
353 XLogDumpPrivate *private = state->private_data;
354 int count = XLOG_BLCKSZ;
355 WALReadError errinfo;
357 if (private->endptr != InvalidXLogRecPtr)
359 if (targetPagePtr + XLOG_BLCKSZ <= private->endptr)
360 count = XLOG_BLCKSZ;
361 else if (targetPagePtr + reqLen <= private->endptr)
362 count = private->endptr - targetPagePtr;
363 else
365 private->endptr_reached = true;
366 return -1;
370 if (!WALRead(state, readBuff, targetPagePtr, count, private->timeline,
371 &errinfo))
373 WALOpenSegment *seg = &errinfo.wre_seg;
374 char fname[MAXPGPATH];
376 XLogFileName(fname, seg->ws_tli, seg->ws_segno,
377 state->segcxt.ws_segsize);
379 if (errinfo.wre_errno != 0)
381 errno = errinfo.wre_errno;
382 fatal_error("could not read from file %s, offset %d: %m",
383 fname, errinfo.wre_off);
385 else
386 fatal_error("could not read from file %s, offset %d: read %d of %d",
387 fname, errinfo.wre_off, errinfo.wre_read,
388 errinfo.wre_req);
391 return count;
395 * Calculate the size of a record, split into !FPI and FPI parts.
397 static void
398 XLogDumpRecordLen(XLogReaderState *record, uint32 *rec_len, uint32 *fpi_len)
400 int block_id;
403 * Calculate the amount of FPI data in the record.
405 * XXX: We peek into xlogreader's private decoded backup blocks for the
406 * bimg_len indicating the length of FPI data.
408 *fpi_len = 0;
409 for (block_id = 0; block_id <= XLogRecMaxBlockId(record); block_id++)
411 if (XLogRecHasBlockImage(record, block_id))
412 *fpi_len += XLogRecGetBlock(record, block_id)->bimg_len;
416 * Calculate the length of the record as the total length - the length of
417 * all the block images.
419 *rec_len = XLogRecGetTotalLen(record) - *fpi_len;
423 * Store per-rmgr and per-record statistics for a given record.
425 static void
426 XLogDumpCountRecord(XLogDumpConfig *config, XLogDumpStats *stats,
427 XLogReaderState *record)
429 RmgrId rmid;
430 uint8 recid;
431 uint32 rec_len;
432 uint32 fpi_len;
434 stats->count++;
436 rmid = XLogRecGetRmid(record);
438 XLogDumpRecordLen(record, &rec_len, &fpi_len);
440 /* Update per-rmgr statistics */
442 stats->rmgr_stats[rmid].count++;
443 stats->rmgr_stats[rmid].rec_len += rec_len;
444 stats->rmgr_stats[rmid].fpi_len += fpi_len;
447 * Update per-record statistics, where the record is identified by a
448 * combination of the RmgrId and the four bits of the xl_info field that
449 * are the rmgr's domain (resulting in sixteen possible entries per
450 * RmgrId).
453 recid = XLogRecGetInfo(record) >> 4;
456 * XACT records need to be handled differently. Those records use the
457 * first bit of those four bits for an optional flag variable and the
458 * following three bits for the opcode. We filter opcode out of xl_info
459 * and use it as the identifier of the record.
461 if (rmid == RM_XACT_ID)
462 recid &= 0x07;
464 stats->record_stats[rmid][recid].count++;
465 stats->record_stats[rmid][recid].rec_len += rec_len;
466 stats->record_stats[rmid][recid].fpi_len += fpi_len;
470 * Print a record to stdout
472 static void
473 XLogDumpDisplayRecord(XLogDumpConfig *config, XLogReaderState *record)
475 const char *id;
476 const RmgrDescData *desc = &RmgrDescTable[XLogRecGetRmid(record)];
477 uint32 rec_len;
478 uint32 fpi_len;
479 RelFileNode rnode;
480 ForkNumber forknum;
481 BlockNumber blk;
482 int block_id;
483 uint8 info = XLogRecGetInfo(record);
484 XLogRecPtr xl_prev = XLogRecGetPrev(record);
485 StringInfoData s;
487 XLogDumpRecordLen(record, &rec_len, &fpi_len);
489 printf("rmgr: %-11s len (rec/tot): %6u/%6u, tx: %10u, lsn: %X/%08X, prev %X/%08X, ",
490 desc->rm_name,
491 rec_len, XLogRecGetTotalLen(record),
492 XLogRecGetXid(record),
493 LSN_FORMAT_ARGS(record->ReadRecPtr),
494 LSN_FORMAT_ARGS(xl_prev));
496 id = desc->rm_identify(info);
497 if (id == NULL)
498 printf("desc: UNKNOWN (%x) ", info & ~XLR_INFO_MASK);
499 else
500 printf("desc: %s ", id);
502 initStringInfo(&s);
503 desc->rm_desc(&s, record);
504 printf("%s", s.data);
505 pfree(s.data);
507 if (!config->bkp_details)
509 /* print block references (short format) */
510 for (block_id = 0; block_id <= XLogRecMaxBlockId(record); block_id++)
512 if (!XLogRecHasBlockRef(record, block_id))
513 continue;
515 XLogRecGetBlockTag(record, block_id, &rnode, &forknum, &blk);
516 if (forknum != MAIN_FORKNUM)
517 printf(", blkref #%d: rel %u/%u/%u fork %s blk %u",
518 block_id,
519 rnode.spcNode, rnode.dbNode, rnode.relNode,
520 forkNames[forknum],
521 blk);
522 else
523 printf(", blkref #%d: rel %u/%u/%u blk %u",
524 block_id,
525 rnode.spcNode, rnode.dbNode, rnode.relNode,
526 blk);
527 if (XLogRecHasBlockImage(record, block_id))
529 if (XLogRecBlockImageApply(record, block_id))
530 printf(" FPW");
531 else
532 printf(" FPW for WAL verification");
535 putchar('\n');
537 else
539 /* print block references (detailed format) */
540 putchar('\n');
541 for (block_id = 0; block_id <= XLogRecMaxBlockId(record); block_id++)
543 if (!XLogRecHasBlockRef(record, block_id))
544 continue;
546 XLogRecGetBlockTag(record, block_id, &rnode, &forknum, &blk);
547 printf("\tblkref #%d: rel %u/%u/%u fork %s blk %u",
548 block_id,
549 rnode.spcNode, rnode.dbNode, rnode.relNode,
550 forkNames[forknum],
551 blk);
552 if (XLogRecHasBlockImage(record, block_id))
554 uint8 bimg_info = XLogRecGetBlock(record, block_id)->bimg_info;
556 if (BKPIMAGE_COMPRESSED(bimg_info))
558 const char *method;
560 if ((bimg_info & BKPIMAGE_COMPRESS_PGLZ) != 0)
561 method = "pglz";
562 else if ((bimg_info & BKPIMAGE_COMPRESS_LZ4) != 0)
563 method = "lz4";
564 else if ((bimg_info & BKPIMAGE_COMPRESS_ZSTD) != 0)
565 method = "zstd";
566 else
567 method = "unknown";
569 printf(" (FPW%s); hole: offset: %u, length: %u, "
570 "compression saved: %u, method: %s",
571 XLogRecBlockImageApply(record, block_id) ?
572 "" : " for WAL verification",
573 XLogRecGetBlock(record, block_id)->hole_offset,
574 XLogRecGetBlock(record, block_id)->hole_length,
575 BLCKSZ -
576 XLogRecGetBlock(record, block_id)->hole_length -
577 XLogRecGetBlock(record, block_id)->bimg_len,
578 method);
580 else
582 printf(" (FPW%s); hole: offset: %u, length: %u",
583 XLogRecBlockImageApply(record, block_id) ?
584 "" : " for WAL verification",
585 XLogRecGetBlock(record, block_id)->hole_offset,
586 XLogRecGetBlock(record, block_id)->hole_length);
589 putchar('\n');
595 * Display a single row of record counts and sizes for an rmgr or record.
597 static void
598 XLogDumpStatsRow(const char *name,
599 uint64 n, uint64 total_count,
600 uint64 rec_len, uint64 total_rec_len,
601 uint64 fpi_len, uint64 total_fpi_len,
602 uint64 tot_len, uint64 total_len)
604 double n_pct,
605 rec_len_pct,
606 fpi_len_pct,
607 tot_len_pct;
609 n_pct = 0;
610 if (total_count != 0)
611 n_pct = 100 * (double) n / total_count;
613 rec_len_pct = 0;
614 if (total_rec_len != 0)
615 rec_len_pct = 100 * (double) rec_len / total_rec_len;
617 fpi_len_pct = 0;
618 if (total_fpi_len != 0)
619 fpi_len_pct = 100 * (double) fpi_len / total_fpi_len;
621 tot_len_pct = 0;
622 if (total_len != 0)
623 tot_len_pct = 100 * (double) tot_len / total_len;
625 printf("%-27s "
626 "%20" INT64_MODIFIER "u (%6.02f) "
627 "%20" INT64_MODIFIER "u (%6.02f) "
628 "%20" INT64_MODIFIER "u (%6.02f) "
629 "%20" INT64_MODIFIER "u (%6.02f)\n",
630 name, n, n_pct, rec_len, rec_len_pct, fpi_len, fpi_len_pct,
631 tot_len, tot_len_pct);
636 * Display summary statistics about the records seen so far.
638 static void
639 XLogDumpDisplayStats(XLogDumpConfig *config, XLogDumpStats *stats)
641 int ri,
643 uint64 total_count = 0;
644 uint64 total_rec_len = 0;
645 uint64 total_fpi_len = 0;
646 uint64 total_len = 0;
647 double rec_len_pct,
648 fpi_len_pct;
651 * Leave if no stats have been computed yet, as tracked by the end LSN.
653 if (XLogRecPtrIsInvalid(stats->endptr))
654 return;
657 * Each row shows its percentages of the total, so make a first pass to
658 * calculate column totals.
661 for (ri = 0; ri < RM_NEXT_ID; ri++)
663 total_count += stats->rmgr_stats[ri].count;
664 total_rec_len += stats->rmgr_stats[ri].rec_len;
665 total_fpi_len += stats->rmgr_stats[ri].fpi_len;
667 total_len = total_rec_len + total_fpi_len;
669 printf("WAL statistics between %X/%X and %X/%X:\n",
670 LSN_FORMAT_ARGS(stats->startptr), LSN_FORMAT_ARGS(stats->endptr));
673 * 27 is strlen("Transaction/COMMIT_PREPARED"), 20 is strlen(2^64), 8 is
674 * strlen("(100.00%)")
677 printf("%-27s %20s %8s %20s %8s %20s %8s %20s %8s\n"
678 "%-27s %20s %8s %20s %8s %20s %8s %20s %8s\n",
679 "Type", "N", "(%)", "Record size", "(%)", "FPI size", "(%)", "Combined size", "(%)",
680 "----", "-", "---", "-----------", "---", "--------", "---", "-------------", "---");
682 for (ri = 0; ri < RM_NEXT_ID; ri++)
684 uint64 count,
685 rec_len,
686 fpi_len,
687 tot_len;
688 const RmgrDescData *desc = &RmgrDescTable[ri];
690 if (!config->stats_per_record)
692 count = stats->rmgr_stats[ri].count;
693 rec_len = stats->rmgr_stats[ri].rec_len;
694 fpi_len = stats->rmgr_stats[ri].fpi_len;
695 tot_len = rec_len + fpi_len;
697 XLogDumpStatsRow(desc->rm_name,
698 count, total_count, rec_len, total_rec_len,
699 fpi_len, total_fpi_len, tot_len, total_len);
701 else
703 for (rj = 0; rj < MAX_XLINFO_TYPES; rj++)
705 const char *id;
707 count = stats->record_stats[ri][rj].count;
708 rec_len = stats->record_stats[ri][rj].rec_len;
709 fpi_len = stats->record_stats[ri][rj].fpi_len;
710 tot_len = rec_len + fpi_len;
712 /* Skip undefined combinations and ones that didn't occur */
713 if (count == 0)
714 continue;
716 /* the upper four bits in xl_info are the rmgr's */
717 id = desc->rm_identify(rj << 4);
718 if (id == NULL)
719 id = psprintf("UNKNOWN (%x)", rj << 4);
721 XLogDumpStatsRow(psprintf("%s/%s", desc->rm_name, id),
722 count, total_count, rec_len, total_rec_len,
723 fpi_len, total_fpi_len, tot_len, total_len);
728 printf("%-27s %20s %8s %20s %8s %20s %8s %20s\n",
729 "", "--------", "", "--------", "", "--------", "", "--------");
732 * The percentages in earlier rows were calculated against the column
733 * total, but the ones that follow are against the row total. Note that
734 * these are displayed with a % symbol to differentiate them from the
735 * earlier ones, and are thus up to 9 characters long.
738 rec_len_pct = 0;
739 if (total_len != 0)
740 rec_len_pct = 100 * (double) total_rec_len / total_len;
742 fpi_len_pct = 0;
743 if (total_len != 0)
744 fpi_len_pct = 100 * (double) total_fpi_len / total_len;
746 printf("%-27s "
747 "%20" INT64_MODIFIER "u %-9s"
748 "%20" INT64_MODIFIER "u %-9s"
749 "%20" INT64_MODIFIER "u %-9s"
750 "%20" INT64_MODIFIER "u %-6s\n",
751 "Total", stats->count, "",
752 total_rec_len, psprintf("[%.02f%%]", rec_len_pct),
753 total_fpi_len, psprintf("[%.02f%%]", fpi_len_pct),
754 total_len, "[100%]");
757 static void
758 usage(void)
760 printf(_("%s decodes and displays PostgreSQL write-ahead logs for debugging.\n\n"),
761 progname);
762 printf(_("Usage:\n"));
763 printf(_(" %s [OPTION]... [STARTSEG [ENDSEG]]\n"), progname);
764 printf(_("\nOptions:\n"));
765 printf(_(" -b, --bkp-details output detailed information about backup blocks\n"));
766 printf(_(" -e, --end=RECPTR stop reading at WAL location RECPTR\n"));
767 printf(_(" -f, --follow keep retrying after reaching end of WAL\n"));
768 printf(_(" -n, --limit=N number of records to display\n"));
769 printf(_(" -p, --path=PATH directory in which to find log segment files or a\n"
770 " directory with a ./pg_wal that contains such files\n"
771 " (default: current directory, ./pg_wal, $PGDATA/pg_wal)\n"));
772 printf(_(" -q, --quiet do not print any output, except for errors\n"));
773 printf(_(" -r, --rmgr=RMGR only show records generated by resource manager RMGR;\n"
774 " use --rmgr=list to list valid resource manager names\n"));
775 printf(_(" -s, --start=RECPTR start reading at WAL location RECPTR\n"));
776 printf(_(" -t, --timeline=TLI timeline from which to read log records\n"
777 " (default: 1 or the value used in STARTSEG)\n"));
778 printf(_(" -V, --version output version information, then exit\n"));
779 printf(_(" -x, --xid=XID only show records with transaction ID XID\n"));
780 printf(_(" -z, --stats[=record] show statistics instead of records\n"
781 " (optionally, show per-record statistics)\n"));
782 printf(_(" -?, --help show this help, then exit\n"));
783 printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
784 printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
788 main(int argc, char **argv)
790 uint32 xlogid;
791 uint32 xrecoff;
792 XLogReaderState *xlogreader_state;
793 XLogDumpPrivate private;
794 XLogDumpConfig config;
795 XLogDumpStats stats;
796 XLogRecord *record;
797 XLogRecPtr first_record;
798 char *waldir = NULL;
799 char *errormsg;
801 static struct option long_options[] = {
802 {"bkp-details", no_argument, NULL, 'b'},
803 {"end", required_argument, NULL, 'e'},
804 {"follow", no_argument, NULL, 'f'},
805 {"help", no_argument, NULL, '?'},
806 {"limit", required_argument, NULL, 'n'},
807 {"path", required_argument, NULL, 'p'},
808 {"quiet", no_argument, NULL, 'q'},
809 {"rmgr", required_argument, NULL, 'r'},
810 {"start", required_argument, NULL, 's'},
811 {"timeline", required_argument, NULL, 't'},
812 {"xid", required_argument, NULL, 'x'},
813 {"version", no_argument, NULL, 'V'},
814 {"stats", optional_argument, NULL, 'z'},
815 {NULL, 0, NULL, 0}
818 int option;
819 int optindex = 0;
821 #ifndef WIN32
822 pqsignal(SIGINT, sigint_handler);
823 #endif
825 pg_logging_init(argv[0]);
826 set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_waldump"));
827 progname = get_progname(argv[0]);
829 if (argc > 1)
831 if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
833 usage();
834 exit(0);
836 if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
838 puts("pg_waldump (PostgreSQL) " PG_VERSION);
839 exit(0);
843 memset(&private, 0, sizeof(XLogDumpPrivate));
844 memset(&config, 0, sizeof(XLogDumpConfig));
845 memset(&stats, 0, sizeof(XLogDumpStats));
847 private.timeline = 1;
848 private.startptr = InvalidXLogRecPtr;
849 private.endptr = InvalidXLogRecPtr;
850 private.endptr_reached = false;
852 config.quiet = false;
853 config.bkp_details = false;
854 config.stop_after_records = -1;
855 config.already_displayed_records = 0;
856 config.follow = false;
857 /* filter_by_rmgr array was zeroed by memset above */
858 config.filter_by_rmgr_enabled = false;
859 config.filter_by_xid = InvalidTransactionId;
860 config.filter_by_xid_enabled = false;
861 config.stats = false;
862 config.stats_per_record = false;
864 stats.startptr = InvalidXLogRecPtr;
865 stats.endptr = InvalidXLogRecPtr;
867 if (argc <= 1)
869 pg_log_error("no arguments specified");
870 goto bad_argument;
873 while ((option = getopt_long(argc, argv, "be:fn:p:qr:s:t:x:z",
874 long_options, &optindex)) != -1)
876 switch (option)
878 case 'b':
879 config.bkp_details = true;
880 break;
881 case 'e':
882 if (sscanf(optarg, "%X/%X", &xlogid, &xrecoff) != 2)
884 pg_log_error("could not parse end WAL location \"%s\"",
885 optarg);
886 goto bad_argument;
888 private.endptr = (uint64) xlogid << 32 | xrecoff;
889 break;
890 case 'f':
891 config.follow = true;
892 break;
893 case 'n':
894 if (sscanf(optarg, "%d", &config.stop_after_records) != 1)
896 pg_log_error("could not parse limit \"%s\"", optarg);
897 goto bad_argument;
899 break;
900 case 'p':
901 waldir = pg_strdup(optarg);
902 break;
903 case 'q':
904 config.quiet = true;
905 break;
906 case 'r':
908 int i;
910 if (pg_strcasecmp(optarg, "list") == 0)
912 print_rmgr_list();
913 exit(EXIT_SUCCESS);
916 for (i = 0; i <= RM_MAX_ID; i++)
918 if (pg_strcasecmp(optarg, RmgrDescTable[i].rm_name) == 0)
920 config.filter_by_rmgr[i] = true;
921 config.filter_by_rmgr_enabled = true;
922 break;
925 if (i > RM_MAX_ID)
927 pg_log_error("resource manager \"%s\" does not exist",
928 optarg);
929 goto bad_argument;
932 break;
933 case 's':
934 if (sscanf(optarg, "%X/%X", &xlogid, &xrecoff) != 2)
936 pg_log_error("could not parse start WAL location \"%s\"",
937 optarg);
938 goto bad_argument;
940 else
941 private.startptr = (uint64) xlogid << 32 | xrecoff;
942 break;
943 case 't':
944 if (sscanf(optarg, "%u", &private.timeline) != 1)
946 pg_log_error("could not parse timeline \"%s\"", optarg);
947 goto bad_argument;
949 break;
950 case 'x':
951 if (sscanf(optarg, "%u", &config.filter_by_xid) != 1)
953 pg_log_error("could not parse \"%s\" as a transaction ID",
954 optarg);
955 goto bad_argument;
957 config.filter_by_xid_enabled = true;
958 break;
959 case 'z':
960 config.stats = true;
961 config.stats_per_record = false;
962 if (optarg)
964 if (strcmp(optarg, "record") == 0)
965 config.stats_per_record = true;
966 else if (strcmp(optarg, "rmgr") != 0)
968 pg_log_error("unrecognized argument to --stats: %s",
969 optarg);
970 goto bad_argument;
973 break;
974 default:
975 goto bad_argument;
979 if ((optind + 2) < argc)
981 pg_log_error("too many command-line arguments (first is \"%s\")",
982 argv[optind + 2]);
983 goto bad_argument;
986 if (waldir != NULL)
988 /* validate path points to directory */
989 if (!verify_directory(waldir))
991 pg_log_error("could not open directory \"%s\": %m", waldir);
992 goto bad_argument;
996 /* parse files as start/end boundaries, extract path if not specified */
997 if (optind < argc)
999 char *directory = NULL;
1000 char *fname = NULL;
1001 int fd;
1002 XLogSegNo segno;
1004 split_path(argv[optind], &directory, &fname);
1006 if (waldir == NULL && directory != NULL)
1008 waldir = directory;
1010 if (!verify_directory(waldir))
1011 fatal_error("could not open directory \"%s\": %m", waldir);
1014 waldir = identify_target_directory(waldir, fname);
1015 fd = open_file_in_directory(waldir, fname);
1016 if (fd < 0)
1017 fatal_error("could not open file \"%s\"", fname);
1018 close(fd);
1020 /* parse position from file */
1021 XLogFromFileName(fname, &private.timeline, &segno, WalSegSz);
1023 if (XLogRecPtrIsInvalid(private.startptr))
1024 XLogSegNoOffsetToRecPtr(segno, 0, WalSegSz, private.startptr);
1025 else if (!XLByteInSeg(private.startptr, segno, WalSegSz))
1027 pg_log_error("start WAL location %X/%X is not inside file \"%s\"",
1028 LSN_FORMAT_ARGS(private.startptr),
1029 fname);
1030 goto bad_argument;
1033 /* no second file specified, set end position */
1034 if (!(optind + 1 < argc) && XLogRecPtrIsInvalid(private.endptr))
1035 XLogSegNoOffsetToRecPtr(segno + 1, 0, WalSegSz, private.endptr);
1037 /* parse ENDSEG if passed */
1038 if (optind + 1 < argc)
1040 XLogSegNo endsegno;
1042 /* ignore directory, already have that */
1043 split_path(argv[optind + 1], &directory, &fname);
1045 fd = open_file_in_directory(waldir, fname);
1046 if (fd < 0)
1047 fatal_error("could not open file \"%s\"", fname);
1048 close(fd);
1050 /* parse position from file */
1051 XLogFromFileName(fname, &private.timeline, &endsegno, WalSegSz);
1053 if (endsegno < segno)
1054 fatal_error("ENDSEG %s is before STARTSEG %s",
1055 argv[optind + 1], argv[optind]);
1057 if (XLogRecPtrIsInvalid(private.endptr))
1058 XLogSegNoOffsetToRecPtr(endsegno + 1, 0, WalSegSz,
1059 private.endptr);
1061 /* set segno to endsegno for check of --end */
1062 segno = endsegno;
1066 if (!XLByteInSeg(private.endptr, segno, WalSegSz) &&
1067 private.endptr != (segno + 1) * WalSegSz)
1069 pg_log_error("end WAL location %X/%X is not inside file \"%s\"",
1070 LSN_FORMAT_ARGS(private.endptr),
1071 argv[argc - 1]);
1072 goto bad_argument;
1075 else
1076 waldir = identify_target_directory(waldir, NULL);
1078 /* we don't know what to print */
1079 if (XLogRecPtrIsInvalid(private.startptr))
1081 pg_log_error("no start WAL location given");
1082 goto bad_argument;
1085 /* done with argument parsing, do the actual work */
1087 /* we have everything we need, start reading */
1088 xlogreader_state =
1089 XLogReaderAllocate(WalSegSz, waldir,
1090 XL_ROUTINE(.page_read = WALDumpReadPage,
1091 .segment_open = WALDumpOpenSegment,
1092 .segment_close = WALDumpCloseSegment),
1093 &private);
1094 if (!xlogreader_state)
1095 fatal_error("out of memory while allocating a WAL reading processor");
1097 /* first find a valid recptr to start from */
1098 first_record = XLogFindNextRecord(xlogreader_state, private.startptr);
1100 if (first_record == InvalidXLogRecPtr)
1101 fatal_error("could not find a valid record after %X/%X",
1102 LSN_FORMAT_ARGS(private.startptr));
1105 * Display a message that we're skipping data if `from` wasn't a pointer
1106 * to the start of a record and also wasn't a pointer to the beginning of
1107 * a segment (e.g. we were used in file mode).
1109 if (first_record != private.startptr &&
1110 XLogSegmentOffset(private.startptr, WalSegSz) != 0)
1111 printf(ngettext("first record is after %X/%X, at %X/%X, skipping over %u byte\n",
1112 "first record is after %X/%X, at %X/%X, skipping over %u bytes\n",
1113 (first_record - private.startptr)),
1114 LSN_FORMAT_ARGS(private.startptr),
1115 LSN_FORMAT_ARGS(first_record),
1116 (uint32) (first_record - private.startptr));
1118 if (config.stats == true && !config.quiet)
1119 stats.startptr = first_record;
1121 for (;;)
1123 if (time_to_stop)
1125 /* We've been Ctrl-C'ed, so leave */
1126 break;
1129 /* try to read the next record */
1130 record = XLogReadRecord(xlogreader_state, &errormsg);
1131 if (!record)
1133 if (!config.follow || private.endptr_reached)
1134 break;
1135 else
1137 pg_usleep(1000000L); /* 1 second */
1138 continue;
1142 /* apply all specified filters */
1143 if (config.filter_by_rmgr_enabled &&
1144 !config.filter_by_rmgr[record->xl_rmid])
1145 continue;
1147 if (config.filter_by_xid_enabled &&
1148 config.filter_by_xid != record->xl_xid)
1149 continue;
1151 /* perform any per-record work */
1152 if (!config.quiet)
1154 if (config.stats == true)
1156 XLogDumpCountRecord(&config, &stats, xlogreader_state);
1157 stats.endptr = xlogreader_state->EndRecPtr;
1159 else
1160 XLogDumpDisplayRecord(&config, xlogreader_state);
1163 /* check whether we printed enough */
1164 config.already_displayed_records++;
1165 if (config.stop_after_records > 0 &&
1166 config.already_displayed_records >= config.stop_after_records)
1167 break;
1170 if (config.stats == true && !config.quiet)
1171 XLogDumpDisplayStats(&config, &stats);
1173 if (time_to_stop)
1174 exit(0);
1176 if (errormsg)
1177 fatal_error("error in WAL record at %X/%X: %s",
1178 LSN_FORMAT_ARGS(xlogreader_state->ReadRecPtr),
1179 errormsg);
1181 XLogReaderFree(xlogreader_state);
1183 return EXIT_SUCCESS;
1185 bad_argument:
1186 fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
1187 return EXIT_FAILURE;