Skip to content

refactor(metrics): phase 1 deprecation of the legacy monitor stack - #6988

Open
warku123 wants to merge 18 commits into
tronprotocol:release_v4.8.3from
warku123:feature/phase1-deprecate-legacy-metrics
Open

warku123 wants to merge 18 commits into
tronprotocol:release_v4.8.3from
warku123:feature/phase1-deprecate-legacy-metrics

Conversation

@warku123

@warku123 warku123 commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Implements #6923 (item 7 of #6921) — Phase 1 of the two-release retirement of the legacy Monitor metrics stack: migrate its only functional consumer, deprecate the legacy APIs, and add the replacement observability, while keeping both legacy endpoints fully functional until Phase 2. This is Phase 1 of #6923; the issue stays open until the Phase 2 removal PR.

  • Fetch-block peer selection migrated off the metrics registry. The per-IP Dropwizard histogram net.latency.fetch.block.<peerIP> — written via histogramUpdateUnCheck (bypassing the metrics-enable gate), keyed per peer IP, never cleaned on disconnect — is removed from BlockMsgHandler (write site) and FetchBlockService (read site). Peer selection now ranks by a bounded, per-connection EWMA of measured request-to-block durations on PeerConnection:
    • explicit unsampled state; while unsampled, reads fall back to the libp2p channel RTT (Channel.getAvgLatency()), never a placeholder observation;
    • the first real fetch replaces the fallback; smoothing (fixed α = 0.1, (ewma * 9 + last) / 10, named constant with documented rationale) starts from the second sample;
    • estimates are clamped to fetchBlockTimeout; shouldFetchBlock gains an explicit hard-timeout branch (unconditional switch once the wall-clock budget is exhausted) plus a saturation gate requiring a strictly better candidate;
    • state lives and dies with the PeerConnection — O(1) read, bounded memory, no unbounded per-IP keys, no disconnect bookkeeping.
  • New metrics (Prometheus): tron:node_info is an Info metric with two labels; the three fetch counters are unlabeled.
    • tron:node_info{version, genesis_block_id} (Info) — node version plus the full genesis block hash as the canonical chain identifier;
    • tron:block_fetch_armed — incremented when FetchBlockService arms a fetch tracking;
    • tron:block_fetch_secondary — incremented when a secondary fetch is sent;
    • tron:block_already_known — incremented for a matching outstanding adv request whose exact block ID is already known before processing that response (best-effort; concurrent arrivals may be missed; does not establish secondary-fetch attribution).
      The three fetch counters describe fetch behavior that remains after the legacy stack is removed and are retained long-term; normalized rates are derived in PromQL. In code the counters are named tron:block_fetch_armed / tron:block_fetch_secondary / tron:block_already_known; the Prometheus exposition appends _total (and the tron:node collector surfaces as tron:node_info), matching the names used in [Feature] Remove the legacy Monitor API and non-Prometheus metrics implementation #6923. The existing unlabeled tron:block_fetch_latency_seconds histogram is unchanged.
  • Deprecation, no removal. service Monitor and message MetricsInfo are marked option deprecated = true in the protos; a startup WARN fires when node.metricsEnable is present, and a process-once WARN fires on the first invocation of either deprecated API (gRPC Monitor.GetStatsInfo or HTTP /monitor/getstatsinfo — the latter is not gated by the config key). Both APIs keep serving exactly as before.

Why are these changes required?

Per #6921, Prometheus is the single supported monitoring backend and public APIs get a one-release deprecation window before removal. The per-IP histogram is functional scheduling state, not observability: its read must be migrated before the legacy registry can be deleted, so Phase 2 can be a pure deletion following the WalletExtension staging precedent (#6975). The old signal also has real defects the EWMA fixes: a candidate that has never served a fetch reads P75 = 0.0 from the auto-created empty histogram and is always ranked fastest, making the comparison branch history-dependent; samples from different fetch paths are mixed; and the metric family is unbounded.

Behaviour differences vs develop

Listed per principle 1 of #6921: previously a candidate whose P75 exceeded fetchBlockTimeout was filtered out; with the clamped EWMA a saturated candidate stays eligible and may receive the secondary request at the hard timeout when no better candidate exists — a deliberate liveness improvement, documented and tested.

This PR has been tested by:

  • Unit Tests
  • CI / static checks: checkstyleMain, checkstyleTest, git diff --check.

Follow up

  • Docs: legacy-field-to-Prometheus mapping table (including fields without an equivalent) published in the release notes and the documentation-en / documentation-zh repos before the release ships.
  • tron-docker metric_monitor/README.md mirror update.
  • Phase 2 PR in the agreed major release: remove the legacy implementation, node.metricsEnable chain (tombstone WARN when still present), proto definitions, and the Dropwizard dependency; the three counters and tron:node_info remain. Target major will be recorded in Tracking: code refactor and cleanup #6921 and cross-referenced in [Feature] Remove the legacy Monitor API and non-Prometheus metrics implementation #6923.

Extra details

  • Backward Compatibility: Phase 1 is non-breaking — runtime behavior and response formats of both legacy endpoints (gRPC Monitor.GetStatsInfo, HTTP /monitor/getstatsinfo) are unchanged; removal only in Phase 2. Config migration is time-boxed and non-breaking during Phase 1:
Before After
node.metricsEnable = true node.metrics.prometheus.enable = true
(legacy port n/a) node.metrics.prometheus.port = 9527 (default)

Replace the legacy Dropwizard per-peer histogram P75 with
Channel.getAvgLatency() for fetch-block peer selection, and remove the
per-peer histogram write in BlockMsgHandler.
…and fetch-block peer selection

Add tests to satisfy the changed-line coverage gate (>60%) that failed
in the fork validation CI:

- PrometheusApiServiceTest: testNodeInfoMetric verifies the tron:node_info
  Info collector is registered and exposes the node version; testNodeInfoUnknownKey
  exercises the null-guard branch in MetricsInfo.set; testApplyBlockDupWitness
  and testApplyBlockWithTxs cover the migrated MetricsService.applyBlock
  Prometheus-only path (dup-witness MINER counter and TXS counter).

- FetchBlockServiceTest: testSelectLowestLatencyPeer verifies that
  fetchBlockProcess selects the idle peer with the lowest channel avg
  latency (the migrated replacement for the legacy per-IP histogram P75)
  and dispatches a FetchInvDataMessage; testSwitchOnOldPeerTimeout covers
  the fast-switch branch when the old peer exceeds fetchBlockTimeout.
…timator

Replace the raw channel average latency used by fetch-block peer
selection with a bounded per-connection estimator:

- PeerConnection tracks a volatile fetchLatency EWMA (alpha = 0.1),
  seeded from the channel average latency on the first sample and
  clamped to [0, fetchBlockTimeout] to resist outliers
- BlockMsgHandler feeds measured fetch durations into the estimator
- FetchBlockService reads the estimator; the wall-clock hard timeout
  switches peers unconditionally while the latency-saturation gate
  requires a strictly better candidate to avoid 500v500 flapping

Fetch latency stays observable via the unlabeled Prometheus histogram.
Add tron:node_info{version="..."} so the node version that the legacy
Monitor API used to report is still observable through prometheus.
Node IP is intentionally not added; the prometheus instance label
already identifies the source node.
…zation

The first real fetch latency now directly initializes the estimator
(isomorphic to RFC 6298 SRTT initialization) instead of being blended
with the channel average latency. The channel latency is demoted to a
read-only fallback for the unsampled state via getFetchLatency(), and
never enters the sample sequence. EWMA alpha=0.1 applies from the
second sample onward; clamp keeps math-check compliance.
…fallback

Unsampled peers now read their channel avgLatency as a fallback instead
of 0, so the both-unsampled quadrant flips from suppressing failover to
allowing it (candidate 0 < (200 - 0) * 0.5). The first-sample test now
asserts direct replacement with clamp instead of channel blending, and
the isolation test asserts the fresh connection's channel fallback.
The label value is the chain id derived from the genesis block hash,
so genesis_block_id describes what it identifies more accurately.
…cated

Add 'option deprecated = true;' to the Monitor gRPC service and the
MetricsInfo message so generated classes carry @deprecated.
Log a process-level warning once when the deprecated legacy metrics
stack is used: node startup with node.metricsEnable, HTTP
/monitor/getstatsinfo, and rpc Monitor.GetStatsInfo. The servlet and
rpc warnings use independent once-flags.
…cates

Register two label-free prometheus counters: tron:block_fetch_secondary
increments when the estimator-driven failover issues a secondary fetch;
tron:block_duplicate increments when an adv block below head (already
processed) is received.
Its read and write points were removed with the legacy fetch-block
histogram.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

[Feature] Remove the legacy Monitor API and non-Prometheus metrics implementation

2 participants