PostgreSQL Source Code  git master
pg_waldump.c
Go to the documentation of this file.
1 /*-------------------------------------------------------------------------
2  *
3  * pg_waldump.c - decode and display WAL
4  *
5  * Copyright (c) 2013-2022, PostgreSQL Global Development Group
6  *
7  * IDENTIFICATION
8  * src/bin/pg_waldump/pg_waldump.c
9  *-------------------------------------------------------------------------
10  */
11 
12 #define FRONTEND 1
13 #include "postgres.h"
14 
15 #include <dirent.h>
16 #include <signal.h>
17 #include <sys/stat.h>
18 #include <unistd.h>
19 
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"
28 
29 static const char *progname;
30 
31 static int WalSegSz;
32 static volatile sig_atomic_t time_to_stop = false;
33 
34 typedef struct XLogDumpPrivate
35 {
41 
42 typedef struct XLogDumpConfig
43 {
44  /* display options */
45  bool quiet;
49  bool follow;
50  bool stats;
52 
53  /* filter options */
59 
60 typedef struct Stats
61 {
62  uint64 count;
63  uint64 rec_len;
64  uint64 fpi_len;
66 
67 #define MAX_XLINFO_TYPES 16
68 
69 typedef struct XLogDumpStats
70 {
71  uint64 count;
77 
78 #define fatal_error(...) do { pg_log_fatal(__VA_ARGS__); exit(EXIT_FAILURE); } while(0)
79 
80 /*
81  * When sigint is called, just tell the system to exit at the next possible
82  * moment.
83  */
84 #ifndef WIN32
85 
86 static void
87 sigint_handler(int signum)
88 {
89  time_to_stop = true;
90 }
91 #endif
92 
93 static void
95 {
96  int i;
97 
98  for (i = 0; i <= RM_MAX_ID; i++)
99  {
100  printf("%s\n", RmgrDescTable[i].rm_name);
101  }
102 }
103 
104 /*
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.
107  */
108 static bool
110 {
111  DIR *dir = opendir(directory);
112 
113  if (dir == NULL)
114  return false;
115  closedir(dir);
116  return true;
117 }
118 
119 /*
120  * Split a pathname as dirname(1) and basename(1) would.
121  *
122  * XXX this probably doesn't do very well on Windows. We probably need to
123  * apply canonicalize_path(), at the very least.
124  */
125 static void
126 split_path(const char *path, char **dir, char **fname)
127 {
128  char *sep;
129 
130  /* split filepath into directory & filename */
131  sep = strrchr(path, '/');
132 
133  /* directory path */
134  if (sep != NULL)
135  {
136  *dir = pnstrdup(path, sep - path);
137  *fname = pg_strdup(sep + 1);
138  }
139  /* local directory */
140  else
141  {
142  *dir = NULL;
143  *fname = pg_strdup(path);
144  }
145 }
146 
147 /*
148  * Open the file in the valid target directory.
149  *
150  * return a read only fd
151  */
152 static int
153 open_file_in_directory(const char *directory, const char *fname)
154 {
155  int fd = -1;
156  char fpath[MAXPGPATH];
157 
158  Assert(directory != NULL);
159 
160  snprintf(fpath, MAXPGPATH, "%s/%s", directory, fname);
161  fd = open(fpath, O_RDONLY | PG_BINARY, 0);
162 
163  if (fd < 0 && errno != ENOENT)
164  fatal_error("could not open file \"%s\": %m", fname);
165  return fd;
166 }
167 
168 /*
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.
173  */
174 static bool
175 search_directory(const char *directory, const char *fname)
176 {
177  int fd = -1;
178  DIR *xldir;
179 
180  /* open file if valid filename is provided */
181  if (fname != NULL)
183 
184  /*
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.
188  */
189  else if ((xldir = opendir(directory)) != NULL)
190  {
191  struct dirent *xlde;
192 
193  while ((xlde = readdir(xldir)) != NULL)
194  {
195  if (IsXLogFileName(xlde->d_name))
196  {
198  fname = xlde->d_name;
199  break;
200  }
201  }
202 
203  closedir(xldir);
204  }
205 
206  /* set WalSegSz if file is successfully opened */
207  if (fd >= 0)
208  {
210  int r;
211 
212  r = read(fd, buf.data, XLOG_BLCKSZ);
213  if (r == XLOG_BLCKSZ)
214  {
215  XLogLongPageHeader longhdr = (XLogLongPageHeader) buf.data;
216 
217  WalSegSz = longhdr->xlp_seg_size;
218 
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);
224  }
225  else
226  {
227  if (errno != 0)
228  fatal_error("could not read file \"%s\": %m",
229  fname);
230  else
231  fatal_error("could not read file \"%s\": read %d of %d",
232  fname, r, XLOG_BLCKSZ);
233  }
234  close(fd);
235  return true;
236  }
237 
238  return false;
239 }
240 
241 /*
242  * Identify the target directory.
243  *
244  * Try to find the file in several places:
245  * if directory != NULL:
246  * directory /
247  * directory / XLOGDIR /
248  * else
249  * .
250  * XLOGDIR /
251  * $PGDATA / XLOGDIR /
252  *
253  * The valid target directory is returned.
254  */
255 static char *
257 {
258  char fpath[MAXPGPATH];
259 
260  if (directory != NULL)
261  {
262  if (search_directory(directory, fname))
263  return pg_strdup(directory);
264 
265  /* directory / XLOGDIR */
266  snprintf(fpath, MAXPGPATH, "%s/%s", directory, XLOGDIR);
267  if (search_directory(fpath, fname))
268  return pg_strdup(fpath);
269  }
270  else
271  {
272  const char *datadir;
273 
274  /* current directory */
275  if (search_directory(".", fname))
276  return pg_strdup(".");
277  /* XLOGDIR */
278  if (search_directory(XLOGDIR, fname))
279  return pg_strdup(XLOGDIR);
280 
281  datadir = getenv("PGDATA");
282  /* $PGDATA / XLOGDIR */
283  if (datadir != NULL)
284  {
285  snprintf(fpath, MAXPGPATH, "%s/%s", datadir, XLOGDIR);
286  if (search_directory(fpath, fname))
287  return pg_strdup(fpath);
288  }
289  }
290 
291  /* could not locate WAL file */
292  if (fname)
293  fatal_error("could not locate WAL file \"%s\"", fname);
294  else
295  fatal_error("could not find any WAL file");
296 
297  return NULL; /* not reached */
298 }
299 
300 /* pg_waldump's XLogReaderRoutine->segment_open callback */
301 static void
303  TimeLineID *tli_p)
304 {
305  TimeLineID tli = *tli_p;
306  char fname[MAXPGPATH];
307  int tries;
308 
309  XLogFileName(fname, tli, nextSegNo, state->segcxt.ws_segsize);
310 
311  /*
312  * In follow mode there is a short period of time after the server has
313  * written the end of the previous file before the new file is available.
314  * So we loop for 5 seconds looking for the file to appear before giving
315  * up.
316  */
317  for (tries = 0; tries < 10; tries++)
318  {
319  state->seg.ws_file = open_file_in_directory(state->segcxt.ws_dir, fname);
320  if (state->seg.ws_file >= 0)
321  return;
322  if (errno == ENOENT)
323  {
324  int save_errno = errno;
325 
326  /* File not there yet, try again */
327  pg_usleep(500 * 1000);
328 
329  errno = save_errno;
330  continue;
331  }
332  /* Any other error, fall through and fail */
333  break;
334  }
335 
336  fatal_error("could not find file \"%s\": %m", fname);
337 }
338 
339 /*
340  * pg_waldump's XLogReaderRoutine->segment_close callback. Same as
341  * wal_segment_close
342  */
343 static void
345 {
346  close(state->seg.ws_file);
347  /* need to check errno? */
348  state->seg.ws_file = -1;
349 }
350 
351 /* pg_waldump's XLogReaderRoutine->page_read callback */
352 static int
353 WALDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
354  XLogRecPtr targetPtr, char *readBuff)
355 {
356  XLogDumpPrivate *private = state->private_data;
357  int count = XLOG_BLCKSZ;
358  WALReadError errinfo;
359 
360  if (private->endptr != InvalidXLogRecPtr)
361  {
362  if (targetPagePtr + XLOG_BLCKSZ <= private->endptr)
363  count = XLOG_BLCKSZ;
364  else if (targetPagePtr + reqLen <= private->endptr)
365  count = private->endptr - targetPagePtr;
366  else
367  {
368  private->endptr_reached = true;
369  return -1;
370  }
371  }
372 
373  if (!WALRead(state, readBuff, targetPagePtr, count, private->timeline,
374  &errinfo))
375  {
376  WALOpenSegment *seg = &errinfo.wre_seg;
377  char fname[MAXPGPATH];
378 
379  XLogFileName(fname, seg->ws_tli, seg->ws_segno,
380  state->segcxt.ws_segsize);
381 
382  if (errinfo.wre_errno != 0)
383  {
384  errno = errinfo.wre_errno;
385  fatal_error("could not read from file %s, offset %d: %m",
386  fname, errinfo.wre_off);
387  }
388  else
389  fatal_error("could not read from file %s, offset %d: read %d of %d",
390  fname, errinfo.wre_off, errinfo.wre_read,
391  errinfo.wre_req);
392  }
393 
394  return count;
395 }
396 
397 /*
398  * Calculate the size of a record, split into !FPI and FPI parts.
399  */
400 static void
401 XLogDumpRecordLen(XLogReaderState *record, uint32 *rec_len, uint32 *fpi_len)
402 {
403  int block_id;
404 
405  /*
406  * Calculate the amount of FPI data in the record.
407  *
408  * XXX: We peek into xlogreader's private decoded backup blocks for the
409  * bimg_len indicating the length of FPI data. It doesn't seem worth it to
410  * add an accessor macro for this.
411  */
412  *fpi_len = 0;
413  for (block_id = 0; block_id <= record->max_block_id; block_id++)
414  {
415  if (XLogRecHasBlockImage(record, block_id))
416  *fpi_len += record->blocks[block_id].bimg_len;
417  }
418 
419  /*
420  * Calculate the length of the record as the total length - the length of
421  * all the block images.
422  */
423  *rec_len = XLogRecGetTotalLen(record) - *fpi_len;
424 }
425 
426 /*
427  * Store per-rmgr and per-record statistics for a given record.
428  */
429 static void
431  XLogReaderState *record)
432 {
433  RmgrId rmid;
434  uint8 recid;
435  uint32 rec_len;
436  uint32 fpi_len;
437 
438  stats->count++;
439 
440  rmid = XLogRecGetRmid(record);
441 
442  XLogDumpRecordLen(record, &rec_len, &fpi_len);
443 
444  /* Update per-rmgr statistics */
445 
446  stats->rmgr_stats[rmid].count++;
447  stats->rmgr_stats[rmid].rec_len += rec_len;
448  stats->rmgr_stats[rmid].fpi_len += fpi_len;
449 
450  /*
451  * Update per-record statistics, where the record is identified by a
452  * combination of the RmgrId and the four bits of the xl_info field that
453  * are the rmgr's domain (resulting in sixteen possible entries per
454  * RmgrId).
455  */
456 
457  recid = XLogRecGetInfo(record) >> 4;
458 
459  /*
460  * XACT records need to be handled differently. Those records use the
461  * first bit of those four bits for an optional flag variable and the
462  * following three bits for the opcode. We filter opcode out of xl_info
463  * and use it as the identifier of the record.
464  */
465  if (rmid == RM_XACT_ID)
466  recid &= 0x07;
467 
468  stats->record_stats[rmid][recid].count++;
469  stats->record_stats[rmid][recid].rec_len += rec_len;
470  stats->record_stats[rmid][recid].fpi_len += fpi_len;
471 }
472 
473 /*
474  * Print a record to stdout
475  */
476 static void
478 {
479  const char *id;
480  const RmgrDescData *desc = &RmgrDescTable[XLogRecGetRmid(record)];
481  uint32 rec_len;
482  uint32 fpi_len;
483  RelFileNode rnode;
484  ForkNumber forknum;
485  BlockNumber blk;
486  int block_id;
487  uint8 info = XLogRecGetInfo(record);
488  XLogRecPtr xl_prev = XLogRecGetPrev(record);
489  StringInfoData s;
490 
491  XLogDumpRecordLen(record, &rec_len, &fpi_len);
492 
493  printf("rmgr: %-11s len (rec/tot): %6u/%6u, tx: %10u, lsn: %X/%08X, prev %X/%08X, ",
494  desc->rm_name,
495  rec_len, XLogRecGetTotalLen(record),
496  XLogRecGetXid(record),
497  LSN_FORMAT_ARGS(record->ReadRecPtr),
498  LSN_FORMAT_ARGS(xl_prev));
499 
500  id = desc->rm_identify(info);
501  if (id == NULL)
502  printf("desc: UNKNOWN (%x) ", info & ~XLR_INFO_MASK);
503  else
504  printf("desc: %s ", id);
505 
506  initStringInfo(&s);
507  desc->rm_desc(&s, record);
508  printf("%s", s.data);
509  pfree(s.data);
510 
511  if (!config->bkp_details)
512  {
513  /* print block references (short format) */
514  for (block_id = 0; block_id <= record->max_block_id; block_id++)
515  {
516  if (!XLogRecHasBlockRef(record, block_id))
517  continue;
518 
519  XLogRecGetBlockTag(record, block_id, &rnode, &forknum, &blk);
520  if (forknum != MAIN_FORKNUM)
521  printf(", blkref #%d: rel %u/%u/%u fork %s blk %u",
522  block_id,
523  rnode.spcNode, rnode.dbNode, rnode.relNode,
524  forkNames[forknum],
525  blk);
526  else
527  printf(", blkref #%d: rel %u/%u/%u blk %u",
528  block_id,
529  rnode.spcNode, rnode.dbNode, rnode.relNode,
530  blk);
531  if (XLogRecHasBlockImage(record, block_id))
532  {
533  if (XLogRecBlockImageApply(record, block_id))
534  printf(" FPW");
535  else
536  printf(" FPW for WAL verification");
537  }
538  }
539  putchar('\n');
540  }
541  else
542  {
543  /* print block references (detailed format) */
544  putchar('\n');
545  for (block_id = 0; block_id <= record->max_block_id; block_id++)
546  {
547  if (!XLogRecHasBlockRef(record, block_id))
548  continue;
549 
550  XLogRecGetBlockTag(record, block_id, &rnode, &forknum, &blk);
551  printf("\tblkref #%d: rel %u/%u/%u fork %s blk %u",
552  block_id,
553  rnode.spcNode, rnode.dbNode, rnode.relNode,
554  forkNames[forknum],
555  blk);
556  if (XLogRecHasBlockImage(record, block_id))
557  {
558  uint8 bimg_info = record->blocks[block_id].bimg_info;
559 
560  if (BKPIMAGE_COMPRESSED(bimg_info))
561  {
562  const char *method;
563 
564  if ((bimg_info & BKPIMAGE_COMPRESS_PGLZ) != 0)
565  method = "pglz";
566  else if ((bimg_info & BKPIMAGE_COMPRESS_LZ4) != 0)
567  method = "lz4";
568  else
569  method = "unknown";
570 
571  printf(" (FPW%s); hole: offset: %u, length: %u, "
572  "compression saved: %u, method: %s",
573  XLogRecBlockImageApply(record, block_id) ?
574  "" : " for WAL verification",
575  record->blocks[block_id].hole_offset,
576  record->blocks[block_id].hole_length,
577  BLCKSZ -
578  record->blocks[block_id].hole_length -
579  record->blocks[block_id].bimg_len,
580  method);
581  }
582  else
583  {
584  printf(" (FPW%s); hole: offset: %u, length: %u",
585  XLogRecBlockImageApply(record, block_id) ?
586  "" : " for WAL verification",
587  record->blocks[block_id].hole_offset,
588  record->blocks[block_id].hole_length);
589  }
590  }
591  putchar('\n');
592  }
593  }
594 }
595 
596 /*
597  * Display a single row of record counts and sizes for an rmgr or record.
598  */
599 static void
600 XLogDumpStatsRow(const char *name,
601  uint64 n, uint64 total_count,
602  uint64 rec_len, uint64 total_rec_len,
603  uint64 fpi_len, uint64 total_fpi_len,
604  uint64 tot_len, uint64 total_len)
605 {
606  double n_pct,
607  rec_len_pct,
608  fpi_len_pct,
609  tot_len_pct;
610 
611  n_pct = 0;
612  if (total_count != 0)
613  n_pct = 100 * (double) n / total_count;
614 
615  rec_len_pct = 0;
616  if (total_rec_len != 0)
617  rec_len_pct = 100 * (double) rec_len / total_rec_len;
618 
619  fpi_len_pct = 0;
620  if (total_fpi_len != 0)
621  fpi_len_pct = 100 * (double) fpi_len / total_fpi_len;
622 
623  tot_len_pct = 0;
624  if (total_len != 0)
625  tot_len_pct = 100 * (double) tot_len / total_len;
626 
627  printf("%-27s "
628  "%20" INT64_MODIFIER "u (%6.02f) "
629  "%20" INT64_MODIFIER "u (%6.02f) "
630  "%20" INT64_MODIFIER "u (%6.02f) "
631  "%20" INT64_MODIFIER "u (%6.02f)\n",
632  name, n, n_pct, rec_len, rec_len_pct, fpi_len, fpi_len_pct,
633  tot_len, tot_len_pct);
634 }
635 
636 
637 /*
638  * Display summary statistics about the records seen so far.
639  */
640 static void
642 {
643  int ri,
644  rj;
645  uint64 total_count = 0;
646  uint64 total_rec_len = 0;
647  uint64 total_fpi_len = 0;
648  uint64 total_len = 0;
649  double rec_len_pct,
650  fpi_len_pct;
651 
652  /*
653  * Leave if no stats have been computed yet, as tracked by the end LSN.
654  */
655  if (XLogRecPtrIsInvalid(stats->endptr))
656  return;
657 
658  /*
659  * Each row shows its percentages of the total, so make a first pass to
660  * calculate column totals.
661  */
662 
663  for (ri = 0; ri < RM_NEXT_ID; ri++)
664  {
665  total_count += stats->rmgr_stats[ri].count;
666  total_rec_len += stats->rmgr_stats[ri].rec_len;
667  total_fpi_len += stats->rmgr_stats[ri].fpi_len;
668  }
669  total_len = total_rec_len + total_fpi_len;
670 
671  printf("WAL statistics between %X/%X and %X/%X:\n",
673 
674  /*
675  * 27 is strlen("Transaction/COMMIT_PREPARED"), 20 is strlen(2^64), 8 is
676  * strlen("(100.00%)")
677  */
678 
679  printf("%-27s %20s %8s %20s %8s %20s %8s %20s %8s\n"
680  "%-27s %20s %8s %20s %8s %20s %8s %20s %8s\n",
681  "Type", "N", "(%)", "Record size", "(%)", "FPI size", "(%)", "Combined size", "(%)",
682  "----", "-", "---", "-----------", "---", "--------", "---", "-------------", "---");
683 
684  for (ri = 0; ri < RM_NEXT_ID; ri++)
685  {
686  uint64 count,
687  rec_len,
688  fpi_len,
689  tot_len;
690  const RmgrDescData *desc = &RmgrDescTable[ri];
691 
692  if (!config->stats_per_record)
693  {
694  count = stats->rmgr_stats[ri].count;
695  rec_len = stats->rmgr_stats[ri].rec_len;
696  fpi_len = stats->rmgr_stats[ri].fpi_len;
697  tot_len = rec_len + fpi_len;
698 
700  count, total_count, rec_len, total_rec_len,
701  fpi_len, total_fpi_len, tot_len, total_len);
702  }
703  else
704  {
705  for (rj = 0; rj < MAX_XLINFO_TYPES; rj++)
706  {
707  const char *id;
708 
709  count = stats->record_stats[ri][rj].count;
710  rec_len = stats->record_stats[ri][rj].rec_len;
711  fpi_len = stats->record_stats[ri][rj].fpi_len;
712  tot_len = rec_len + fpi_len;
713 
714  /* Skip undefined combinations and ones that didn't occur */
715  if (count == 0)
716  continue;
717 
718  /* the upper four bits in xl_info are the rmgr's */
719  id = desc->rm_identify(rj << 4);
720  if (id == NULL)
721  id = psprintf("UNKNOWN (%x)", rj << 4);
722 
723  XLogDumpStatsRow(psprintf("%s/%s", desc->rm_name, id),
724  count, total_count, rec_len, total_rec_len,
725  fpi_len, total_fpi_len, tot_len, total_len);
726  }
727  }
728  }
729 
730  printf("%-27s %20s %8s %20s %8s %20s %8s %20s\n",
731  "", "--------", "", "--------", "", "--------", "", "--------");
732 
733  /*
734  * The percentages in earlier rows were calculated against the column
735  * total, but the ones that follow are against the row total. Note that
736  * these are displayed with a % symbol to differentiate them from the
737  * earlier ones, and are thus up to 9 characters long.
738  */
739 
740  rec_len_pct = 0;
741  if (total_len != 0)
742  rec_len_pct = 100 * (double) total_rec_len / total_len;
743 
744  fpi_len_pct = 0;
745  if (total_len != 0)
746  fpi_len_pct = 100 * (double) total_fpi_len / total_len;
747 
748  printf("%-27s "
749  "%20" INT64_MODIFIER "u %-9s"
750  "%20" INT64_MODIFIER "u %-9s"
751  "%20" INT64_MODIFIER "u %-9s"
752  "%20" INT64_MODIFIER "u %-6s\n",
753  "Total", stats->count, "",
754  total_rec_len, psprintf("[%.02f%%]", rec_len_pct),
755  total_fpi_len, psprintf("[%.02f%%]", fpi_len_pct),
756  total_len, "[100%]");
757 }
758 
759 static void
760 usage(void)
761 {
762  printf(_("%s decodes and displays PostgreSQL write-ahead logs for debugging.\n\n"),
763  progname);
764  printf(_("Usage:\n"));
765  printf(_(" %s [OPTION]... [STARTSEG [ENDSEG]]\n"), progname);
766  printf(_("\nOptions:\n"));
767  printf(_(" -b, --bkp-details output detailed information about backup blocks\n"));
768  printf(_(" -e, --end=RECPTR stop reading at WAL location RECPTR\n"));
769  printf(_(" -f, --follow keep retrying after reaching end of WAL\n"));
770  printf(_(" -n, --limit=N number of records to display\n"));
771  printf(_(" -p, --path=PATH directory in which to find log segment files or a\n"
772  " directory with a ./pg_wal that contains such files\n"
773  " (default: current directory, ./pg_wal, $PGDATA/pg_wal)\n"));
774  printf(_(" -q, --quiet do not print any output, except for errors\n"));
775  printf(_(" -r, --rmgr=RMGR only show records generated by resource manager RMGR;\n"
776  " use --rmgr=list to list valid resource manager names\n"));
777  printf(_(" -s, --start=RECPTR start reading at WAL location RECPTR\n"));
778  printf(_(" -t, --timeline=TLI timeline from which to read log records\n"
779  " (default: 1 or the value used in STARTSEG)\n"));
780  printf(_(" -V, --version output version information, then exit\n"));
781  printf(_(" -x, --xid=XID only show records with transaction ID XID\n"));
782  printf(_(" -z, --stats[=record] show statistics instead of records\n"
783  " (optionally, show per-record statistics)\n"));
784  printf(_(" -?, --help show this help, then exit\n"));
785  printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
786  printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
787 }
788 
789 int
790 main(int argc, char **argv)
791 {
792  uint32 xlogid;
793  uint32 xrecoff;
794  XLogReaderState *xlogreader_state;
795  XLogDumpPrivate private;
796  XLogDumpConfig config;
797  XLogDumpStats stats;
798  XLogRecord *record;
799  XLogRecPtr first_record;
800  char *waldir = NULL;
801  char *errormsg;
802 
803  static struct option long_options[] = {
804  {"bkp-details", no_argument, NULL, 'b'},
805  {"end", required_argument, NULL, 'e'},
806  {"follow", no_argument, NULL, 'f'},
807  {"help", no_argument, NULL, '?'},
808  {"limit", required_argument, NULL, 'n'},
809  {"path", required_argument, NULL, 'p'},
810  {"quiet", no_argument, NULL, 'q'},
811  {"rmgr", required_argument, NULL, 'r'},
812  {"start", required_argument, NULL, 's'},
813  {"timeline", required_argument, NULL, 't'},
814  {"xid", required_argument, NULL, 'x'},
815  {"version", no_argument, NULL, 'V'},
816  {"stats", optional_argument, NULL, 'z'},
817  {NULL, 0, NULL, 0}
818  };
819 
820  int option;
821  int optindex = 0;
822 
823 #ifndef WIN32
824  pqsignal(SIGINT, sigint_handler);
825 #endif
826 
827  pg_logging_init(argv[0]);
828  set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_waldump"));
829  progname = get_progname(argv[0]);
830 
831  if (argc > 1)
832  {
833  if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
834  {
835  usage();
836  exit(0);
837  }
838  if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
839  {
840  puts("pg_waldump (PostgreSQL) " PG_VERSION);
841  exit(0);
842  }
843  }
844 
845  memset(&private, 0, sizeof(XLogDumpPrivate));
846  memset(&config, 0, sizeof(XLogDumpConfig));
847  memset(&stats, 0, sizeof(XLogDumpStats));
848 
849  private.timeline = 1;
850  private.startptr = InvalidXLogRecPtr;
851  private.endptr = InvalidXLogRecPtr;
852  private.endptr_reached = false;
853 
854  config.quiet = false;
855  config.bkp_details = false;
856  config.stop_after_records = -1;
857  config.already_displayed_records = 0;
858  config.follow = false;
859  /* filter_by_rmgr array was zeroed by memset above */
860  config.filter_by_rmgr_enabled = false;
862  config.filter_by_xid_enabled = false;
863  config.stats = false;
864  config.stats_per_record = false;
865 
866  stats.startptr = InvalidXLogRecPtr;
867  stats.endptr = InvalidXLogRecPtr;
868 
869  if (argc <= 1)
870  {
871  pg_log_error("no arguments specified");
872  goto bad_argument;
873  }
874 
875  while ((option = getopt_long(argc, argv, "be:fn:p:qr:s:t:x:z",
876  long_options, &optindex)) != -1)
877  {
878  switch (option)
879  {
880  case 'b':
881  config.bkp_details = true;
882  break;
883  case 'e':
884  if (sscanf(optarg, "%X/%X", &xlogid, &xrecoff) != 2)
885  {
886  pg_log_error("could not parse end WAL location \"%s\"",
887  optarg);
888  goto bad_argument;
889  }
890  private.endptr = (uint64) xlogid << 32 | xrecoff;
891  break;
892  case 'f':
893  config.follow = true;
894  break;
895  case 'n':
896  if (sscanf(optarg, "%d", &config.stop_after_records) != 1)
897  {
898  pg_log_error("could not parse limit \"%s\"", optarg);
899  goto bad_argument;
900  }
901  break;
902  case 'p':
903  waldir = pg_strdup(optarg);
904  break;
905  case 'q':
906  config.quiet = true;
907  break;
908  case 'r':
909  {
910  int i;
911 
912  if (pg_strcasecmp(optarg, "list") == 0)
913  {
914  print_rmgr_list();
916  }
917 
918  for (i = 0; i <= RM_MAX_ID; i++)
919  {
920  if (pg_strcasecmp(optarg, RmgrDescTable[i].rm_name) == 0)
921  {
922  config.filter_by_rmgr[i] = true;
923  config.filter_by_rmgr_enabled = true;
924  break;
925  }
926  }
927  if (i > RM_MAX_ID)
928  {
929  pg_log_error("resource manager \"%s\" does not exist",
930  optarg);
931  goto bad_argument;
932  }
933  }
934  break;
935  case 's':
936  if (sscanf(optarg, "%X/%X", &xlogid, &xrecoff) != 2)
937  {
938  pg_log_error("could not parse start WAL location \"%s\"",
939  optarg);
940  goto bad_argument;
941  }
942  else
943  private.startptr = (uint64) xlogid << 32 | xrecoff;
944  break;
945  case 't':
946  if (sscanf(optarg, "%u", &private.timeline) != 1)
947  {
948  pg_log_error("could not parse timeline \"%s\"", optarg);
949  goto bad_argument;
950  }
951  break;
952  case 'x':
953  if (sscanf(optarg, "%u", &config.filter_by_xid) != 1)
954  {
955  pg_log_error("could not parse \"%s\" as a transaction ID",
956  optarg);
957  goto bad_argument;
958  }
959  config.filter_by_xid_enabled = true;
960  break;
961  case 'z':
962  config.stats = true;
963  config.stats_per_record = false;
964  if (optarg)
965  {
966  if (strcmp(optarg, "record") == 0)
967  config.stats_per_record = true;
968  else if (strcmp(optarg, "rmgr") != 0)
969  {
970  pg_log_error("unrecognized argument to --stats: %s",
971  optarg);
972  goto bad_argument;
973  }
974  }
975  break;
976  default:
977  goto bad_argument;
978  }
979  }
980 
981  if ((optind + 2) < argc)
982  {
983  pg_log_error("too many command-line arguments (first is \"%s\")",
984  argv[optind + 2]);
985  goto bad_argument;
986  }
987 
988  if (waldir != NULL)
989  {
990  /* validate path points to directory */
991  if (!verify_directory(waldir))
992  {
993  pg_log_error("could not open directory \"%s\": %m", waldir);
994  goto bad_argument;
995  }
996  }
997 
998  /* parse files as start/end boundaries, extract path if not specified */
999  if (optind < argc)
1000  {
1001  char *directory = NULL;
1002  char *fname = NULL;
1003  int fd;
1004  XLogSegNo segno;
1005 
1006  split_path(argv[optind], &directory, &fname);
1007 
1008  if (waldir == NULL && directory != NULL)
1009  {
1010  waldir = directory;
1011 
1012  if (!verify_directory(waldir))
1013  fatal_error("could not open directory \"%s\": %m", waldir);
1014  }
1015 
1016  waldir = identify_target_directory(waldir, fname);
1017  fd = open_file_in_directory(waldir, fname);
1018  if (fd < 0)
1019  fatal_error("could not open file \"%s\"", fname);
1020  close(fd);
1021 
1022  /* parse position from file */
1023  XLogFromFileName(fname, &private.timeline, &segno, WalSegSz);
1024 
1025  if (XLogRecPtrIsInvalid(private.startptr))
1026  XLogSegNoOffsetToRecPtr(segno, 0, WalSegSz, private.startptr);
1027  else if (!XLByteInSeg(private.startptr, segno, WalSegSz))
1028  {
1029  pg_log_error("start WAL location %X/%X is not inside file \"%s\"",
1030  LSN_FORMAT_ARGS(private.startptr),
1031  fname);
1032  goto bad_argument;
1033  }
1034 
1035  /* no second file specified, set end position */
1036  if (!(optind + 1 < argc) && XLogRecPtrIsInvalid(private.endptr))
1037  XLogSegNoOffsetToRecPtr(segno + 1, 0, WalSegSz, private.endptr);
1038 
1039  /* parse ENDSEG if passed */
1040  if (optind + 1 < argc)
1041  {
1042  XLogSegNo endsegno;
1043 
1044  /* ignore directory, already have that */
1045  split_path(argv[optind + 1], &directory, &fname);
1046 
1047  fd = open_file_in_directory(waldir, fname);
1048  if (fd < 0)
1049  fatal_error("could not open file \"%s\"", fname);
1050  close(fd);
1051 
1052  /* parse position from file */
1053  XLogFromFileName(fname, &private.timeline, &endsegno, WalSegSz);
1054 
1055  if (endsegno < segno)
1056  fatal_error("ENDSEG %s is before STARTSEG %s",
1057  argv[optind + 1], argv[optind]);
1058 
1059  if (XLogRecPtrIsInvalid(private.endptr))
1060  XLogSegNoOffsetToRecPtr(endsegno + 1, 0, WalSegSz,
1061  private.endptr);
1062 
1063  /* set segno to endsegno for check of --end */
1064  segno = endsegno;
1065  }
1066 
1067 
1068  if (!XLByteInSeg(private.endptr, segno, WalSegSz) &&
1069  private.endptr != (segno + 1) * WalSegSz)
1070  {
1071  pg_log_error("end WAL location %X/%X is not inside file \"%s\"",
1072  LSN_FORMAT_ARGS(private.endptr),
1073  argv[argc - 1]);
1074  goto bad_argument;
1075  }
1076  }
1077  else
1078  waldir = identify_target_directory(waldir, NULL);
1079 
1080  /* we don't know what to print */
1081  if (XLogRecPtrIsInvalid(private.startptr))
1082  {
1083  pg_log_error("no start WAL location given");
1084  goto bad_argument;
1085  }
1086 
1087  /* done with argument parsing, do the actual work */
1088 
1089  /* we have everything we need, start reading */
1090  xlogreader_state =
1091  XLogReaderAllocate(WalSegSz, waldir,
1092  XL_ROUTINE(.page_read = WALDumpReadPage,
1093  .segment_open = WALDumpOpenSegment,
1094  .segment_close = WALDumpCloseSegment),
1095  &private);
1096  if (!xlogreader_state)
1097  fatal_error("out of memory while allocating a WAL reading processor");
1098 
1099  /* first find a valid recptr to start from */
1100  first_record = XLogFindNextRecord(xlogreader_state, private.startptr);
1101 
1102  if (first_record == InvalidXLogRecPtr)
1103  fatal_error("could not find a valid record after %X/%X",
1104  LSN_FORMAT_ARGS(private.startptr));
1105 
1106  /*
1107  * Display a message that we're skipping data if `from` wasn't a pointer
1108  * to the start of a record and also wasn't a pointer to the beginning of
1109  * a segment (e.g. we were used in file mode).
1110  */
1111  if (first_record != private.startptr &&
1112  XLogSegmentOffset(private.startptr, WalSegSz) != 0)
1113  printf(ngettext("first record is after %X/%X, at %X/%X, skipping over %u byte\n",
1114  "first record is after %X/%X, at %X/%X, skipping over %u bytes\n",
1115  (first_record - private.startptr)),
1116  LSN_FORMAT_ARGS(private.startptr),
1117  LSN_FORMAT_ARGS(first_record),
1118  (uint32) (first_record - private.startptr));
1119 
1120  if (config.stats == true && !config.quiet)
1121  stats.startptr = first_record;
1122 
1123  for (;;)
1124  {
1125  if (time_to_stop)
1126  {
1127  /* We've been Ctrl-C'ed, so leave */
1128  break;
1129  }
1130 
1131  /* try to read the next record */
1132  record = XLogReadRecord(xlogreader_state, &errormsg);
1133  if (!record)
1134  {
1135  if (!config.follow || private.endptr_reached)
1136  break;
1137  else
1138  {
1139  pg_usleep(1000000L); /* 1 second */
1140  continue;
1141  }
1142  }
1143 
1144  /* apply all specified filters */
1145  if (config.filter_by_rmgr_enabled &&
1146  !config.filter_by_rmgr[record->xl_rmid])
1147  continue;
1148 
1149  if (config.filter_by_xid_enabled &&
1150  config.filter_by_xid != record->xl_xid)
1151  continue;
1152 
1153  /* perform any per-record work */
1154  if (!config.quiet)
1155  {
1156  if (config.stats == true)
1157  {
1158  XLogDumpCountRecord(&config, &stats, xlogreader_state);
1159  stats.endptr = xlogreader_state->EndRecPtr;
1160  }
1161  else
1162  XLogDumpDisplayRecord(&config, xlogreader_state);
1163  }
1164 
1165  /* check whether we printed enough */
1166  config.already_displayed_records++;
1167  if (config.stop_after_records > 0 &&
1169  break;
1170  }
1171 
1172  if (config.stats == true && !config.quiet)
1173  XLogDumpDisplayStats(&config, &stats);
1174 
1175  if (time_to_stop)
1176  exit(0);
1177 
1178  if (errormsg)
1179  fatal_error("error in WAL record at %X/%X: %s",
1180  LSN_FORMAT_ARGS(xlogreader_state->ReadRecPtr),
1181  errormsg);
1182 
1183  XLogReaderFree(xlogreader_state);
1184 
1185  return EXIT_SUCCESS;
1186 
1187 bad_argument:
1188  fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
1189  return EXIT_FAILURE;
1190 }
uint32 BlockNumber
Definition: block.h:31
unsigned int uint32
Definition: c.h:441
#define ngettext(s, p, n)
Definition: c.h:1179
#define PG_TEXTDOMAIN(domain)
Definition: c.h:1212
#define PG_BINARY
Definition: c.h:1268
unsigned char uint8
Definition: c.h:439
uint32 TransactionId
Definition: c.h:587
void set_pglocale_pgservice(const char *argv0, const char *app)
Definition: exec.c:441
int closedir(DIR *)
Definition: dirent.c:123
struct dirent * readdir(DIR *)
Definition: dirent.c:78
DIR * opendir(const char *)
Definition: dirent.c:33
#define _(x)
Definition: elog.c:89
const char * name
Definition: encode.c:561
char * pg_strdup(const char *in)
Definition: fe_memutils.c:85
int getopt_long(int argc, char *const argv[], const char *optstring, const struct option *longopts, int *longindex)
Definition: getopt_long.c:57
#define no_argument
Definition: getopt_long.h:24
#define required_argument
Definition: getopt_long.h:25
#define optional_argument
Definition: getopt_long.h:26
#define close(a)
Definition: win32.h:12
#define read(a, b, c)
Definition: win32.h:13
int i
Definition: isn.c:73
Assert(fmt[strlen(fmt) - 1] !='\n')
exit(1)
void pg_logging_init(const char *argv0)
Definition: logging.c:81
#define pg_log_error(...)
Definition: logging.h:80
char * pnstrdup(const char *in, Size len)
Definition: mcxt.c:1310
void pfree(void *pointer)
Definition: mcxt.c:1169
#define MAXPGPATH
int optind
Definition: getopt.c:50
char * optarg
Definition: getopt.c:52
char * datadir
static char * buf
Definition: pg_test_fsync.c:70
static bool search_directory(const char *directory, const char *fname)
Definition: pg_waldump.c:175
static int WalSegSz
Definition: pg_waldump.c:31
static void WALDumpCloseSegment(XLogReaderState *state)
Definition: pg_waldump.c:344
static void sigint_handler(int signum)
Definition: pg_waldump.c:87
int main(int argc, char **argv)
Definition: pg_waldump.c:790
static volatile sig_atomic_t time_to_stop
Definition: pg_waldump.c:32
static void split_path(const char *path, char **dir, char **fname)
Definition: pg_waldump.c:126
#define fatal_error(...)
Definition: pg_waldump.c:78
static void XLogDumpDisplayStats(XLogDumpConfig *config, XLogDumpStats *stats)
Definition: pg_waldump.c:641
struct XLogDumpConfig XLogDumpConfig
static int open_file_in_directory(const char *directory, const char *fname)
Definition: pg_waldump.c:153
static void XLogDumpRecordLen(XLogReaderState *record, uint32 *rec_len, uint32 *fpi_len)
Definition: pg_waldump.c:401
static void XLogDumpDisplayRecord(XLogDumpConfig *config, XLogReaderState *record)
Definition: pg_waldump.c:477
static void XLogDumpStatsRow(const char *name, uint64 n, uint64 total_count, uint64 rec_len, uint64 total_rec_len, uint64 fpi_len, uint64 total_fpi_len, uint64 tot_len, uint64 total_len)
Definition: pg_waldump.c:600
#define MAX_XLINFO_TYPES
Definition: pg_waldump.c:67
static bool verify_directory(const char *directory)
Definition: pg_waldump.c:109
struct Stats Stats
static void print_rmgr_list(void)
Definition: pg_waldump.c:94
static void WALDumpOpenSegment(XLogReaderState *state, XLogSegNo nextSegNo, TimeLineID *tli_p)
Definition: pg_waldump.c:302
struct XLogDumpPrivate XLogDumpPrivate
static const char * progname
Definition: pg_waldump.c:29
struct XLogDumpStats XLogDumpStats
static void usage(void)
Definition: pg_waldump.c:760
static int WALDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen, XLogRecPtr targetPtr, char *readBuff)
Definition: pg_waldump.c:353
static char * identify_target_directory(char *directory, char *fname)
Definition: pg_waldump.c:256
static void XLogDumpCountRecord(XLogDumpConfig *config, XLogDumpStats *stats, XLogReaderState *record)
Definition: pg_waldump.c:430
int pg_strcasecmp(const char *s1, const char *s2)
Definition: pgstrcasecmp.c:36
const char * get_progname(const char *argv0)
Definition: path.c:453
#define snprintf
Definition: port.h:225
#define fprintf
Definition: port.h:229
#define printf(...)
Definition: port.h:231
static int fd(const char *x, int i)
Definition: preproc-init.c:105
char * psprintf(const char *fmt,...)
Definition: psprintf.c:46
const char *const forkNames[]
Definition: relpath.c:33
ForkNumber
Definition: relpath.h:41
@ MAIN_FORKNUM
Definition: relpath.h:43
@ RM_NEXT_ID
Definition: rmgr.h:28
#define RM_MAX_ID
Definition: rmgr.h:33
uint8 RmgrId
Definition: rmgr.h:11
const RmgrDescData RmgrDescTable[RM_MAX_ID+1]
Definition: rmgrdesc.c:38
#define EXIT_SUCCESS
Definition: settings.h:158
#define EXIT_FAILURE
Definition: settings.h:162
void pg_usleep(long microsec)
Definition: signal.c:53
pqsigfunc pqsignal(int signum, pqsigfunc handler)
Definition: signal.c:180
void initStringInfo(StringInfo str)
Definition: stringinfo.c:59
Definition: dirent.c:26
uint16 hole_length
Definition: xlogreader.h:136
uint16 hole_offset
Definition: xlogreader.h:135
const char *(* rm_identify)(uint8 info)
Definition: rmgrdesc.h:18
const char * rm_name
Definition: rmgrdesc.h:16
void(* rm_desc)(StringInfo buf, XLogReaderState *record)
Definition: rmgrdesc.h:17
uint64 fpi_len
Definition: pg_waldump.c:64
uint64 rec_len
Definition: pg_waldump.c:63
uint64 count
Definition: pg_waldump.c:62
XLogSegNo ws_segno
Definition: xlogreader.h:47
TimeLineID ws_tli
Definition: xlogreader.h:48
WALOpenSegment wre_seg
Definition: xlogreader.h:301
bool filter_by_xid_enabled
Definition: pg_waldump.c:57
bool bkp_details
Definition: pg_waldump.c:46
bool filter_by_rmgr[RM_MAX_ID+1]
Definition: pg_waldump.c:54
bool stats_per_record
Definition: pg_waldump.c:51
int already_displayed_records
Definition: pg_waldump.c:48
int stop_after_records
Definition: pg_waldump.c:47
bool filter_by_rmgr_enabled
Definition: pg_waldump.c:55
TransactionId filter_by_xid
Definition: pg_waldump.c:56
XLogRecPtr endptr
Definition: pg_waldump.c:38
XLogRecPtr startptr
Definition: pg_waldump.c:37
TimeLineID timeline
Definition: pg_waldump.c:36
bool endptr_reached
Definition: pg_waldump.c:39
Stats record_stats[RM_NEXT_ID][MAX_XLINFO_TYPES]
Definition: pg_waldump.c:75
XLogRecPtr startptr
Definition: pg_waldump.c:72
XLogRecPtr endptr
Definition: pg_waldump.c:73
Stats rmgr_stats[RM_NEXT_ID]
Definition: pg_waldump.c:74
uint64 count
Definition: pg_waldump.c:71
XLogRecPtr EndRecPtr
Definition: xlogreader.h:176
DecodedBkpBlock blocks[XLR_MAX_BLOCK_ID+1]
Definition: xlogreader.h:207
XLogRecPtr ReadRecPtr
Definition: xlogreader.h:175
TransactionId xl_xid
Definition: xlogrecord.h:44
RmgrId xl_rmid
Definition: xlogrecord.h:47
Definition: dirent.h:10
char d_name[MAX_PATH]
Definition: dirent.h:15
Definition: regguts.h:318
#define InvalidTransactionId
Definition: transam.h:31
#define IsValidWalSegSize(size)
Definition: xlog_internal.h:96
XLogLongPageHeaderData * XLogLongPageHeader
Definition: xlog_internal.h:71
#define XLogSegmentOffset(xlogptr, wal_segsz_bytes)
#define XLogFileName(fname, tli, logSegNo, wal_segsz_bytes)
#define IsXLogFileName(fname)
#define XLogSegNoOffsetToRecPtr(segno, offset, wal_segsz_bytes, dest)
#define XLOGDIR
#define XLByteInSeg(xlrp, logSegNo, wal_segsz_bytes)
#define XLogFromFileName(fname, tli, logSegNo, wal_segsz_bytes)
#define LSN_FORMAT_ARGS(lsn)
Definition: xlogdefs.h:43
#define XLogRecPtrIsInvalid(r)
Definition: xlogdefs.h:29
uint64 XLogRecPtr
Definition: xlogdefs.h:21
#define InvalidXLogRecPtr
Definition: xlogdefs.h:28
uint32 TimeLineID
Definition: xlogdefs.h:59
uint64 XLogSegNo
Definition: xlogdefs.h:48
XLogRecord * XLogReadRecord(XLogReaderState *state, char **errormsg)
Definition: xlogreader.c:271
bool XLogRecGetBlockTag(XLogReaderState *record, uint8 block_id, RelFileNode *rnode, ForkNumber *forknum, BlockNumber *blknum)
Definition: xlogreader.c:1531
bool WALRead(XLogReaderState *state, char *buf, XLogRecPtr startptr, Size count, TimeLineID tli, WALReadError *errinfo)
Definition: xlogreader.c:1100
void XLogReaderFree(XLogReaderState *state)
Definition: xlogreader.c:142
XLogReaderState * XLogReaderAllocate(int wal_segment_size, const char *waldir, XLogReaderRoutine *routine, void *private_data)
Definition: xlogreader.c:78
#define XLogRecGetInfo(decoder)
Definition: xlogreader.h:315
#define XLogRecBlockImageApply(decoder, block_id)
Definition: xlogreader.h:327
#define XLogRecGetRmid(decoder)
Definition: xlogreader.h:316
#define XLogRecGetTotalLen(decoder)
Definition: xlogreader.h:313
#define XLogRecGetXid(decoder)
Definition: xlogreader.h:317
#define XL_ROUTINE(...)
Definition: xlogreader.h:116
#define XLogRecHasBlockImage(decoder, block_id)
Definition: xlogreader.h:325
#define XLogRecHasBlockRef(decoder, block_id)
Definition: xlogreader.h:323
#define XLogRecGetPrev(decoder)
Definition: xlogreader.h:314
#define BKPIMAGE_COMPRESS_LZ4
Definition: xlogrecord.h:150
#define BKPIMAGE_COMPRESSED(info)
Definition: xlogrecord.h:151
#define XLR_INFO_MASK
Definition: xlogrecord.h:62
#define BKPIMAGE_COMPRESS_PGLZ
Definition: xlogrecord.h:149
static const char * directory
Definition: zic.c:632