fix: query Horizon inside the container and flag lost core connection
- Fetch Horizon via curl inside the container (127.0.0.1:8000) instead of the host port 31401; the host->bridge forwarding hung until the 5 s timeout and made each cycle take ~6 s instead of ~1 s - Report an error when Horizon's core_latest_ledger is 0 (no connection to stellar-core) instead of a false "Synced" with a negative lag - Compute the ingest lag against stellar-core's own ledger - "History Oldest" now shows history_elder_ledger; the latest ledger is listed separately as "History Latest" Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -45,7 +45,7 @@ Single self-contained script (`pi-stat`). Logic is split into helper functions a
|
||||
|
||||
- **Docker SDK** — container CPU/RAM stats; all commands run inside the auto-detected container (`mainnet` → `testnet2` → `testnet`, first running one) via `exec_run`
|
||||
- **Stellar Core HTTP API** — `exec_json()` runs `stellar-core http-command <cmd>` inside the container
|
||||
- **Horizon API** — `http://localhost:31401`
|
||||
- **Horizon API** — `curl http://127.0.0.1:8000/` run inside the container via `exec_json()`, not via the host port `31401` (host→bridge forwarding can hang until timeout if Docker's host firewall rules are broken)
|
||||
- **PostgreSQL** — `psql` inside the container; used for peer DB counts and pubkey→IP mapping
|
||||
- **ZFS CLI** — `zpool`/`zfs` commands on the host for disk health (`z01pool`); if `zpool` is not found (`HAS_ZFS = False`), falls back to `shutil.disk_usage()` — works on Linux without ZFS, WSL2, and Windows
|
||||
- **ip-api.com** — batch geolocation (Country, City), cached in `_geo_cache`; only fetched with `-g`
|
||||
|
||||
@@ -38,7 +38,7 @@ except ImportError:
|
||||
# --- Configuration ---
|
||||
HAS_ZFS = shutil.which("zpool") is not None
|
||||
CANDIDATE_CONTAINERS = ["mainnet", "testnet2", "testnet"]
|
||||
HORIZON_URL = "http://localhost:31401"
|
||||
HORIZON_URL = "http://127.0.0.1:8000" # queried from inside the container, not via the host port
|
||||
CFG_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "stellar-core.cfg")
|
||||
DEFAULT_REFRESH = 5
|
||||
REFRESH_WARN_RATIO = 0.8 # warn if a cycle takes >= 80% of the refresh interval
|
||||
@@ -262,12 +262,12 @@ def run_cycle(client, peers_extended, geo_enabled, show_ip, interactive=False, e
|
||||
if raw:
|
||||
core_info = raw.get("info")
|
||||
|
||||
# Query Horizon via curl inside the container: independent of the host's
|
||||
# port forwarding (31401), which can hang until timeout if Docker's
|
||||
# firewall rules on the host are broken.
|
||||
hz_data = None
|
||||
try:
|
||||
with urlopen(HORIZON_URL + "/", timeout=5) as resp:
|
||||
hz_data = json.loads(resp.read())
|
||||
except Exception:
|
||||
pass
|
||||
if container:
|
||||
hz_data = exec_json(container, ["curl", "-s", "-m", "5", HORIZON_URL + "/"])
|
||||
|
||||
# =============================================================
|
||||
# 1. CONTAINER STATUS
|
||||
@@ -373,23 +373,36 @@ def run_cycle(client, peers_extended, geo_enabled, show_ip, interactive=False, e
|
||||
hdr("🌅 API Status (Horizon)")
|
||||
|
||||
if hz_data is None:
|
||||
err(f"Horizon API not reachable ({HORIZON_URL})")
|
||||
err(f"Horizon API not reachable ({HORIZON_URL} in container '{container_name}')")
|
||||
else:
|
||||
core_blk = hz_data.get("core_latest_ledger", 0)
|
||||
hist_blk = hz_data.get("history_latest_ledger", 0)
|
||||
elder_blk = hz_data.get("history_elder_ledger", 0)
|
||||
ings_blk = hz_data.get("ingest_latest_ledger", 0)
|
||||
lag = core_blk - ings_blk
|
||||
closed_at = hz_data.get("history_latest_ledger_closed_at", "?")
|
||||
|
||||
if lag <= 2:
|
||||
# Reference ledger for the ingest lag: stellar-core's own ledger if we
|
||||
# have it, otherwise what Horizon reports for core. core_latest_ledger
|
||||
# is 0 when Horizon has lost its connection to stellar-core; computing
|
||||
# the lag against 0 would give a negative value and a false "Synced".
|
||||
ref_blk = block if core_info is not None else core_blk
|
||||
lag = (ref_blk - ings_blk) if ref_blk else None
|
||||
|
||||
if not core_blk:
|
||||
err("Status: Horizon has no connection to stellar-core (core_latest_ledger = 0)")
|
||||
if lag is not None and lag > 2:
|
||||
warn(f"Ingest {lag} ledger behind core")
|
||||
elif lag <= 2:
|
||||
ok(f"Status: Synced (Ingest lag: {lag} ledger)")
|
||||
else:
|
||||
warn(f"Status: Lagging (Ingest {lag} ledger behind core)")
|
||||
core_diff = (block - core_blk) if (core_info is not None and core_blk) else None
|
||||
core_diff_str = f" (Δ {core_diff:+d} vs Core)" if core_diff is not None else ""
|
||||
print(f" Core Latest Ledger: {core_blk}{core_diff_str}")
|
||||
core_blk_str = str(core_blk) if core_blk else "— (not connected)"
|
||||
print(f" Core Latest Ledger: {core_blk_str}{core_diff_str}")
|
||||
print(f" Ingest Latest Ledger: {ings_blk} (last closed: {closed_at})")
|
||||
print(f" History Oldest: {hist_blk}")
|
||||
print(f" History Latest: {hist_blk}")
|
||||
print(f" History Oldest: {elder_blk}")
|
||||
|
||||
# =============================================================
|
||||
# 5. NODE METRICS (stellar-core)
|
||||
|
||||
Reference in New Issue
Block a user