diff --git a/src/backend/common/Hashrate.cpp b/src/backend/common/Hashrate.cpp index 88709102..30b39288 100644 --- a/src/backend/common/Hashrate.cpp +++ b/src/backend/common/Hashrate.cpp @@ -34,12 +34,18 @@ #include "base/io/json/Json.h" #include "base/tools/Chrono.h" #include "base/tools/Handle.h" +#include "base/io/log/Log.h" -inline static const char *format(double h, char *buf, size_t size) +inline static const char *format(double h, char *buf, size_t size, double errorDown, double errorUp) { if (std::isnormal(h)) { - snprintf(buf, size, (h < 100.0) ? "%04.2f" : "%03.1f", h); + if (std::max(errorDown, errorUp) >= (h < 100.0 ? 0.01 : 0.1)) { + snprintf(buf, size, (h < 100.0) ? "%5.2f" BLACK_BOLD("(-%.2f/+%.2f}") : "%5.1f" BLACK_BOLD("(-%.1f/+%.1f)"), h, errorDown, errorUp); + } + else { + snprintf(buf, size, (h < 100.0) ? "%5.2f" : "%5.1f", h); + } return buf; } @@ -52,12 +58,16 @@ xmrig::Hashrate::Hashrate(size_t threads) : { m_counts = new uint64_t*[threads]; m_timestamps = new uint64_t*[threads]; - m_top = new uint32_t[threads]; + m_head = new uint32_t[threads]; + m_tail = new uint32_t*[threads]; + const uint64_t now = xmrig::Chrono::highResolutionMSecs(); for (size_t i = 0; i < threads; i++) { m_counts[i] = new uint64_t[kBucketSize](); m_timestamps[i] = new uint64_t[kBucketSize](); - m_top[i] = 0; + m_head[i] = 0; + m_tail[i] = new uint32_t[3](); + m_timestamps[i][0] = now; } } @@ -67,92 +77,97 @@ xmrig::Hashrate::~Hashrate() for (size_t i = 0; i < m_threads; i++) { delete [] m_counts[i]; delete [] m_timestamps[i]; + delete [] m_tail[i]; } delete [] m_counts; delete [] m_timestamps; - delete [] m_top; + delete [] m_head; + delete [] m_tail; } -double xmrig::Hashrate::calc(size_t ms) const + +xmrig::Hashrate::Value xmrig::Hashrate::calc(size_t threadId, Intervals ms) const { - double result = 0.0; - double data; - - for (size_t i = 0; i < m_threads; ++i) { - data = calc(i, ms); - if (std::isnormal(data)) { - result += data; - } + TimeRange time; + uint64_t count; + if (!findRecords(threadId, ms, time, count)) { + return { nan(""), nan(""), nan("") }; } + return { count * 1000.0 / (time.second - time.first), 0.0, 0.0 }; +} - return result; +xmrig::Hashrate::Value xmrig::Hashrate::calc(Intervals ms) const +{ + const uint64_t now = xmrig::Chrono::highResolutionMSecs(); + uint64_t time_earliest[2] = { now, 0 }; + uint64_t time_latest[2] = { 0, now }; + uint64_t total_count = 0; + double result = 0.0; + for (size_t threadId = 0; threadId < m_threads; ++threadId) { + TimeRange time; + uint64_t count; + if (!findRecords(threadId, ms, time, count)) { + continue; + } + for (size_t j = 0; j < 2; ++j) { + if ((time.first < time_earliest[j]) ^ j) { + time_earliest[j] = time.first; + } + if ((time.second > time_latest[j]) ^ j) { + time_latest[j] = time.second; + } + } + total_count += count; + result += count * 1000.0 / (time.second - time.first); + } + const double lower = total_count * 1000.0 / (time_latest[0] - time_earliest[0]); + const double upper = total_count * 1000.0 / (time_latest[1] - time_earliest[1]); + return { result, result - lower, upper - result }; } -double xmrig::Hashrate::calc(size_t threadId, size_t ms) const +bool xmrig::Hashrate::findRecords(size_t threadId, Intervals ms, TimeRange &time, uint64_t &count) const { assert(threadId < m_threads); if (threadId >= m_threads) { - return nan(""); + return false; } - uint64_t earliestHashCount = 0; - uint64_t earliestStamp = 0; - bool haveFullSet = false; - const uint64_t timeStampLimit = xmrig::Chrono::highResolutionMSecs() - ms; - uint64_t* timestamps = m_timestamps[threadId]; - uint64_t* counts = m_counts[threadId]; - - const size_t idx_start = (m_top[threadId] - 1) & kBucketMask; - size_t idx = idx_start; - - uint64_t lastestStamp = timestamps[idx]; - uint64_t lastestHashCnt = counts[idx]; - - do { - if (timestamps[idx] < timeStampLimit) { - haveFullSet = (timestamps[idx] != 0); - if (idx != idx_start) { - idx = (idx + 1) & kBucketMask; - earliestStamp = timestamps[idx]; - earliestHashCount = counts[idx]; - } - break; + const uint32_t head = m_head[threadId]; + // time[tale] < timeStampLimit <= time[later_tail] <= time[head] + uint32_t &tail = m_tail[threadId][ms == ShortInterval ? 0 : (ms == MediumInterval ? 1 : 2)]; + if (m_timestamps[threadId][tail] >= timeStampLimit) { + return false; + } + while (tail != head) { + const uint32_t later_tail = (tail + 1) & kBucketMask; + if (m_timestamps[threadId][later_tail] >= timeStampLimit) { + time = { m_timestamps[threadId][later_tail], m_timestamps[threadId][head] }; + count = m_counts[threadId][head] - m_counts[threadId][later_tail]; + return true; } - idx = (idx - 1) & kBucketMask; - } while (idx != idx_start); - - if (!haveFullSet || earliestStamp == 0 || lastestStamp == 0) { - return nan(""); + tail = later_tail; } - - if (lastestStamp - earliestStamp == 0) { - return nan(""); - } - - const auto hashes = static_cast(lastestHashCnt - earliestHashCount); - const auto time = static_cast(lastestStamp - earliestStamp) / 1000.0; - - return hashes / time; + return false; } void xmrig::Hashrate::add(size_t threadId, uint64_t count, uint64_t timestamp) { - const size_t top = m_top[threadId]; - m_counts[threadId][top] = count; - m_timestamps[threadId][top] = timestamp; + const uint32_t head = (m_head[threadId] + 1) & kBucketMask; + m_counts[threadId][head] = count; + m_timestamps[threadId][head] = timestamp; - m_top[threadId] = (top + 1) & kBucketMask; + m_head[threadId] = head; } -const char *xmrig::Hashrate::format(double h, char *buf, size_t size) +const char *xmrig::Hashrate::format(Value h, char *buf, size_t size) { - return ::format(h, buf, size); + return ::format(h.estimate, buf, size, h.errorDown, h.errorUp); } diff --git a/src/backend/common/Hashrate.h b/src/backend/common/Hashrate.h index 59e1afe1..89ed39b3 100644 --- a/src/backend/common/Hashrate.h +++ b/src/backend/common/Hashrate.h @@ -28,6 +28,7 @@ #include #include +#include #include "3rdparty/rapidjson/fwd.h" @@ -42,6 +43,21 @@ class Hashrate public: XMRIG_DISABLE_COPY_MOVE_DEFAULT(Hashrate) + struct Value { + Value() = default; + Value(const double estimate, const double errorDown, const double errorUp): estimate(estimate), errorDown(errorDown), errorUp(errorUp) {} + + inline operator double() const { return estimate; } + inline Value operator *(const double v) const { return { estimate * v , errorDown * v, errorUp * v}; } + inline Value& operator +=(const Value &o) { estimate += o.estimate; errorDown += o.errorDown; errorUp += o.errorUp; return *this; } + + double estimate; + double errorDown; + double errorUp; + }; + + typedef std::pair TimeRange; + enum Intervals { ShortInterval = 10000, MediumInterval = 60000, @@ -50,13 +66,13 @@ public: Hashrate(size_t threads); ~Hashrate(); - double calc(size_t ms) const; - double calc(size_t threadId, size_t ms) const; + Value calc(Intervals ms) const; + Value calc(size_t threadId, Intervals ms) const; void add(size_t threadId, uint64_t count, uint64_t timestamp); inline size_t threads() const { return m_threads; } - static const char *format(double h, char *buf, size_t size); + static const char *format(Value h, char *buf, size_t size); static rapidjson::Value normalize(double d); # ifdef XMRIG_FEATURE_API @@ -65,11 +81,14 @@ public: # endif private: + bool findRecords(size_t threadId, Intervals ms, TimeRange &time, uint64_t &count) const; + constexpr static size_t kBucketSize = 2 << 11; constexpr static size_t kBucketMask = kBucketSize - 1; size_t m_threads; - uint32_t* m_top; + uint32_t* m_head; + uint32_t** m_tail; uint64_t** m_counts; uint64_t** m_timestamps; }; diff --git a/src/core/Miner.cpp b/src/core/Miner.cpp index d92e9915..844c51b0 100644 --- a/src/core/Miner.cpp +++ b/src/core/Miner.cpp @@ -244,8 +244,9 @@ public: void printHashrate(bool details) { - char num[16 * 4] = { 0 }; - double speed[3] = { 0.0 }; + constexpr size_t num_width = 32; + char num[num_width * 4] = {}; + Hashrate::Value speed[3] = {}; for (auto backend : backends) { const auto hashrate = backend->hashrate(); @@ -304,12 +305,12 @@ public: } # endif - LOG_INFO("%s " WHITE_BOLD("speed") " 10s/60s/15m " CYAN_BOLD("%s") CYAN(" %s %s ") CYAN_BOLD("%s") " max " CYAN_BOLD("%s %s"), + LOG_INFO("%s " WHITE_BOLD("speed") " 10s/60s/15m " CYAN_BOLD("%s") " " CYAN("%s") " " CYAN("%s") " " CYAN_BOLD("%s") " max " CYAN_BOLD("%s %s"), Tags::miner(), - Hashrate::format(speed[0] * scale, num, sizeof(num) / 4), - Hashrate::format(speed[1] * scale, num + 16, sizeof(num) / 4), - Hashrate::format(speed[2] * scale, num + 16 * 2, sizeof(num) / 4), h, - Hashrate::format(maxHashrate[algorithm] * scale, num + 16 * 3, sizeof(num) / 4), h + Hashrate::format(speed[0] * scale, num, num_width), + Hashrate::format(speed[1] * scale, num + num_width, num_width), + Hashrate::format(speed[2] * scale, num + num_width, num_width), h, + Hashrate::format(maxHashrate[algorithm] * scale, num + num_width * 3, num_width), h ); } @@ -327,7 +328,7 @@ public: bool battery_power = false; Controller *controller; Job job; - mutable std::map maxHashrate; + mutable std::map maxHashrate; std::vector backends; String userJobId; Timer *timer = nullptr; @@ -570,7 +571,7 @@ void xmrig::Miner::onConfigChanged(Config *config, Config *previousConfig) void xmrig::Miner::onTimer(const Timer *) { - double maxHashrate = 0.0; + Hashrate::Value maxHashrate{}; const auto healthPrintTime = d_ptr->controller->config()->healthPrintTime(); for (IBackend *backend : d_ptr->backends) {