279 lines
7.2 KiB
C
279 lines
7.2 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* pg_walsummary.c
|
|
* Prints the contents of WAL summary files.
|
|
*
|
|
* Copyright (c) 2017-2024, PostgreSQL Global Development Group
|
|
*
|
|
* IDENTIFICATION
|
|
* src/bin/pg_walsummary/pg_walsummary.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres_fe.h"
|
|
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
|
|
#include "common/blkreftable.h"
|
|
#include "common/int.h"
|
|
#include "common/logging.h"
|
|
#include "fe_utils/option_utils.h"
|
|
#include "getopt_long.h"
|
|
#include "lib/stringinfo.h"
|
|
|
|
typedef struct ws_options
|
|
{
|
|
bool individual;
|
|
bool quiet;
|
|
} ws_options;
|
|
|
|
typedef struct ws_file_info
|
|
{
|
|
int fd;
|
|
char *filename;
|
|
} ws_file_info;
|
|
|
|
static BlockNumber *block_buffer = NULL;
|
|
static unsigned block_buffer_size = 512; /* Initial size. */
|
|
|
|
static void dump_one_relation(ws_options *opt, RelFileLocator *rlocator,
|
|
ForkNumber forknum, BlockNumber limit_block,
|
|
BlockRefTableReader *reader);
|
|
static void help(const char *progname);
|
|
static int compare_block_numbers(const void *a, const void *b);
|
|
static int walsummary_read_callback(void *callback_arg, void *data,
|
|
int length);
|
|
static void walsummary_error_callback(void *callback_arg, char *fmt,...) pg_attribute_printf(2, 3);
|
|
|
|
/*
|
|
* Main program.
|
|
*/
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
static struct option long_options[] = {
|
|
{"individual", no_argument, NULL, 'i'},
|
|
{"quiet", no_argument, NULL, 'q'},
|
|
{NULL, 0, NULL, 0}
|
|
};
|
|
|
|
const char *progname;
|
|
int optindex;
|
|
int c;
|
|
ws_options opt;
|
|
|
|
memset(&opt, 0, sizeof(ws_options));
|
|
|
|
pg_logging_init(argv[0]);
|
|
progname = get_progname(argv[0]);
|
|
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_walsummary"));
|
|
handle_help_version_opts(argc, argv, progname, help);
|
|
|
|
/* process command-line options */
|
|
while ((c = getopt_long(argc, argv, "f:iqw:",
|
|
long_options, &optindex)) != -1)
|
|
{
|
|
switch (c)
|
|
{
|
|
case 'i':
|
|
opt.individual = true;
|
|
break;
|
|
case 'q':
|
|
opt.quiet = true;
|
|
break;
|
|
default:
|
|
/* getopt_long already emitted a complaint */
|
|
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
if (optind >= argc)
|
|
{
|
|
pg_log_error("%s: no input files specified", progname);
|
|
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
|
|
exit(1);
|
|
}
|
|
|
|
while (optind < argc)
|
|
{
|
|
ws_file_info ws;
|
|
BlockRefTableReader *reader;
|
|
RelFileLocator rlocator;
|
|
ForkNumber forknum;
|
|
BlockNumber limit_block;
|
|
|
|
ws.filename = argv[optind++];
|
|
if ((ws.fd = open(ws.filename, O_RDONLY | PG_BINARY, 0)) < 0)
|
|
pg_fatal("could not open file \"%s\": %m", ws.filename);
|
|
|
|
reader = CreateBlockRefTableReader(walsummary_read_callback, &ws,
|
|
ws.filename,
|
|
walsummary_error_callback, NULL);
|
|
while (BlockRefTableReaderNextRelation(reader, &rlocator, &forknum,
|
|
&limit_block))
|
|
dump_one_relation(&opt, &rlocator, forknum, limit_block, reader);
|
|
|
|
DestroyBlockRefTableReader(reader);
|
|
close(ws.fd);
|
|
}
|
|
|
|
exit(0);
|
|
}
|
|
|
|
/*
|
|
* Dump details for one relation.
|
|
*/
|
|
static void
|
|
dump_one_relation(ws_options *opt, RelFileLocator *rlocator,
|
|
ForkNumber forknum, BlockNumber limit_block,
|
|
BlockRefTableReader *reader)
|
|
{
|
|
unsigned i = 0;
|
|
unsigned nblocks;
|
|
BlockNumber startblock = InvalidBlockNumber;
|
|
BlockNumber endblock = InvalidBlockNumber;
|
|
|
|
/* Dump limit block, if any. */
|
|
if (limit_block != InvalidBlockNumber)
|
|
printf("TS %u, DB %u, REL %u, FORK %s: limit %u\n",
|
|
rlocator->spcOid, rlocator->dbOid, rlocator->relNumber,
|
|
forkNames[forknum], limit_block);
|
|
|
|
/* If we haven't allocated a block buffer yet, do that now. */
|
|
if (block_buffer == NULL)
|
|
block_buffer = palloc_array(BlockNumber, block_buffer_size);
|
|
|
|
/* Try to fill the block buffer. */
|
|
nblocks = BlockRefTableReaderGetBlocks(reader,
|
|
block_buffer,
|
|
block_buffer_size);
|
|
|
|
/* If we filled the block buffer completely, we must enlarge it. */
|
|
while (nblocks >= block_buffer_size)
|
|
{
|
|
unsigned new_size;
|
|
|
|
/* Double the size, being careful about overflow. */
|
|
new_size = block_buffer_size * 2;
|
|
if (new_size < block_buffer_size)
|
|
new_size = PG_UINT32_MAX;
|
|
block_buffer = repalloc_array(block_buffer, BlockNumber, new_size);
|
|
|
|
/* Try to fill the newly-allocated space. */
|
|
nblocks +=
|
|
BlockRefTableReaderGetBlocks(reader,
|
|
block_buffer + block_buffer_size,
|
|
new_size - block_buffer_size);
|
|
|
|
/* Save the new size for later calls. */
|
|
block_buffer_size = new_size;
|
|
}
|
|
|
|
/* If we don't need to produce any output, skip the rest of this. */
|
|
if (opt->quiet)
|
|
return;
|
|
|
|
/*
|
|
* Sort the returned block numbers. If the block reference table was using
|
|
* the bitmap representation for a given chunk, the block numbers in that
|
|
* chunk will already be sorted, but when the array-of-offsets
|
|
* representation is used, we can receive block numbers here out of order.
|
|
*/
|
|
qsort(block_buffer, nblocks, sizeof(BlockNumber), compare_block_numbers);
|
|
|
|
/* Dump block references. */
|
|
while (i < nblocks)
|
|
{
|
|
/*
|
|
* Find the next range of blocks to print, but if --individual was
|
|
* specified, then consider each block a separate range.
|
|
*/
|
|
startblock = endblock = block_buffer[i++];
|
|
if (!opt->individual)
|
|
{
|
|
while (i < nblocks && block_buffer[i] == endblock + 1)
|
|
{
|
|
endblock++;
|
|
i++;
|
|
}
|
|
}
|
|
|
|
/* Format this range of block numbers as a string. */
|
|
if (startblock == endblock)
|
|
printf("TS %u, DB %u, REL %u, FORK %s: block %u\n",
|
|
rlocator->spcOid, rlocator->dbOid, rlocator->relNumber,
|
|
forkNames[forknum], startblock);
|
|
else
|
|
printf("TS %u, DB %u, REL %u, FORK %s: blocks %u..%u\n",
|
|
rlocator->spcOid, rlocator->dbOid, rlocator->relNumber,
|
|
forkNames[forknum], startblock, endblock);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Quicksort comparator for block numbers.
|
|
*/
|
|
static int
|
|
compare_block_numbers(const void *a, const void *b)
|
|
{
|
|
BlockNumber aa = *(BlockNumber *) a;
|
|
BlockNumber bb = *(BlockNumber *) b;
|
|
|
|
return pg_cmp_u32(aa, bb);
|
|
}
|
|
|
|
/*
|
|
* Error callback.
|
|
*/
|
|
void
|
|
walsummary_error_callback(void *callback_arg, char *fmt,...)
|
|
{
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
pg_log_generic_v(PG_LOG_ERROR, PG_LOG_PRIMARY, fmt, ap);
|
|
va_end(ap);
|
|
|
|
exit(1);
|
|
}
|
|
|
|
/*
|
|
* Read callback.
|
|
*/
|
|
int
|
|
walsummary_read_callback(void *callback_arg, void *data, int length)
|
|
{
|
|
ws_file_info *ws = callback_arg;
|
|
int rc;
|
|
|
|
if ((rc = read(ws->fd, data, length)) < 0)
|
|
pg_fatal("could not read file \"%s\": %m", ws->filename);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* help
|
|
*
|
|
* Prints help page for the program
|
|
*
|
|
* progname: the name of the executed program, such as "pg_walsummary"
|
|
*/
|
|
static void
|
|
help(const char *progname)
|
|
{
|
|
printf(_("%s prints the contents of a WAL summary file.\n\n"), progname);
|
|
printf(_("Usage:\n"));
|
|
printf(_(" %s [OPTION]... FILE...\n"), progname);
|
|
printf(_("\nOptions:\n"));
|
|
printf(_(" -i, --individual list block numbers individually, not as ranges\n"));
|
|
printf(_(" -q, --quiet don't print anything, just parse the files\n"));
|
|
printf(_(" -V, --version output version information, then exit\n"));
|
|
printf(_(" -?, --help show this help, then exit\n"));
|
|
|
|
printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
|
|
printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
|
|
}
|