1 /*-------------------------------------------------------------------------
3 * pg_waldump.c - decode and display WAL
5 * Copyright (c) 2013-2017, PostgreSQL Global Development Group
8 * src/bin/pg_waldump/pg_waldump.c
9 *-------------------------------------------------------------------------
18 #include "access/xlogreader.h"
19 #include "access/xlogrecord.h"
20 #include "access/xlog_internal.h"
21 #include "access/transam.h"
22 #include "common/fe_memutils.h"
23 #include "getopt_long.h"
27 static const char *progname;
29 typedef struct XLogDumpPrivate
38 typedef struct XLogDumpConfig
42 int stop_after_records;
43 int already_displayed_records;
46 bool stats_per_record;
50 TransactionId filter_by_xid;
51 bool filter_by_xid_enabled;
61 #define MAX_XLINFO_TYPES 16
63 typedef struct XLogDumpStats
66 Stats rmgr_stats[RM_NEXT_ID];
67 Stats record_stats[RM_NEXT_ID][MAX_XLINFO_TYPES];
70 static void fatal_error(const char *fmt,...) pg_attribute_printf(1, 2);
73 * Big red button to push when things go horribly wrong.
76 fatal_error(const char *fmt,...)
82 fprintf(stderr, _("%s: FATAL: "), progname);
84 vfprintf(stderr, _(fmt), args);
96 for (i = 0; i <= RM_MAX_ID; i++)
98 printf("%s\n", RmgrDescTable[i].rm_name);
103 * Check whether directory exists and whether we can open it. Keep errno set so
104 * that the caller can report errors somewhat more accurately.
107 verify_directory(const char *directory)
109 DIR *dir = opendir(directory);
118 * Split a pathname as dirname(1) and basename(1) would.
120 * XXX this probably doesn't do very well on Windows. We probably need to
121 * apply canonicalize_path(), at the very least.
124 split_path(const char *path, char **dir, char **fname)
128 /* split filepath into directory & filename */
129 sep = strrchr(path, '/');
134 *dir = pg_strdup(path);
135 (*dir)[(sep - path) + 1] = '\0'; /* no strndup */
136 *fname = pg_strdup(sep + 1);
138 /* local directory */
142 *fname = pg_strdup(path);
147 * Try to find the file in several places:
148 * if directory == NULL:
151 * $PGDATA / XLOGDIR / fname
154 * directory / XLOGDIR / fname
156 * return a read only fd
159 fuzzy_open_file(const char *directory, const char *fname)
162 char fpath[MAXPGPATH];
164 if (directory == NULL)
169 fd = open(fname, O_RDONLY | PG_BINARY, 0);
170 if (fd < 0 && errno != ENOENT)
175 /* XLOGDIR / fname */
176 snprintf(fpath, MAXPGPATH, "%s/%s",
178 fd = open(fpath, O_RDONLY | PG_BINARY, 0);
179 if (fd < 0 && errno != ENOENT)
184 datadir = getenv("PGDATA");
185 /* $PGDATA / XLOGDIR / fname */
188 snprintf(fpath, MAXPGPATH, "%s/%s/%s",
189 datadir, XLOGDIR, fname);
190 fd = open(fpath, O_RDONLY | PG_BINARY, 0);
191 if (fd < 0 && errno != ENOENT)
199 /* directory / fname */
200 snprintf(fpath, MAXPGPATH, "%s/%s",
202 fd = open(fpath, O_RDONLY | PG_BINARY, 0);
203 if (fd < 0 && errno != ENOENT)
208 /* directory / XLOGDIR / fname */
209 snprintf(fpath, MAXPGPATH, "%s/%s/%s",
210 directory, XLOGDIR, fname);
211 fd = open(fpath, O_RDONLY | PG_BINARY, 0);
212 if (fd < 0 && errno != ENOENT)
221 * Read count bytes from a segment file in the specified directory, for the
222 * given timeline, containing the specified record pointer; store the data in
226 XLogDumpXLogRead(const char *directory, TimeLineID timeline_id,
227 XLogRecPtr startptr, char *buf, Size count)
233 static int sendFile = -1;
234 static XLogSegNo sendSegNo = 0;
235 static uint32 sendOff = 0;
247 startoff = recptr % XLogSegSize;
249 if (sendFile < 0 || !XLByteInSeg(recptr, sendSegNo))
251 char fname[MAXFNAMELEN];
254 /* Switch to another logfile segment */
258 XLByteToSeg(recptr, sendSegNo);
260 XLogFileName(fname, timeline_id, sendSegNo);
263 * In follow mode there is a short period of time after the server
264 * has written the end of the previous file before the new file is
265 * available. So we loop for 5 seconds looking for the file to
266 * appear before giving up.
268 for (tries = 0; tries < 10; tries++)
270 sendFile = fuzzy_open_file(directory, fname);
275 int save_errno = errno;
277 /* File not there yet, try again */
278 pg_usleep(500 * 1000);
283 /* Any other error, fall through and fail */
288 fatal_error("could not find file \"%s\": %s",
289 fname, strerror(errno));
293 /* Need to seek in the file? */
294 if (sendOff != startoff)
296 if (lseek(sendFile, (off_t) startoff, SEEK_SET) < 0)
299 char fname[MAXPGPATH];
301 XLogFileName(fname, timeline_id, sendSegNo);
303 fatal_error("could not seek in log file %s to offset %u: %s",
304 fname, startoff, strerror(err));
309 /* How many bytes are within this segment? */
310 if (nbytes > (XLogSegSize - startoff))
311 segbytes = XLogSegSize - startoff;
315 readbytes = read(sendFile, p, segbytes);
319 char fname[MAXPGPATH];
321 XLogFileName(fname, timeline_id, sendSegNo);
323 fatal_error("could not read from log file %s, offset %u, length %d: %s",
324 fname, sendOff, segbytes, strerror(err));
327 /* Update state for read */
330 sendOff += readbytes;
337 * XLogReader read_page callback
340 XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
341 XLogRecPtr targetPtr, char *readBuff, TimeLineID *curFileTLI)
343 XLogDumpPrivate *private = state->private_data;
344 int count = XLOG_BLCKSZ;
346 if (private->endptr != InvalidXLogRecPtr)
348 if (targetPagePtr + XLOG_BLCKSZ <= private->endptr)
350 else if (targetPagePtr + reqLen <= private->endptr)
351 count = private->endptr - targetPagePtr;
354 private->endptr_reached = true;
359 XLogDumpXLogRead(private->inpath, private->timeline, targetPagePtr,
366 * Calculate the size of a record, split into !FPI and FPI parts.
369 XLogDumpRecordLen(XLogReaderState *record, uint32 *rec_len, uint32 *fpi_len)
374 * Calculate the amount of FPI data in the record.
376 * XXX: We peek into xlogreader's private decoded backup blocks for the
377 * bimg_len indicating the length of FPI data. It doesn't seem worth it to
378 * add an accessor macro for this.
381 for (block_id = 0; block_id <= record->max_block_id; block_id++)
383 if (XLogRecHasBlockImage(record, block_id))
384 *fpi_len += record->blocks[block_id].bimg_len;
388 * Calculate the length of the record as the total length - the length of
389 * all the block images.
391 *rec_len = XLogRecGetTotalLen(record) - *fpi_len;
395 * Store per-rmgr and per-record statistics for a given record.
398 XLogDumpCountRecord(XLogDumpConfig *config, XLogDumpStats *stats,
399 XLogReaderState *record)
408 rmid = XLogRecGetRmid(record);
410 XLogDumpRecordLen(record, &rec_len, &fpi_len);
412 /* Update per-rmgr statistics */
414 stats->rmgr_stats[rmid].count++;
415 stats->rmgr_stats[rmid].rec_len += rec_len;
416 stats->rmgr_stats[rmid].fpi_len += fpi_len;
419 * Update per-record statistics, where the record is identified by a
420 * combination of the RmgrId and the four bits of the xl_info field that
421 * are the rmgr's domain (resulting in sixteen possible entries per
425 recid = XLogRecGetInfo(record) >> 4;
427 stats->record_stats[rmid][recid].count++;
428 stats->record_stats[rmid][recid].rec_len += rec_len;
429 stats->record_stats[rmid][recid].fpi_len += fpi_len;
433 * Print a record to stdout
436 XLogDumpDisplayRecord(XLogDumpConfig *config, XLogReaderState *record)
439 const RmgrDescData *desc = &RmgrDescTable[XLogRecGetRmid(record)];
446 uint8 info = XLogRecGetInfo(record);
447 XLogRecPtr xl_prev = XLogRecGetPrev(record);
449 XLogDumpRecordLen(record, &rec_len, &fpi_len);
451 id = desc->rm_identify(info);
453 id = psprintf("UNKNOWN (%x)", info & ~XLR_INFO_MASK);
455 printf("rmgr: %-11s len (rec/tot): %6u/%6u, tx: %10u, lsn: %X/%08X, prev %X/%08X, ",
457 rec_len, XLogRecGetTotalLen(record),
458 XLogRecGetXid(record),
459 (uint32) (record->ReadRecPtr >> 32), (uint32) record->ReadRecPtr,
460 (uint32) (xl_prev >> 32), (uint32) xl_prev);
461 printf("desc: %s ", id);
463 /* the desc routine will printf the description directly to stdout */
464 desc->rm_desc(NULL, record);
466 if (!config->bkp_details)
468 /* print block references (short format) */
469 for (block_id = 0; block_id <= record->max_block_id; block_id++)
471 if (!XLogRecHasBlockRef(record, block_id))
474 XLogRecGetBlockTag(record, block_id, &rnode, &forknum, &blk);
475 if (forknum != MAIN_FORKNUM)
476 printf(", blkref #%u: rel %u/%u/%u fork %s blk %u",
478 rnode.spcNode, rnode.dbNode, rnode.relNode,
482 printf(", blkref #%u: rel %u/%u/%u blk %u",
484 rnode.spcNode, rnode.dbNode, rnode.relNode,
486 if (XLogRecHasBlockImage(record, block_id))
488 if (XLogRecBlockImageApply(record, block_id))
491 printf(" FPW for WAL verification");
498 /* print block references (detailed format) */
500 for (block_id = 0; block_id <= record->max_block_id; block_id++)
502 if (!XLogRecHasBlockRef(record, block_id))
505 XLogRecGetBlockTag(record, block_id, &rnode, &forknum, &blk);
506 printf("\tblkref #%u: rel %u/%u/%u fork %s blk %u",
508 rnode.spcNode, rnode.dbNode, rnode.relNode,
511 if (XLogRecHasBlockImage(record, block_id))
513 if (record->blocks[block_id].bimg_info &
514 BKPIMAGE_IS_COMPRESSED)
516 printf(" (FPW%s); hole: offset: %u, length: %u, "
517 "compression saved: %u\n",
518 XLogRecBlockImageApply(record, block_id) ?
519 "" : " for WAL verification",
520 record->blocks[block_id].hole_offset,
521 record->blocks[block_id].hole_length,
523 record->blocks[block_id].hole_length -
524 record->blocks[block_id].bimg_len);
528 printf(" (FPW%s); hole: offset: %u, length: %u\n",
529 XLogRecBlockImageApply(record, block_id) ?
530 "" : " for WAL verification",
531 record->blocks[block_id].hole_offset,
532 record->blocks[block_id].hole_length);
541 * Display a single row of record counts and sizes for an rmgr or record.
544 XLogDumpStatsRow(const char *name,
545 uint64 n, uint64 total_count,
546 uint64 rec_len, uint64 total_rec_len,
547 uint64 fpi_len, uint64 total_fpi_len,
548 uint64 tot_len, uint64 total_len)
556 if (total_count != 0)
557 n_pct = 100 * (double) n / total_count;
560 if (total_rec_len != 0)
561 rec_len_pct = 100 * (double) rec_len / total_rec_len;
564 if (total_fpi_len != 0)
565 fpi_len_pct = 100 * (double) fpi_len / total_fpi_len;
569 tot_len_pct = 100 * (double) tot_len / total_len;
572 "%20" INT64_MODIFIER "u (%6.02f) "
573 "%20" INT64_MODIFIER "u (%6.02f) "
574 "%20" INT64_MODIFIER "u (%6.02f) "
575 "%20" INT64_MODIFIER "u (%6.02f)\n",
576 name, n, n_pct, rec_len, rec_len_pct, fpi_len, fpi_len_pct,
577 tot_len, tot_len_pct);
582 * Display summary statistics about the records seen so far.
585 XLogDumpDisplayStats(XLogDumpConfig *config, XLogDumpStats *stats)
589 uint64 total_count = 0;
590 uint64 total_rec_len = 0;
591 uint64 total_fpi_len = 0;
592 uint64 total_len = 0;
597 * Make a first pass to calculate column totals:
599 * sum(xl_len+SizeOfXLogRecord),
600 * sum(xl_tot_len-xl_len-SizeOfXLogRecord), and
602 * These are used to calculate percentages for each record type.
606 for (ri = 0; ri < RM_NEXT_ID; ri++)
608 total_count += stats->rmgr_stats[ri].count;
609 total_rec_len += stats->rmgr_stats[ri].rec_len;
610 total_fpi_len += stats->rmgr_stats[ri].fpi_len;
612 total_len = total_rec_len + total_fpi_len;
615 * 27 is strlen("Transaction/COMMIT_PREPARED"), 20 is strlen(2^64), 8 is
616 * strlen("(100.00%)")
619 printf("%-27s %20s %8s %20s %8s %20s %8s %20s %8s\n"
620 "%-27s %20s %8s %20s %8s %20s %8s %20s %8s\n",
621 "Type", "N", "(%)", "Record size", "(%)", "FPI size", "(%)", "Combined size", "(%)",
622 "----", "-", "---", "-----------", "---", "--------", "---", "-------------", "---");
624 for (ri = 0; ri < RM_NEXT_ID; ri++)
630 const RmgrDescData *desc = &RmgrDescTable[ri];
632 if (!config->stats_per_record)
634 count = stats->rmgr_stats[ri].count;
635 rec_len = stats->rmgr_stats[ri].rec_len;
636 fpi_len = stats->rmgr_stats[ri].fpi_len;
637 tot_len = rec_len + fpi_len;
639 XLogDumpStatsRow(desc->rm_name,
640 count, total_count, rec_len, total_rec_len,
641 fpi_len, total_fpi_len, tot_len, total_len);
645 for (rj = 0; rj < MAX_XLINFO_TYPES; rj++)
649 count = stats->record_stats[ri][rj].count;
650 rec_len = stats->record_stats[ri][rj].rec_len;
651 fpi_len = stats->record_stats[ri][rj].fpi_len;
652 tot_len = rec_len + fpi_len;
654 /* Skip undefined combinations and ones that didn't occur */
658 /* the upper four bits in xl_info are the rmgr's */
659 id = desc->rm_identify(rj << 4);
661 id = psprintf("UNKNOWN (%x)", rj << 4);
663 XLogDumpStatsRow(psprintf("%s/%s", desc->rm_name, id),
664 count, total_count, rec_len, total_rec_len,
665 fpi_len, total_fpi_len, tot_len, total_len);
670 printf("%-27s %20s %8s %20s %8s %20s %8s %20s\n",
671 "", "--------", "", "--------", "", "--------", "", "--------");
674 * The percentages in earlier rows were calculated against the column
675 * total, but the ones that follow are against the row total. Note that
676 * these are displayed with a % symbol to differentiate them from the
677 * earlier ones, and are thus up to 9 characters long.
682 rec_len_pct = 100 * (double) total_rec_len / total_len;
686 fpi_len_pct = 100 * (double) total_fpi_len / total_len;
689 "%20" INT64_MODIFIER "u %-9s"
690 "%20" INT64_MODIFIER "u %-9s"
691 "%20" INT64_MODIFIER "u %-9s"
692 "%20" INT64_MODIFIER "u %-6s\n",
693 "Total", stats->count, "",
694 total_rec_len, psprintf("[%.02f%%]", rec_len_pct),
695 total_fpi_len, psprintf("[%.02f%%]", fpi_len_pct),
696 total_len, "[100%]");
702 printf(_("%s decodes and displays PostgreSQL write-ahead logs for debugging.\n\n"),
704 printf(_("Usage:\n"));
705 printf(_(" %s [OPTION]... [STARTSEG [ENDSEG]]\n"), progname);
706 printf(_("\nOptions:\n"));
707 printf(_(" -b, --bkp-details output detailed information about backup blocks\n"));
708 printf(_(" -e, --end=RECPTR stop reading at WAL location RECPTR\n"));
709 printf(_(" -f, --follow keep retrying after reaching end of WAL\n"));
710 printf(_(" -n, --limit=N number of records to display\n"));
711 printf(_(" -p, --path=PATH directory in which to find log segment files or a\n"
712 " directory with a ./pg_wal that contains such files\n"
713 " (default: current directory, ./pg_wal, $PGDATA/pg_wal)\n"));
714 printf(_(" -r, --rmgr=RMGR only show records generated by resource manager RMGR;\n"
715 " use --rmgr=list to list valid resource manager names\n"));
716 printf(_(" -s, --start=RECPTR start reading at WAL location RECPTR\n"));
717 printf(_(" -t, --timeline=TLI timeline from which to read log records\n"
718 " (default: 1 or the value used in STARTSEG)\n"));
719 printf(_(" -V, --version output version information, then exit\n"));
720 printf(_(" -x, --xid=XID only show records with transaction ID XID\n"));
721 printf(_(" -z, --stats[=record] show statistics instead of records\n"
722 " (optionally, show per-record statistics)\n"));
723 printf(_(" -?, --help show this help, then exit\n"));
727 main(int argc, char **argv)
731 XLogReaderState *xlogreader_state;
732 XLogDumpPrivate private;
733 XLogDumpConfig config;
736 XLogRecPtr first_record;
739 static struct option long_options[] = {
740 {"bkp-details", no_argument, NULL, 'b'},
741 {"end", required_argument, NULL, 'e'},
742 {"follow", no_argument, NULL, 'f'},
743 {"help", no_argument, NULL, '?'},
744 {"limit", required_argument, NULL, 'n'},
745 {"path", required_argument, NULL, 'p'},
746 {"rmgr", required_argument, NULL, 'r'},
747 {"start", required_argument, NULL, 's'},
748 {"timeline", required_argument, NULL, 't'},
749 {"xid", required_argument, NULL, 'x'},
750 {"version", no_argument, NULL, 'V'},
751 {"stats", optional_argument, NULL, 'z'},
758 set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_waldump"));
759 progname = get_progname(argv[0]);
761 memset(&private, 0, sizeof(XLogDumpPrivate));
762 memset(&config, 0, sizeof(XLogDumpConfig));
763 memset(&stats, 0, sizeof(XLogDumpStats));
765 private.timeline = 1;
766 private.startptr = InvalidXLogRecPtr;
767 private.endptr = InvalidXLogRecPtr;
768 private.endptr_reached = false;
770 config.bkp_details = false;
771 config.stop_after_records = -1;
772 config.already_displayed_records = 0;
773 config.follow = false;
774 config.filter_by_rmgr = -1;
775 config.filter_by_xid = InvalidTransactionId;
776 config.filter_by_xid_enabled = false;
777 config.stats = false;
778 config.stats_per_record = false;
782 fprintf(stderr, _("%s: no arguments specified\n"), progname);
786 while ((option = getopt_long(argc, argv, "be:?fn:p:r:s:t:Vx:z",
787 long_options, &optindex)) != -1)
792 config.bkp_details = true;
795 if (sscanf(optarg, "%X/%X", &xlogid, &xrecoff) != 2)
797 fprintf(stderr, _("%s: could not parse end WAL location \"%s\"\n"),
801 private.endptr = (uint64) xlogid << 32 | xrecoff;
804 config.follow = true;
811 if (sscanf(optarg, "%d", &config.stop_after_records) != 1)
813 fprintf(stderr, _("%s: could not parse limit \"%s\"\n"),
819 private.inpath = pg_strdup(optarg);
825 if (pg_strcasecmp(optarg, "list") == 0)
831 for (i = 0; i <= RM_MAX_ID; i++)
833 if (pg_strcasecmp(optarg, RmgrDescTable[i].rm_name) == 0)
835 config.filter_by_rmgr = i;
840 if (config.filter_by_rmgr == -1)
842 fprintf(stderr, _("%s: resource manager \"%s\" does not exist\n"),
849 if (sscanf(optarg, "%X/%X", &xlogid, &xrecoff) != 2)
851 fprintf(stderr, _("%s: could not parse start WAL location \"%s\"\n"),
856 private.startptr = (uint64) xlogid << 32 | xrecoff;
859 if (sscanf(optarg, "%d", &private.timeline) != 1)
861 fprintf(stderr, _("%s: could not parse timeline \"%s\"\n"),
867 puts("pg_waldump (PostgreSQL) " PG_VERSION);
871 if (sscanf(optarg, "%u", &config.filter_by_xid) != 1)
873 fprintf(stderr, _("%s: could not parse \"%s\" as a transaction ID\n"),
877 config.filter_by_xid_enabled = true;
881 config.stats_per_record = false;
884 if (strcmp(optarg, "record") == 0)
885 config.stats_per_record = true;
886 else if (strcmp(optarg, "rmgr") != 0)
888 fprintf(stderr, _("%s: unrecognized argument to --stats: %s\n"),
899 if ((optind + 2) < argc)
902 _("%s: too many command-line arguments (first is \"%s\")\n"),
903 progname, argv[optind + 2]);
907 if (private.inpath != NULL)
909 /* validate path points to directory */
910 if (!verify_directory(private.inpath))
913 _("%s: path \"%s\" could not be opened: %s\n"),
914 progname, private.inpath, strerror(errno));
919 /* parse files as start/end boundaries, extract path if not specified */
922 char *directory = NULL;
927 split_path(argv[optind], &directory, &fname);
929 if (private.inpath == NULL && directory != NULL)
931 private.inpath = directory;
933 if (!verify_directory(private.inpath))
934 fatal_error("could not open directory \"%s\": %s",
935 private.inpath, strerror(errno));
938 fd = fuzzy_open_file(private.inpath, fname);
940 fatal_error("could not open file \"%s\"", fname);
943 /* parse position from file */
944 XLogFromFileName(fname, &private.timeline, &segno);
946 if (XLogRecPtrIsInvalid(private.startptr))
947 XLogSegNoOffsetToRecPtr(segno, 0, private.startptr);
948 else if (!XLByteInSeg(private.startptr, segno))
951 _("%s: start WAL location %X/%X is not inside file \"%s\"\n"),
953 (uint32) (private.startptr >> 32),
954 (uint32) private.startptr,
959 /* no second file specified, set end position */
960 if (!(optind + 1 < argc) && XLogRecPtrIsInvalid(private.endptr))
961 XLogSegNoOffsetToRecPtr(segno + 1, 0, private.endptr);
963 /* parse ENDSEG if passed */
964 if (optind + 1 < argc)
968 /* ignore directory, already have that */
969 split_path(argv[optind + 1], &directory, &fname);
971 fd = fuzzy_open_file(private.inpath, fname);
973 fatal_error("could not open file \"%s\"", fname);
976 /* parse position from file */
977 XLogFromFileName(fname, &private.timeline, &endsegno);
979 if (endsegno < segno)
980 fatal_error("ENDSEG %s is before STARTSEG %s",
981 argv[optind + 1], argv[optind]);
983 if (XLogRecPtrIsInvalid(private.endptr))
984 XLogSegNoOffsetToRecPtr(endsegno + 1, 0, private.endptr);
986 /* set segno to endsegno for check of --end */
991 if (!XLByteInSeg(private.endptr, segno) &&
992 private.endptr != (segno + 1) * XLogSegSize)
995 _("%s: end WAL location %X/%X is not inside file \"%s\"\n"),
997 (uint32) (private.endptr >> 32),
998 (uint32) private.endptr,
1004 /* we don't know what to print */
1005 if (XLogRecPtrIsInvalid(private.startptr))
1007 fprintf(stderr, _("%s: no start WAL location given\n"), progname);
1011 /* done with argument parsing, do the actual work */
1013 /* we have everything we need, start reading */
1014 xlogreader_state = XLogReaderAllocate(XLogDumpReadPage, &private);
1015 if (!xlogreader_state)
1016 fatal_error("out of memory");
1018 /* first find a valid recptr to start from */
1019 first_record = XLogFindNextRecord(xlogreader_state, private.startptr);
1021 if (first_record == InvalidXLogRecPtr)
1022 fatal_error("could not find a valid record after %X/%X",
1023 (uint32) (private.startptr >> 32),
1024 (uint32) private.startptr);
1027 * Display a message that we're skipping data if `from` wasn't a pointer
1028 * to the start of a record and also wasn't a pointer to the beginning of
1029 * a segment (e.g. we were used in file mode).
1031 if (first_record != private.startptr && (private.startptr % XLogSegSize) != 0)
1032 printf(ngettext("first record is after %X/%X, at %X/%X, skipping over %u byte\n",
1033 "first record is after %X/%X, at %X/%X, skipping over %u bytes\n",
1034 (first_record - private.startptr)),
1035 (uint32) (private.startptr >> 32), (uint32) private.startptr,
1036 (uint32) (first_record >> 32), (uint32) first_record,
1037 (uint32) (first_record - private.startptr));
1041 /* try to read the next record */
1042 record = XLogReadRecord(xlogreader_state, first_record, &errormsg);
1045 if (!config.follow || private.endptr_reached)
1049 pg_usleep(1000000L); /* 1 second */
1054 /* after reading the first record, continue at next one */
1055 first_record = InvalidXLogRecPtr;
1057 /* apply all specified filters */
1058 if (config.filter_by_rmgr != -1 &&
1059 config.filter_by_rmgr != record->xl_rmid)
1062 if (config.filter_by_xid_enabled &&
1063 config.filter_by_xid != record->xl_xid)
1066 /* process the record */
1067 if (config.stats == true)
1068 XLogDumpCountRecord(&config, &stats, xlogreader_state);
1070 XLogDumpDisplayRecord(&config, xlogreader_state);
1072 /* check whether we printed enough */
1073 config.already_displayed_records++;
1074 if (config.stop_after_records > 0 &&
1075 config.already_displayed_records >= config.stop_after_records)
1079 if (config.stats == true)
1080 XLogDumpDisplayStats(&config, &stats);
1083 fatal_error("error in WAL record at %X/%X: %s",
1084 (uint32) (xlogreader_state->ReadRecPtr >> 32),
1085 (uint32) xlogreader_state->ReadRecPtr,
1088 XLogReaderFree(xlogreader_state);
1090 return EXIT_SUCCESS;
1093 fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
1094 return EXIT_FAILURE;