refactor(telemetry): retire the duplicate nodestore_latency gauge

nodestore_latency published six values that nodestore_state already
publishes from the same Database accessors, so the two gauges were
duplicate readings of the same atomics:

  write_count       -> node_writes             getStoreCount()
  read_count        -> node_reads_total        getFetchTotalCount()
  write_duration_us -> node_writes_duration_us getStoreDurationUs()
  read_duration_us  -> node_reads_duration_us  getFetchDurationUs()
  write_mean_us     -> write_mean_us           store duration / count
  read_mean_us      -> read_mean_us            fetch duration / count

nodestore_state is kept because its means go through scaledMean(), which
saturates at INT64_MAX instead of wrapping and omits a mean when the
denominator is zero rather than reporting a misleading 0 us.

Removes registerNodeStoreLatencyGauge, its instrument member, the
metric::nodestoreLatency constant and the lval::nodestore_latency label
namespace. The gauge-over-histogram rationale and the "p99 is not
obtainable" consequence are folded into observeNodeStoreTotals' docs.

Retargets the gauge-contract test onto nodestore_state rather than
deleting it: the scaledMean arithmetic is covered by the static_asserts
in tests/libxrpl/telemetry/MetricsRegistry.cpp, but nothing else asserts
that these named series multiplex onto one instrument keyed by `metric`.
The test now calls the production scaledMean instead of a copy of the
division, and its sub-microsecond case asserts scaledMean's actual
behaviour (a genuine mean of 0 on a zero numerator with a non-zero
count), which differs from the retired gauge's extra numerator guard.

Rewrites both ledger-sync-health copies' panel 38/39 queries and drops
the obsolete claim that the write numerator was never written: all three
concrete store paths call recordStoreDuration, so write_mean_us is live
on an ordinary node. The same stale [import_db] caveat is removed from
the runbook, the 09 reference row and the workload validator's note.
This commit is contained in:
Pratik Mankawde
2026-07-28 11:52:44 +01:00
parent 05f337c686
commit c4e434d520
12 changed files with 196 additions and 343 deletions

View File

@@ -56,7 +56,7 @@ hardcoded allowlist:
| D | Every dashboard label resolves to an L1 span attribute, a native-metric label (L6, emitted by MetricsRegistry), or a Prometheus/Grafana builtin. TraceQL scope prefixes (`span.`/`resource.`/…) are stripped before the L1 lookup. |
| E | No dotted `xrpl.<domain>.<field>` attribute key in the runbook (only the L1 resource attrs `xrpl.network.*` may be dotted). Span names, filenames, OTel-standard keys, and metric labels are not flagged. |
| I | No string literals as **metric** instrument names or label keys — the mirror of Rule F. Applies to the name passed to an `XRPL_METRIC_*` macro or a `meter->Create*` factory and to the label _keys_ in its label set. Label _values_, descriptions, `*MetricNames.h`, `MetricMacros.h` and test files are exempt. Scoped by metric **family** (first underscore segment): declaring a constant opts that family in, so the metric surface can be converted subsystem by subsystem. Unconverted families warn as Rule L. |
| J | Metric instrument names follow the suffix conventions: `lower_snake_case`, no `xrpld_`/`xrpl_` prefix (the exporter adds it), a counter ends `_total`, a histogram ends `_us`/`_ms`/`_seconds`, a gauge does not end `_total`. The instrument **kind** is read from the emit site, never guessed from words in the name — so a multi-series gauge carrying units in its label values (e.g. `nodestore_latency` observing `write_mean_us`) is not a violation. |
| J | Metric instrument names follow the suffix conventions: `lower_snake_case`, no `xrpld_`/`xrpl_` prefix (the exporter adds it), a counter ends `_total`, a histogram ends `_us`/`_ms`/`_seconds`, a gauge does not end `_total`. The instrument **kind** is read from the emit site, never guessed from words in the name — so a multi-series gauge carrying units in its label values (e.g. `nodestore_state` observing `write_mean_us`) is not a violation. |
| K | Every metric named in `docker/telemetry/workload/expected_metrics.json` resolves to a declared constant, so a rename in code cannot leave the workload validator asserting a name nothing emits. PromQL selectors (`m{label="v"}`) and exporter-appended histogram suffixes (`_bucket`/`_count`/`_sum`) are normalized away first; groups fed by another emit path (`statsd_gauges`, `statsd_counters`, `spanmetrics`) are out of scope by design. |
Rule F runs **unconditionally** (it is a purely syntactic check on the

View File

@@ -1263,9 +1263,9 @@ def instrument_kinds(root: Path, wire_by_symbol: Dict[str, str]) -> Dict[str, st
The kind is what decides which suffix is correct, so it must be read from
the emit site rather than guessed from the name -- guessing from words like
"latency" mislabels a multi-series gauge whose units live in its label
VALUES (e.g. `nodestore_latency` observing `write_mean_us`), which is a
legitimate shape, not a violation.
"latency" or "us" mislabels a multi-series gauge whose units live in its
label VALUES (e.g. `nodestore_state` observing `write_mean_us`), which is
a legitimate shape, not a violation.
Returns one of `counter`, `histogram`, `gauge`, `updown` per wire name.
A name whose emit site is not found is absent from the result, so Rule J

View File

@@ -1184,14 +1184,14 @@ class RuleJMetricSuffixes(unittest.TestCase):
[],
)
def test_gauge_named_latency_is_not_flagged(self):
def test_gauge_with_unit_bearing_label_values_is_not_flagged(self):
# The regression this rule's kind-awareness exists for: a multi-series
# GAUGE whose units live in its label VALUES (nodestore_latency
# GAUGE whose units live in its label VALUES (nodestore_state
# observing write_mean_us) must not be read as a mis-suffixed duration.
self.assertEqual(
self._run(
_mc("nodestoreLatency", "nodestore_latency"),
'meter_->CreateInt64ObservableGauge(metric::nodestoreLatency, "d");\n',
_mc("nodestoreState", "nodestore_state"),
'meter_->CreateInt64ObservableGauge(metric::nodestoreState, "d");\n',
),
[],
)