Files
rippled/src/libxrpl/core/detail/LoadMonitor.cpp
2026-04-29 15:17:35 +00:00

183 lines
4.2 KiB
C++

#include <xrpl/core/LoadMonitor.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/UptimeClock.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/core/LoadEvent.h>
#include <chrono>
#include <mutex>
namespace xrpl {
/*
TODO
----
- Use Journal for logging
*/
//------------------------------------------------------------------------------
LoadMonitor::Stats::Stats() : latencyAvg(0), latencyPeak(0)
{
}
//------------------------------------------------------------------------------
LoadMonitor::LoadMonitor(beast::Journal j)
: mLatencyMSAvg(0)
, mLatencyMSPeak(0)
, mTargetLatencyAvg(0)
, mTargetLatencyPk(0)
, mLastUpdate(UptimeClock::now())
, j_(j)
{
}
// VFALCO NOTE WHY do we need "the mutex?" This dependence on
// a hidden global, especially a synchronization primitive,
// is a flawed design.
// It's not clear exactly which data needs to be protected.
//
// call with the mutex
void
LoadMonitor::update()
{
using namespace std::chrono_literals;
auto now = UptimeClock::now();
if (now == mLastUpdate) // current
return;
// VFALCO TODO Why 8?
if ((now < mLastUpdate) || (now > (mLastUpdate + 8s)))
{
// way out of date
mCounts = 0;
mLatencyEvents = 0;
mLatencyMSAvg = 0ms;
mLatencyMSPeak = 0ms;
mLastUpdate = now;
return;
}
// do exponential decay
/*
David:
"Imagine if you add 10 to something every second. And you
also reduce it by 1/4 every second. It will "idle" at 40,
corresponding to 10 counts per second."
*/
do
{
mLastUpdate += 1s;
mCounts -= ((mCounts + 3) / 4);
mLatencyEvents -= ((mLatencyEvents + 3) / 4);
mLatencyMSAvg -= (mLatencyMSAvg / 4);
mLatencyMSPeak -= (mLatencyMSPeak / 4);
} while (mLastUpdate < now);
}
void
LoadMonitor::addLoadSample(LoadEvent const& s)
{
using namespace std::chrono;
auto const total = s.runTime() + s.waitTime();
// Don't include "jitter" as part of the latency
auto const latency = total < 2ms ? 0ms : round<milliseconds>(total);
if (latency > 500ms)
{
auto mj = (latency > 1s) ? j_.warn() : j_.info();
JLOG(mj) << "Job: " << s.name() << " run: " << round<milliseconds>(s.runTime()).count()
<< "ms"
<< " wait: " << round<milliseconds>(s.waitTime()).count() << "ms";
}
addSamples(1, latency);
}
/* Add multiple samples
@param count The number of samples to add
@param latencyMS The total number of milliseconds
*/
void
LoadMonitor::addSamples(int count, std::chrono::milliseconds latency)
{
std::scoped_lock const sl(mutex_);
update();
mCounts += count;
mLatencyEvents += count;
mLatencyMSAvg += latency;
mLatencyMSPeak += latency;
auto const latencyPeak = mLatencyEvents * latency * 4 / count;
if (mLatencyMSPeak < latencyPeak)
mLatencyMSPeak = latencyPeak;
}
void
LoadMonitor::setTargetLatency(std::chrono::milliseconds avg, std::chrono::milliseconds pk)
{
mTargetLatencyAvg = avg;
mTargetLatencyPk = pk;
}
bool
LoadMonitor::isOverTarget(std::chrono::milliseconds avg, std::chrono::milliseconds peak)
{
using namespace std::chrono_literals;
return (mTargetLatencyPk > 0ms && (peak > mTargetLatencyPk)) ||
(mTargetLatencyAvg > 0ms && (avg > mTargetLatencyAvg));
}
bool
LoadMonitor::isOver()
{
std::scoped_lock const sl(mutex_);
update();
if (mLatencyEvents == 0)
return false;
return isOverTarget(
mLatencyMSAvg / (mLatencyEvents * 4), mLatencyMSPeak / (mLatencyEvents * 4));
}
LoadMonitor::Stats
LoadMonitor::getStats()
{
using namespace std::chrono_literals;
Stats stats;
std::scoped_lock const sl(mutex_);
update();
stats.count = mCounts / 4;
if (mLatencyEvents == 0)
{
stats.latencyAvg = 0ms;
stats.latencyPeak = 0ms;
}
else
{
stats.latencyAvg = mLatencyMSAvg / (mLatencyEvents * 4);
stats.latencyPeak = mLatencyMSPeak / (mLatencyEvents * 4);
}
stats.isOverloaded = isOverTarget(stats.latencyAvg, stats.latencyPeak);
return stats;
}
} // namespace xrpl