#include #include #include #include #include #include #include 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(total); if (latency > 500ms) { auto mj = (latency > 1s) ? j_.warn() : j_.info(); JLOG(mj) << "Job: " << s.name() << " run: " << round(s.runTime()).count() << "ms" << " wait: " << round(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