Hashrate: display numerical error

Different workers store stats asynchronously and with different frequency.
It has direct impact on accuracy of hashrate calculations.
Error calculation overhead balanced with faster iteration due to tail index

- Hashrate is being calculated as previously
- Error is being displayed when it's above zero
This commit is contained in:
cohcho 2020-10-09 14:31:52 +00:00
parent 1fdc8631e3
commit 9826039373
3 changed files with 107 additions and 72 deletions

View file

@ -34,12 +34,18 @@
#include "base/io/json/Json.h" #include "base/io/json/Json.h"
#include "base/tools/Chrono.h" #include "base/tools/Chrono.h"
#include "base/tools/Handle.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)) { 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; return buf;
} }
@ -52,12 +58,16 @@ xmrig::Hashrate::Hashrate(size_t threads) :
{ {
m_counts = new uint64_t*[threads]; m_counts = new uint64_t*[threads];
m_timestamps = 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++) { for (size_t i = 0; i < threads; i++) {
m_counts[i] = new uint64_t[kBucketSize](); m_counts[i] = new uint64_t[kBucketSize]();
m_timestamps[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++) { for (size_t i = 0; i < m_threads; i++) {
delete [] m_counts[i]; delete [] m_counts[i];
delete [] m_timestamps[i]; delete [] m_timestamps[i];
delete [] m_tail[i];
} }
delete [] m_counts; delete [] m_counts;
delete [] m_timestamps; 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; TimeRange time;
double data; uint64_t count;
if (!findRecords(threadId, ms, time, count)) {
for (size_t i = 0; i < m_threads; ++i) { return { nan(""), nan(""), nan("") };
data = calc(i, ms);
if (std::isnormal(data)) {
result += data;
}
} }
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); assert(threadId < m_threads);
if (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; const uint64_t timeStampLimit = xmrig::Chrono::highResolutionMSecs() - ms;
uint64_t* timestamps = m_timestamps[threadId]; const uint32_t head = m_head[threadId];
uint64_t* counts = m_counts[threadId]; // time[tale] < timeStampLimit <= time[later_tail] <= time[head]
uint32_t &tail = m_tail[threadId][ms == ShortInterval ? 0 : (ms == MediumInterval ? 1 : 2)];
const size_t idx_start = (m_top[threadId] - 1) & kBucketMask; if (m_timestamps[threadId][tail] >= timeStampLimit) {
size_t idx = idx_start; return false;
}
uint64_t lastestStamp = timestamps[idx]; while (tail != head) {
uint64_t lastestHashCnt = counts[idx]; const uint32_t later_tail = (tail + 1) & kBucketMask;
if (m_timestamps[threadId][later_tail] >= timeStampLimit) {
do { time = { m_timestamps[threadId][later_tail], m_timestamps[threadId][head] };
if (timestamps[idx] < timeStampLimit) { count = m_counts[threadId][head] - m_counts[threadId][later_tail];
haveFullSet = (timestamps[idx] != 0); return true;
if (idx != idx_start) {
idx = (idx + 1) & kBucketMask;
earliestStamp = timestamps[idx];
earliestHashCount = counts[idx];
}
break;
} }
idx = (idx - 1) & kBucketMask; tail = later_tail;
} while (idx != idx_start);
if (!haveFullSet || earliestStamp == 0 || lastestStamp == 0) {
return nan("");
} }
return false;
if (lastestStamp - earliestStamp == 0) {
return nan("");
}
const auto hashes = static_cast<double>(lastestHashCnt - earliestHashCount);
const auto time = static_cast<double>(lastestStamp - earliestStamp) / 1000.0;
return hashes / time;
} }
void xmrig::Hashrate::add(size_t threadId, uint64_t count, uint64_t timestamp) void xmrig::Hashrate::add(size_t threadId, uint64_t count, uint64_t timestamp)
{ {
const size_t top = m_top[threadId]; const uint32_t head = (m_head[threadId] + 1) & kBucketMask;
m_counts[threadId][top] = count; m_counts[threadId][head] = count;
m_timestamps[threadId][top] = timestamp; 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);
} }

View file

@ -28,6 +28,7 @@
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <utility>
#include "3rdparty/rapidjson/fwd.h" #include "3rdparty/rapidjson/fwd.h"
@ -42,6 +43,21 @@ class Hashrate
public: public:
XMRIG_DISABLE_COPY_MOVE_DEFAULT(Hashrate) 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<uint64_t, uint64_t> TimeRange;
enum Intervals { enum Intervals {
ShortInterval = 10000, ShortInterval = 10000,
MediumInterval = 60000, MediumInterval = 60000,
@ -50,13 +66,13 @@ public:
Hashrate(size_t threads); Hashrate(size_t threads);
~Hashrate(); ~Hashrate();
double calc(size_t ms) const; Value calc(Intervals ms) const;
double calc(size_t threadId, size_t ms) const; Value calc(size_t threadId, Intervals ms) const;
void add(size_t threadId, uint64_t count, uint64_t timestamp); void add(size_t threadId, uint64_t count, uint64_t timestamp);
inline size_t threads() const { return m_threads; } 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); static rapidjson::Value normalize(double d);
# ifdef XMRIG_FEATURE_API # ifdef XMRIG_FEATURE_API
@ -65,11 +81,14 @@ public:
# endif # endif
private: 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 kBucketSize = 2 << 11;
constexpr static size_t kBucketMask = kBucketSize - 1; constexpr static size_t kBucketMask = kBucketSize - 1;
size_t m_threads; size_t m_threads;
uint32_t* m_top; uint32_t* m_head;
uint32_t** m_tail;
uint64_t** m_counts; uint64_t** m_counts;
uint64_t** m_timestamps; uint64_t** m_timestamps;
}; };

View file

@ -244,8 +244,9 @@ public:
void printHashrate(bool details) void printHashrate(bool details)
{ {
char num[16 * 4] = { 0 }; constexpr size_t num_width = 32;
double speed[3] = { 0.0 }; char num[num_width * 4] = {};
Hashrate::Value speed[3] = {};
for (auto backend : backends) { for (auto backend : backends) {
const auto hashrate = backend->hashrate(); const auto hashrate = backend->hashrate();
@ -304,12 +305,12 @@ public:
} }
# endif # 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(), Tags::miner(),
Hashrate::format(speed[0] * scale, num, sizeof(num) / 4), Hashrate::format(speed[0] * scale, num, num_width),
Hashrate::format(speed[1] * scale, num + 16, sizeof(num) / 4), Hashrate::format(speed[1] * scale, num + num_width, num_width),
Hashrate::format(speed[2] * scale, num + 16 * 2, sizeof(num) / 4), h, Hashrate::format(speed[2] * scale, num + num_width, num_width), h,
Hashrate::format(maxHashrate[algorithm] * scale, num + 16 * 3, sizeof(num) / 4), h Hashrate::format(maxHashrate[algorithm] * scale, num + num_width * 3, num_width), h
); );
} }
@ -327,7 +328,7 @@ public:
bool battery_power = false; bool battery_power = false;
Controller *controller; Controller *controller;
Job job; Job job;
mutable std::map<Algorithm::Id, double> maxHashrate; mutable std::map<Algorithm::Id, Hashrate::Value> maxHashrate;
std::vector<IBackend *> backends; std::vector<IBackend *> backends;
String userJobId; String userJobId;
Timer *timer = nullptr; Timer *timer = nullptr;
@ -570,7 +571,7 @@ void xmrig::Miner::onConfigChanged(Config *config, Config *previousConfig)
void xmrig::Miner::onTimer(const Timer *) void xmrig::Miner::onTimer(const Timer *)
{ {
double maxHashrate = 0.0; Hashrate::Value maxHashrate{};
const auto healthPrintTime = d_ptr->controller->config()->healthPrintTime(); const auto healthPrintTime = d_ptr->controller->config()->healthPrintTime();
for (IBackend *backend : d_ptr->backends) { for (IBackend *backend : d_ptr->backends) {