mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-22 06:40:53 +00:00
kSubMillisecondBoundaries existed but nothing used it, so per-fetch read latency never reached Grafana -- only the coarse read_mean_us gauge did, which cannot separate "every read took 9us" from "most took 2 and a few took 900". Add a nodestore_read_us histogram, register its view against the sub- millisecond ladder rather than kMicrosecondBoundaries (whose first edge is 100us, above the entire range a warm read occupies), and record into it from NodeStoreScheduler::onFetch using FetchReport::elapsed, which a previous change widened to microseconds for exactly this purpose. The name and its labels live in a new include/xrpl/telemetry header because the view registration (xrpld.telemetry) and the record site (xrpld.app) sit in different levelization modules; a copy-pasted literal would let them drift and silently drop the bucket override. Same reason and same placement as GetObjectMetricNames.h. No new levelization edge: xrpld.app > xrpl.telemetry already exists. NodeStoreScheduler had no registry access, so it now takes a ServiceRegistry and resolves the registry per call. It is constructed in Application's initializer list, long before metricsRegistry_ is assigned in setup() and started in startTelemetry(), so capturing a pointer at construction would capture nullptr forever; the metric macros null-check the registry, the meter and the instrument, so early fetches are simply not recorded. Labels are fetch_type and found, both already carried on the report -- 4 series, fixed at compile time. A slow async read delays prefetch while a slow sync read blocks a caller, and a miss can cost a read of every backend, so neither dimension can be collapsed. Negative elapsed times are skipped: the SDK rejects them and logs a warning on every call, which on a per-fetch path is a log flood. Zero is still recorded, since a page-cache-served read genuinely rounds to it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
92 lines
2.9 KiB
C++
92 lines
2.9 KiB
C++
// cspell:ignore ISTOGRAM
|
|
// The all-caps macro name XRPL_METRIC_HISTOGRAM_RECORD_LABELED trips cspell's
|
|
// compound-word splitter, which emits the subword "ISTOGRAM"; ignore it here.
|
|
|
|
#include <xrpld/app/main/NodeStoreScheduler.h>
|
|
|
|
#include <xrpld/telemetry/MetricMacros.h>
|
|
|
|
#include <xrpl/core/Job.h>
|
|
#include <xrpl/core/JobQueue.h>
|
|
#include <xrpl/core/ServiceRegistry.h>
|
|
#include <xrpl/nodestore/Scheduler.h>
|
|
#include <xrpl/nodestore/Task.h>
|
|
#include <xrpl/telemetry/NodeStoreMetricNames.h>
|
|
|
|
#include <chrono>
|
|
#include <string>
|
|
|
|
namespace xrpl {
|
|
|
|
NodeStoreScheduler::NodeStoreScheduler([[maybe_unused]] ServiceRegistry& app, JobQueue& jobQueue)
|
|
#ifdef XRPL_ENABLE_TELEMETRY
|
|
: app_(app), jobQueue_(jobQueue)
|
|
#else
|
|
: jobQueue_(jobQueue)
|
|
#endif
|
|
{
|
|
}
|
|
|
|
void
|
|
NodeStoreScheduler::scheduleTask(node_store::Task& task)
|
|
{
|
|
if (jobQueue_.isStopped())
|
|
return;
|
|
|
|
if (!jobQueue_.addJob(JtWrite, "NObjStore", [&task]() { task.performScheduledTask(); }))
|
|
{
|
|
// Job not added, presumably because we're shutting down.
|
|
// Recover by executing the task synchronously.
|
|
task.performScheduledTask();
|
|
}
|
|
}
|
|
|
|
void
|
|
NodeStoreScheduler::onFetch(node_store::FetchReport const& report)
|
|
{
|
|
if (jobQueue_.isStopped())
|
|
return;
|
|
|
|
auto const isAsync = report.fetchType == node_store::FetchType::Async;
|
|
|
|
// The report is in microseconds but addLoadEvents takes milliseconds, so
|
|
// cast explicitly. The load monitor only tracks whole-millisecond load,
|
|
// so the sub-millisecond detail is deliberately dropped here; the
|
|
// histogram below keeps it.
|
|
jobQueue_.addLoadEvents(
|
|
isAsync ? JtNsAsyncRead : JtNsSyncRead,
|
|
1,
|
|
std::chrono::duration_cast<std::chrono::milliseconds>(report.elapsed));
|
|
|
|
// Skip a negative elapsed time rather than hand it to the SDK, which
|
|
// rejects it and logs a warning on every single call. The clock is
|
|
// monotonic, so this needs a clock bug to happen -- but a per-fetch log
|
|
// flood would be worse than the missing sample.
|
|
if (!telemetry::shouldRecordFetchLatency(report.elapsed.count()))
|
|
return;
|
|
|
|
// Two labels, both already on the report. fetch_type because a slow async
|
|
// read only delays prefetch while a slow sync read blocks a caller;
|
|
// found because a miss can cost a read of every backend, so mixing the
|
|
// two blurs the distribution.
|
|
XRPL_METRIC_HISTOGRAM_RECORD_LABELED(
|
|
app_,
|
|
telemetry::kNodeStoreReadUs,
|
|
telemetry::kNodeStoreReadUsDesc,
|
|
report.elapsed.count(),
|
|
{{telemetry::kFetchTypeLabel, std::string(telemetry::fetchTypeLabelValue(isAsync))},
|
|
{telemetry::kFetchFoundLabel,
|
|
std::string(telemetry::fetchFoundLabelValue(report.wasFound))}});
|
|
}
|
|
|
|
void
|
|
NodeStoreScheduler::onBatchWrite(node_store::BatchWriteReport const& report)
|
|
{
|
|
if (jobQueue_.isStopped())
|
|
return;
|
|
|
|
jobQueue_.addLoadEvents(JtNsWrite, report.writeCount, report.elapsed);
|
|
}
|
|
|
|
} // namespace xrpl
|