feat(telemetry): add txq expired/dropped counters for queue backpressure

The transaction queue had no metric for demand that leaves or never enters the
queue, so fee-underpayment abandonment and admission-control rejection were
invisible (distinct from jq_trans_overflow, which is the job queue).

Add two synchronous counters via MetricsRegistry:
- xrpld_txq_expired_total — incremented in TxQ::processClosedLedger() for each
  queued transaction removed because its LastLedgerSequence passed (submitters
  who under-bid the escalating fee and were never included).
- xrpld_txq_dropped_total{reason} — incremented in TxQ::apply() at the
  queue-full admission-control returns (reason="queue_full").

Both reach MetricsRegistry via the Application& parameter already passed to
these methods; calls are null-guarded so they no-op when telemetry is disabled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Pratik Mankawde
2026-06-04 16:06:33 +01:00
parent 7a509a01eb
commit 793d2ecfce
3 changed files with 55 additions and 0 deletions

View File

@@ -3,6 +3,7 @@
#include <xrpld/app/ledger/OpenLedger.h>
#include <xrpld/app/main/Application.h>
#include <xrpld/app/misc/detail/TxQSpanNames.h>
#include <xrpld/telemetry/MetricsRegistry.h>
#include <xrpl/basics/BasicConfig.h>
#include <xrpl/basics/Log.h>
@@ -1254,6 +1255,8 @@ TxQ::apply(
JLOG(j_.info()) << "Queue is full, and transaction " << transactionID
<< " would kick a transaction from the same account (" << account
<< ") out of the queue.";
if (auto* const metrics = app.getMetricsRegistry(); metrics != nullptr)
metrics->incrementTxqDropped("queue_full");
return {telCAN_NOT_QUEUE_FULL, false};
}
auto const& endAccount = byAccount_.at(lastRIter->account);
@@ -1297,6 +1300,8 @@ TxQ::apply(
{
JLOG(j_.info()) << "Queue is full, and transaction " << transactionID
<< " fee is lower than end item's account average fee";
if (auto* const metrics = app.getMetricsRegistry(); metrics != nullptr)
metrics->incrementTxqDropped("queue_full");
return {telCAN_NOT_QUEUE_FULL, false};
}
}
@@ -1366,12 +1371,17 @@ TxQ::processClosedLedger(Application& app, ReadView const& view, bool timeLeap)
maxSize_ = std::max(snapshot.txnsExpected * setup_.ledgersInQueue, setup_.queueSizeMin);
// Remove any queued candidates whose LastLedgerSequence has gone by.
auto* const metrics = app.getMetricsRegistry();
for (auto candidateIter = byFee_.begin(); candidateIter != byFee_.end();)
{
if (candidateIter->lastValid && *candidateIter->lastValid <= ledgerSeq)
{
byAccount_.at(candidateIter->account).dropPenalty = true;
candidateIter = erase(candidateIter);
// Count each expired transaction: submitters who under-bid the
// escalating fee and were never included before expiry.
if (metrics != nullptr)
metrics->incrementTxqExpired();
}
else
{

View File

@@ -240,6 +240,10 @@ MetricsRegistry::start(std::string const& endpoint, std::string const& instanceI
ledgerHistoryMismatchCounter_ = meter_->CreateUInt64Counter(
"xrpld_ledger_history_mismatch_total",
"Total built-vs-validated ledger mismatches by reason");
txqExpiredCounter_ = meter_->CreateUInt64Counter(
"xrpld_txq_expired_total", "Total transactions expired out of the transaction queue");
txqDroppedCounter_ = meter_->CreateUInt64Counter(
"xrpld_txq_dropped_total", "Total transactions refused admission to the queue by reason");
validationAgreementsCounter_ = meter_->CreateUInt64Counter(
"xrpld_validation_agreements_total", "Total validation agreements");
validationMissedCounter_ =
@@ -1338,4 +1342,22 @@ MetricsRegistry::incrementLedgerHistoryMismatch(std::string_view reason)
#endif
}
void
MetricsRegistry::incrementTxqExpired()
{
#ifdef XRPL_ENABLE_TELEMETRY
if (enabled_ && txqExpiredCounter_)
txqExpiredCounter_->Add(1);
#endif
}
void
MetricsRegistry::incrementTxqDropped(std::string_view reason)
{
#ifdef XRPL_ENABLE_TELEMETRY
if (enabled_ && txqDroppedCounter_)
txqDroppedCounter_->Add(1, {{"reason", std::string(reason)}});
#endif
}
} // namespace xrpl::telemetry

View File

@@ -360,6 +360,23 @@ public:
void
incrementLedgerHistoryMismatch(std::string_view reason);
/** Increment the txq_expired_total counter.
Called from TxQ::processClosedLedger() for each queued transaction
removed because its LastLedgerSequence has passed — submitters who
under-bid the escalating fee and were never included.
*/
void
incrementTxqExpired();
/** Increment the txq_dropped_total{reason} counter.
Called from TxQ::apply() when a transaction is refused admission to
the queue (e.g. the queue is full). Distinct from expiry (already
queued) and from jq_trans_overflow (job queue, not TxQ).
@param reason Admission-control rejection cause (e.g. "queue_full").
*/
void
incrementTxqDropped(std::string_view reason);
/** Access the validation agreement tracker.
Used by consensus and ledger hooks to record our validations and
network validations so the tracker can compute agreement percentages.
@@ -498,6 +515,12 @@ private:
/// built-vs-validated ledger mismatch.
opentelemetry::nostd::unique_ptr<opentelemetry::metrics::Counter<uint64_t>>
ledgerHistoryMismatchCounter_;
/// Counter: xrpld_txq_expired_total — incremented per transaction expired out of the
/// transaction queue.
opentelemetry::nostd::unique_ptr<opentelemetry::metrics::Counter<uint64_t>> txqExpiredCounter_;
/// Counter: xrpld_txq_dropped_total{reason} — incremented when a transaction is refused
/// admission to the queue.
opentelemetry::nostd::unique_ptr<opentelemetry::metrics::Counter<uint64_t>> txqDroppedCounter_;
/// Counter: xrpld_validation_agreements_total — incremented by ValidationTracker on
/// agreement.
opentelemetry::nostd::unique_ptr<opentelemetry::metrics::Counter<uint64_t>>