forked from lei.chen/myxce
wal_type_name() never had a case for WAL_CONFIG_MARKER (type 50) -- every config-change marker entry printed as UNKNOWN instead of a real, defined type. Added the case plus payload decode (tag/sha256) matching config_repl.c's config_marker_encode/_decode byte layout, hand-decoded here rather than linking config_repl.c into xce_test's link graph. Gap detection also had no way to tell "committed prefix missing!" (real loss) apart from a normal checkpoint-triggered WAL trim (persist.c's trim_wal_segments, floor-guarded so it never removes a segment a standby still needs) -- every long-running node's WAL, once checkpointed, would false-alarm on every inspection. Cross-checks the sibling data/snap/ directory (snapshot LSN is literally in the filename, snap-<hex>.capnp) and downgrades the message when a snapshot covers the gap. Adds waldump_stats_t.leading_gap_explained and 3 new tests plus one assertion on the existing gap test.
76 lines
4.2 KiB
C
76 lines
4.2 KiB
C
#pragma once
|
|
/*
|
|
* waldump.h — read-only WAL segment inspector for failover/durability triage.
|
|
*
|
|
* Decodes the on-disk WAL (`wal_entry_hdr_t` + payload, see wal_types.h),
|
|
* lists every physical entry, decodes JOB_SUBMIT jobids via the Cap'n Proto
|
|
* Job schema, and — the decisive signal for the committed-job-loss bug —
|
|
* flags LSN gaps (a missing prefix, or any non-contiguous hole) and reports
|
|
* the CRC-chain "recovered tip" a real recovery would stop at.
|
|
*
|
|
* Strictly read-only: opens segments O_RDONLY, applies no entries, mutates no
|
|
* persist_t state. Safe to run against a live node's data dir.
|
|
*/
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
|
|
/* Aggregate result of scanning one path (a seg file, a wal/ dir, or a node
|
|
* data dir). All counts are over the physically-present, CRC-valid entries. */
|
|
typedef struct {
|
|
uint64_t first_lsn; /* lowest LSN seen (0 = no entries) */
|
|
uint64_t last_lsn; /* highest LSN seen */
|
|
uint64_t recovered_tip; /* highest LSN reachable via an intact CRC chain */
|
|
uint32_t n_entries; /* total entries scanned */
|
|
uint32_t n_submits; /* WAL_JOB_SUBMIT entries */
|
|
uint32_t n_gaps; /* LSN discontinuities (missing prefix counts as 1) */
|
|
uint32_t n_segments; /* segment files scanned */
|
|
uint32_t n_dup_submits; /* JOB_SUBMITs with same jobid as the immediately-
|
|
* preceding entry at an adjacent LSN (bug-845) */
|
|
uint32_t n_dup_identical; /* of n_dup_submits, those whose payload is
|
|
* byte-identical to the prior entry */
|
|
int crc_broken; /* nonzero if a CRC/chain break truncated a segment */
|
|
int leading_gap_explained; /* nonzero if the leading (prefix) gap's
|
|
* upper LSN is covered by a sibling
|
|
* data/snap/snap-<hex>.capnp file — i.e.
|
|
* normal checkpoint-triggered WAL trim
|
|
* (persist.c trim_wal_segments), not
|
|
* genuine loss. 0 if there was no leading
|
|
* gap, or no covering snapshot was found. */
|
|
} waldump_stats_t;
|
|
|
|
/* One physically-present, CRC-valid WAL entry, surfaced to a visitor so a
|
|
* caller (e.g. walcompare) can index the log by `(lsn -> term, pcrc)` without
|
|
* re-implementing the segment/CRC parse. `pcrc` is the payload-only CRC32 (0
|
|
* for an empty payload) — the same fingerprint waldump prints, isolating the
|
|
* blob from the header so a same-LSN content divergence is detectable. */
|
|
typedef struct {
|
|
uint64_t lsn;
|
|
uint64_t term; /* raw on-disk term (legacy 0 kept verbatim) */
|
|
uint32_t type; /* WAL entry type (wal_types.h) */
|
|
uint32_t payload_len; /* payload byte count */
|
|
uint32_t pcrc; /* CRC32 of payload only (0 if payload_len == 0) */
|
|
} waldump_entry_t;
|
|
|
|
/* Per-entry visitor. Invoked once per CRC-valid entry in ascending scan order
|
|
* (before the gap line is printed). Return 0 to continue, non-zero to stop the
|
|
* scan early. `ctx` is the caller's opaque pointer. */
|
|
typedef int (*waldump_visit_fn)(void* ctx, const waldump_entry_t* e);
|
|
|
|
/* Dump every WAL entry under `path` to `out` (human-readable) and fill
|
|
* `*stats` when non-NULL. `path` may be:
|
|
* - a single seg-*.wal file,
|
|
* - a directory holding seg-*.wal files (a wal/ dir), or
|
|
* - a node/data dir whose wal/ subdir (data/wal or wal) holds the segments.
|
|
* Returns 0 on success, or a negative errno on a hard I/O error (e.g. the
|
|
* path does not exist / no segments found). Gaps and CRC breaks are reported
|
|
* through `out`/`stats`, NOT via the return code. */
|
|
int waldump_path(const char* path, FILE* out, waldump_stats_t* stats);
|
|
|
|
/* Same as waldump_path, but also invokes `visit(ctx, &entry)` for every
|
|
* CRC-valid entry so a caller can collect the `(lsn -> term, pcrc)` index.
|
|
* `visit` may be NULL (then identical to waldump_path). `out` may be a sink
|
|
* the caller does not care about (e.g. a /dev/null FILE) — the human dump is
|
|
* still produced. The single canonical WAL decoder is reused; no second
|
|
* segment parser. */
|
|
int waldump_path_visit(const char* path, FILE* out, waldump_stats_t* stats,
|
|
waldump_visit_fn visit, void* ctx);
|