From 2ecece7b3d77e5debb592649bf3b7d952abd6aa9 Mon Sep 17 00:00:00 2001
From: SChernykh <sergey.v.chernykh@gmail.com>
Date: Wed, 14 Oct 2020 19:45:05 +0200
Subject: [PATCH 1/2] Added benchmark and stress test

Easy to use and zero configuration embedded benchmark/stress test.
---
 cmake/flags.cmake                        |   4 +-
 doc/BENCHMARK.md                         |  29 +++++++
 src/backend/common/Worker.h              |   9 +-
 src/backend/common/WorkerJob.h           |   4 +-
 src/backend/common/Workers.cpp           | 102 +++++++++++++++++------
 src/backend/common/Workers.h             |   4 +-
 src/backend/common/interfaces/IBackend.h |   2 +-
 src/backend/common/interfaces/IWorker.h  |   2 +
 src/backend/cpu/CpuBackend.cpp           |   4 +-
 src/backend/cpu/CpuBackend.h             |   2 +-
 src/backend/cpu/CpuWorker.cpp            |  23 ++++-
 src/backend/cuda/CudaBackend.cpp         |   4 +-
 src/backend/cuda/CudaBackend.h           |   2 +-
 src/backend/opencl/OclBackend.cpp        |   4 +-
 src/backend/opencl/OclBackend.h          |   2 +-
 src/base/base.cmake                      |   2 +
 src/base/kernel/config/BaseTransform.cpp |  16 +++-
 src/base/kernel/interfaces/IConfig.h     |   2 +
 src/base/net/stratum/Job.cpp             |   2 +
 src/base/net/stratum/Job.h               |   3 +
 src/base/net/stratum/NullClient.cpp      |  63 ++++++++++++++
 src/base/net/stratum/NullClient.h        |  77 +++++++++++++++++
 src/base/net/stratum/Pool.cpp            |  22 ++++-
 src/base/net/stratum/Pool.h              |   6 +-
 src/core/Miner.cpp                       |  10 ++-
 src/core/config/Config_platform.h        |   2 +
 src/core/config/usage.h                  |   2 +
 src/net/Network.cpp                      |   5 +-
 28 files changed, 354 insertions(+), 55 deletions(-)
 create mode 100644 doc/BENCHMARK.md
 create mode 100644 src/base/net/stratum/NullClient.cpp
 create mode 100644 src/base/net/stratum/NullClient.h

diff --git a/cmake/flags.cmake b/cmake/flags.cmake
index 5edad3392..788d9bdce 100644
--- a/cmake/flags.cmake
+++ b/cmake/flags.cmake
@@ -64,8 +64,8 @@ elseif (CMAKE_CXX_COMPILER_ID MATCHES MSVC)
     set(CMAKE_C_FLAGS_RELEASE "/MT /O2 /Oi /DNDEBUG /GL")
     set(CMAKE_CXX_FLAGS_RELEASE "/MT /O2 /Oi /DNDEBUG /GL")
 
-    set(CMAKE_C_FLAGS_RELWITHDEBINFO "/Ob1 /GL")
-    set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "/Ob1 /GL")
+    set(CMAKE_C_FLAGS_RELWITHDEBINFO "/Ob1 /Zi")
+    set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "/Ob1 /Zi")
 
     add_definitions(/D_CRT_SECURE_NO_WARNINGS)
     add_definitions(/D_CRT_NONSTDC_NO_WARNINGS)
diff --git a/doc/BENCHMARK.md b/doc/BENCHMARK.md
new file mode 100644
index 000000000..a05294b40
--- /dev/null
+++ b/doc/BENCHMARK.md
@@ -0,0 +1,29 @@
+# Embedded benchmark
+
+You can run with XMRig with the following commands:
+```
+xmrig --bench=1M
+xmrig --bench=10M
+xmrig --bench=1M -a rx/wow
+xmrig --bench=10M -a rx/wow
+```
+This will run 1 or 10 millions RandomX hashes and print the time it took. First two commands use Monero variant (2 MB per thread, best for Zen2/Zen3 CPUs), second two commands use Wownero variant (1 MB per thread, useful for Intel and 1st gen Zen/Zen+ CPUs).
+
+Checksum of all the hashes will be also printed to check stability of your hardware: if it's green then it's correct, if it's red then there was hardware error during computation. No Internet connection is required for the benchmark.
+
+Double check that you see `Huge pages 100%` both for dataset and for all threads, and also check for `msr register values ... has been set successfully` - without this result will be far from the best. Running as administrator is required for MSR and huge pages to be set up properly.
+
+![Benchmark example](https://i.imgur.com/PST3BYc.png)
+
+### Benchmark with custom config
+
+You can run benchmark with any configuration you want. Just start without command line parameteres, use regular config.json and add `"benchmark":"1M",` on the next line after pool url. 
+
+# Stress test
+
+You can also run continuous stress-test that is as close to the real RandomX mining as possible and doesn't require any configuration:
+```
+xmrig --stress
+xmrig --stress -a rx/wow
+```
+This will require Internet connection and will run indefinitely.
\ No newline at end of file
diff --git a/src/backend/common/Worker.h b/src/backend/common/Worker.h
index b0825811b..cc38450fa 100644
--- a/src/backend/common/Worker.h
+++ b/src/backend/common/Worker.h
@@ -45,6 +45,8 @@ public:
     inline const VirtualMemory *memory() const override   { return nullptr; }
     inline size_t id() const override                     { return m_id; }
     inline uint64_t rawHashes() const override            { return m_count; }
+    inline uint64_t benchData() const override            { return m_benchData; }
+    inline uint64_t benchDoneTime() const override        { return m_benchDoneTime; }
     void getHashrateData(uint64_t& hashCount, uint64_t& timeStamp) const override;
     inline void jobEarlyNotification(const Job&) override {}
 
@@ -56,8 +58,11 @@ protected:
     uint64_t m_hashCount[2] = {};
     uint64_t m_timestamp[2] = {};
     std::atomic<uint32_t> m_index = {};
-    uint32_t m_node     = 0;
-    uint64_t m_count    = 0;
+    uint32_t m_node      = 0;
+    uint64_t m_count     = 0;
+
+    uint64_t m_benchData     = 0;
+    uint64_t m_benchDoneTime = 0;
 };
 
 
diff --git a/src/backend/common/WorkerJob.h b/src/backend/common/WorkerJob.h
index e835ff1de..a37a05fa9 100644
--- a/src/backend/common/WorkerJob.h
+++ b/src/backend/common/WorkerJob.h
@@ -68,7 +68,7 @@ public:
     {
         m_rounds[index()]++;
 
-        if ((m_rounds[index()] % rounds) == 0) {
+        if ((m_rounds[index()] & (rounds - 1)) == 0) {
             for (size_t i = 0; i < N; ++i) {
                 if (!Nonce::next(index(), nonce(i), rounds * roundSize, nonceMask())) {
                     return false;
@@ -130,7 +130,7 @@ inline bool xmrig::WorkerJob<1>::nextRound(uint32_t rounds, uint32_t roundSize)
 
     uint32_t* n = nonce();
 
-    if ((m_rounds[index()] % rounds) == 0) {
+    if ((m_rounds[index()] & (rounds - 1)) == 0) {
         if (!Nonce::next(index(), n, rounds * roundSize, nonceMask())) {
             return false;
         }
diff --git a/src/backend/common/Workers.cpp b/src/backend/common/Workers.cpp
index 78816c1ef..6a4cf0cad 100644
--- a/src/backend/common/Workers.cpp
+++ b/src/backend/common/Workers.cpp
@@ -29,8 +29,10 @@
 #include "backend/common/Workers.h"
 #include "backend/cpu/CpuWorker.h"
 #include "base/io/log/Log.h"
+#include "base/io/log/Tags.h"
 #include "base/tools/Chrono.h"
 #include "base/tools/Object.h"
+#include "core/Miner.h"
 
 
 #ifdef XMRIG_FEATURE_OPENCL
@@ -63,6 +65,10 @@ public:
 
     Hashrate *hashrate = nullptr;
     IBackend *backend  = nullptr;
+
+    uint32_t bench      = 0;
+    Algorithm benchAlgo = Algorithm::RX_0;
+    uint64_t startTime  = 0;
 };
 
 
@@ -101,6 +107,11 @@ void xmrig::Workers<T>::setBackend(IBackend *backend)
 template<class T>
 void xmrig::Workers<T>::start(const std::vector<T> &data)
 {
+    if (!data.empty()) {
+        d_ptr->bench = data.front().miner->job().bench();
+        d_ptr->benchAlgo = data.front().miner->job().algorithm();
+    }
+
     for (const T &item : data) {
         m_workers.push_back(new Thread<T>(d_ptr->backend, m_workers.size(), item));
     }
@@ -111,11 +122,12 @@ void xmrig::Workers<T>::start(const std::vector<T> &data)
     for (Thread<T> *worker : m_workers) {
         worker->start(Workers<T>::onReady);
 
-        // This sleep is important for optimal caching!
-        // Threads must allocate scratchpads in order so that adjacent cores will use adjacent scratchpads
-        // Sub-optimal caching can result in up to 0.5% hashrate penalty
-        std::this_thread::sleep_for(std::chrono::milliseconds(20));
+        if (!d_ptr->bench) {
+            std::this_thread::sleep_for(std::chrono::milliseconds(20));
+        }
     }
+
+    d_ptr->startTime = Chrono::steadyMSecs();
 }
 
 
@@ -137,55 +149,91 @@ void xmrig::Workers<T>::stop()
 
 
 template<class T>
-void xmrig::Workers<T>::tick(uint64_t)
+static void getHashrateData(xmrig::IWorker* worker, uint64_t& hashCount, uint64_t& timeStamp)
+{
+    worker->getHashrateData(hashCount, timeStamp);
+}
+
+
+template<>
+static void getHashrateData<xmrig::CpuLaunchData>(xmrig::IWorker* worker, uint64_t& hashCount, uint64_t&)
+{
+    hashCount = worker->rawHashes();
+}
+
+
+template<class T>
+bool xmrig::Workers<T>::tick(uint64_t)
 {
     if (!d_ptr->hashrate) {
-        return;
+        return true;
     }
 
+    uint64_t timeStamp = Chrono::steadyMSecs();
+
     bool totalAvailable = true;
     uint64_t totalHashCount = 0;
 
+    uint32_t benchDone = 0;
+    uint64_t benchData = 0;
+    uint64_t benchDoneTime = 0;
+
     for (Thread<T> *handle : m_workers) {
-        if (handle->worker()) {
-            uint64_t hashCount, timeStamp;
-            handle->worker()->getHashrateData(hashCount, timeStamp);
+        IWorker* worker = handle->worker();
+        if (worker) {
+            uint64_t hashCount;
+            getHashrateData<T>(worker, hashCount, timeStamp);
             d_ptr->hashrate->add(handle->id() + 1, hashCount, timeStamp);
 
-            const uint64_t n = handle->worker()->rawHashes();
+            const uint64_t n = worker->rawHashes();
             if (n == 0) {
                 totalAvailable = false;
             }
             totalHashCount += n;
+
+            if (d_ptr->bench && worker->benchDoneTime()) {
+                ++benchDone;
+                benchData ^= worker->benchData();
+                if (worker->benchDoneTime() > benchDoneTime) {
+                    benchDoneTime = worker->benchDoneTime();
+                }
+            }
         }
     }
 
     if (totalAvailable) {
         d_ptr->hashrate->add(0, totalHashCount, Chrono::steadyMSecs());
     }
-}
 
+    if (d_ptr->bench && (benchDone == m_workers.size())) {
+        const double dt = (benchDoneTime - d_ptr->startTime) / 1000.0;
 
-template<>
-void xmrig::Workers<xmrig::CpuLaunchData>::tick(uint64_t)
-{
-    if (!d_ptr->hashrate) {
-        return;
-    }
+        static uint64_t hashCheck[Algorithm::MAX][2] = {};
+        hashCheck[Algorithm::RX_0][0] = 0x898B6E0431C28A6BULL;
+        hashCheck[Algorithm::RX_0][1] = 0xB5231262E2792B26ULL;
+        hashCheck[Algorithm::RX_WOW][0] = 0x0F3E5400B39EA96AULL;
+        hashCheck[Algorithm::RX_WOW][1] = 0x0F9E00C5A511C200ULL;
 
-    const uint64_t timestamp = Chrono::steadyMSecs();
-    uint64_t totalHashCount = 0;
-    for (Thread<CpuLaunchData> *handle : m_workers) {
-        if (handle->worker()) {
-            const uint64_t hashCount = handle->worker()->rawHashes();
-            d_ptr->hashrate->add(handle->id() + 1, hashCount, timestamp);
-            totalHashCount += hashCount;
+        int k = -1;
+
+        switch (d_ptr->bench) {
+        case 1000000:
+            k = 0;
+            break;
+
+        case 10000000:
+            k = 1;
+            break;
         }
+
+        const uint64_t checkData = (k >= 0) ? hashCheck[d_ptr->benchAlgo.id()][k] : 0;
+        const char* color = checkData ? ((benchData == checkData) ? GREEN_BOLD_S : RED_BOLD_S) : BLACK_BOLD_S;
+
+        LOG_INFO("%s Benchmark finished in %.3f seconds, hash sum = %s%016" PRIX64 CLEAR, Tags::miner(), dt, color, benchData);
+        return false;
     }
 
-    if (totalHashCount > 0) {
-        d_ptr->hashrate->add(0, totalHashCount, timestamp);
-    }
+    return true;
 }
 
 
diff --git a/src/backend/common/Workers.h b/src/backend/common/Workers.h
index 022009607..28d9501b2 100644
--- a/src/backend/common/Workers.h
+++ b/src/backend/common/Workers.h
@@ -63,7 +63,7 @@ public:
     void setBackend(IBackend *backend);
     void start(const std::vector<T> &data);
     void stop();
-    void tick(uint64_t ticks);
+    bool tick(uint64_t ticks);
     void jobEarlyNotification(const Job&);
 
 private:
@@ -88,8 +88,6 @@ void xmrig::Workers<T>::jobEarlyNotification(const Job& job)
 
 template<>
 IWorker *Workers<CpuLaunchData>::create(Thread<CpuLaunchData> *handle);
-template<>
-void Workers<CpuLaunchData>::tick(uint64_t);
 extern template class Workers<CpuLaunchData>;
 
 
diff --git a/src/backend/common/interfaces/IBackend.h b/src/backend/common/interfaces/IBackend.h
index 405d876a7..577213d35 100644
--- a/src/backend/common/interfaces/IBackend.h
+++ b/src/backend/common/interfaces/IBackend.h
@@ -60,7 +60,7 @@ public:
     virtual void setJob(const Job &job)                                 = 0;
     virtual void start(IWorker *worker, bool ready)                     = 0;
     virtual void stop()                                                 = 0;
-    virtual void tick(uint64_t ticks)                                   = 0;
+    virtual bool tick(uint64_t ticks)                                   = 0;
 
 #   ifdef XMRIG_FEATURE_API
     virtual rapidjson::Value toJSON(rapidjson::Document &doc) const     = 0;
diff --git a/src/backend/common/interfaces/IWorker.h b/src/backend/common/interfaces/IWorker.h
index 17048c645..3645f6a82 100644
--- a/src/backend/common/interfaces/IWorker.h
+++ b/src/backend/common/interfaces/IWorker.h
@@ -47,6 +47,8 @@ public:
     virtual size_t id() const                                 = 0;
     virtual size_t intensity() const                          = 0;
     virtual uint64_t rawHashes() const                        = 0;
+    virtual uint64_t benchData() const                        = 0;
+    virtual uint64_t benchDoneTime() const                    = 0;
     virtual void getHashrateData(uint64_t&, uint64_t&) const  = 0;
     virtual void start()                                      = 0;
     virtual void jobEarlyNotification(const Job&)             = 0;
diff --git a/src/backend/cpu/CpuBackend.cpp b/src/backend/cpu/CpuBackend.cpp
index e6678bcab..c3267d17f 100644
--- a/src/backend/cpu/CpuBackend.cpp
+++ b/src/backend/cpu/CpuBackend.cpp
@@ -387,9 +387,9 @@ void xmrig::CpuBackend::stop()
 }
 
 
-void xmrig::CpuBackend::tick(uint64_t ticks)
+bool xmrig::CpuBackend::tick(uint64_t ticks)
 {
-    d_ptr->workers.tick(ticks);
+    return d_ptr->workers.tick(ticks);
 }
 
 
diff --git a/src/backend/cpu/CpuBackend.h b/src/backend/cpu/CpuBackend.h
index 1046ec359..be63f4372 100644
--- a/src/backend/cpu/CpuBackend.h
+++ b/src/backend/cpu/CpuBackend.h
@@ -63,7 +63,7 @@ protected:
     void setJob(const Job &job) override;
     void start(IWorker *worker, bool ready) override;
     void stop() override;
-    void tick(uint64_t ticks) override;
+    bool tick(uint64_t ticks) override;
 
 #   ifdef XMRIG_FEATURE_API
     rapidjson::Value toJSON(rapidjson::Document &doc) const override;
diff --git a/src/backend/cpu/CpuWorker.cpp b/src/backend/cpu/CpuWorker.cpp
index ec571001b..5211bed24 100644
--- a/src/backend/cpu/CpuWorker.cpp
+++ b/src/backend/cpu/CpuWorker.cpp
@@ -29,6 +29,7 @@
 
 
 #include "backend/cpu/CpuWorker.h"
+#include "base/tools/Chrono.h"
 #include "core/Miner.h"
 #include "crypto/cn/CnCtx.h"
 #include "crypto/cn/CryptoNight_test.h"
@@ -59,8 +60,10 @@ static constexpr uint32_t kReserveCount = 32768;
 template<size_t N>
 inline bool nextRound(WorkerJob<N> &job)
 {
-    if (!job.nextRound(kReserveCount, 1)) {
-        JobResults::done(job.currentJob());
+    const Job& curJob = job.currentJob();
+    const uint32_t bench = curJob.bench();
+    if (!job.nextRound(bench ? 1 : kReserveCount, 1)) {
+        JobResults::done(curJob);
 
         return false;
     }
@@ -265,7 +268,18 @@ void xmrig::CpuWorker<N>::start()
 
             if (valid) {
                 for (size_t i = 0; i < N; ++i) {
-                    if (*reinterpret_cast<uint64_t*>(m_hash + (i * 32) + 24) < job.target()) {
+                    const uint64_t value = *reinterpret_cast<uint64_t*>(m_hash + (i * 32) + 24);
+
+                    if (job.bench()) {
+                        if (current_job_nonces[i] < job.bench()) {
+                            m_benchData ^= value;
+                        }
+                        else {
+                            m_benchDoneTime = Chrono::steadyMSecs();
+                            return;
+                        }
+                    }
+                    else if (value < job.target()) {
                         JobResults::submit(job, current_job_nonces[i], m_hash + (i * 32));
                     }
                 }
@@ -362,7 +376,8 @@ void xmrig::CpuWorker<N>::consumeJob()
         return;
     }
 
-    m_job.add(m_miner->job(), kReserveCount, Nonce::CPU);
+    const uint32_t bench = m_miner->job().bench();
+    m_job.add(m_miner->job(), bench ? 1 : kReserveCount, Nonce::CPU);
 
 #   ifdef XMRIG_ALGO_RANDOMX
     if (m_job.currentJob().algorithm().family() == Algorithm::RANDOM_X) {
diff --git a/src/backend/cuda/CudaBackend.cpp b/src/backend/cuda/CudaBackend.cpp
index 4d02ccbaa..ab5b22e53 100644
--- a/src/backend/cuda/CudaBackend.cpp
+++ b/src/backend/cuda/CudaBackend.cpp
@@ -501,9 +501,9 @@ void xmrig::CudaBackend::stop()
 }
 
 
-void xmrig::CudaBackend::tick(uint64_t ticks)
+bool xmrig::CudaBackend::tick(uint64_t ticks)
 {
-    d_ptr->workers.tick(ticks);
+    return d_ptr->workers.tick(ticks);
 }
 
 
diff --git a/src/backend/cuda/CudaBackend.h b/src/backend/cuda/CudaBackend.h
index 2e82c589a..ce684d5b3 100644
--- a/src/backend/cuda/CudaBackend.h
+++ b/src/backend/cuda/CudaBackend.h
@@ -63,7 +63,7 @@ protected:
     void setJob(const Job &job) override;
     void start(IWorker *worker, bool ready) override;
     void stop() override;
-    void tick(uint64_t ticks) override;
+    bool tick(uint64_t ticks) override;
 
 #   ifdef XMRIG_FEATURE_API
     rapidjson::Value toJSON(rapidjson::Document &doc) const override;
diff --git a/src/backend/opencl/OclBackend.cpp b/src/backend/opencl/OclBackend.cpp
index 764df70f4..7b99700b5 100644
--- a/src/backend/opencl/OclBackend.cpp
+++ b/src/backend/opencl/OclBackend.cpp
@@ -485,9 +485,9 @@ void xmrig::OclBackend::stop()
 }
 
 
-void xmrig::OclBackend::tick(uint64_t ticks)
+bool xmrig::OclBackend::tick(uint64_t ticks)
 {
-    d_ptr->workers.tick(ticks);
+    return d_ptr->workers.tick(ticks);
 }
 
 
diff --git a/src/backend/opencl/OclBackend.h b/src/backend/opencl/OclBackend.h
index 0ed7b8eb0..49325d412 100644
--- a/src/backend/opencl/OclBackend.h
+++ b/src/backend/opencl/OclBackend.h
@@ -63,7 +63,7 @@ protected:
     void setJob(const Job &job) override;
     void start(IWorker *worker, bool ready) override;
     void stop() override;
-    void tick(uint64_t ticks) override;
+    bool tick(uint64_t ticks) override;
 
 #   ifdef XMRIG_FEATURE_API
     rapidjson::Value toJSON(rapidjson::Document &doc) const override;
diff --git a/src/base/base.cmake b/src/base/base.cmake
index 0574d617e..6acf728de 100644
--- a/src/base/base.cmake
+++ b/src/base/base.cmake
@@ -44,6 +44,7 @@ set(HEADERS_BASE
     src/base/net/http/HttpListener.h
     src/base/net/stratum/BaseClient.h
     src/base/net/stratum/Client.h
+    src/base/net/stratum/NullClient.h
     src/base/net/stratum/Job.h
     src/base/net/stratum/NetworkState.h
     src/base/net/stratum/Pool.h
@@ -97,6 +98,7 @@ set(SOURCES_BASE
     src/base/net/http/Http.cpp
     src/base/net/stratum/BaseClient.cpp
     src/base/net/stratum/Client.cpp
+    src/base/net/stratum/NullClient.cpp
     src/base/net/stratum/Job.cpp
     src/base/net/stratum/NetworkState.cpp
     src/base/net/stratum/Pool.cpp
diff --git a/src/base/kernel/config/BaseTransform.cpp b/src/base/kernel/config/BaseTransform.cpp
index 97041e7de..3694c73f3 100644
--- a/src/base/kernel/config/BaseTransform.cpp
+++ b/src/base/kernel/config/BaseTransform.cpp
@@ -152,6 +152,8 @@ void xmrig::BaseTransform::transform(rapidjson::Document &doc, int key, const ch
         break;
 
     case IConfig::UrlKey: /* --url */
+    case IConfig::StressKey: /* --stress */
+    case IConfig::BenchKey: /* --bench */
     {
         if (!doc.HasMember(Pools::kPools)) {
             doc.AddMember(rapidjson::StringRef(Pools::kPools), rapidjson::kArrayType, doc.GetAllocator());
@@ -162,7 +164,19 @@ void xmrig::BaseTransform::transform(rapidjson::Document &doc, int key, const ch
             array.PushBack(rapidjson::kObjectType, doc.GetAllocator());
         }
 
-        set(doc, array[array.Size() - 1], Pool::kUrl, arg);
+        if (key == IConfig::UrlKey) {
+            set(doc, array[array.Size() - 1], Pool::kUrl, arg);
+        }
+        else {
+            set(doc, array[array.Size() - 1], Pool::kUrl, (key == IConfig::BenchKey) ? "offline" : "donate.v2.xmrig.com:3333");
+            set(doc, "cpu", "huge-pages-jit", true);
+            set(doc, "cpu", "priority", 2);
+            set(doc, "cpu", "yield", false);
+            if (key == IConfig::BenchKey) {
+                set(doc, array[array.Size() - 1], Pool::kBenchmark, arg);
+            }
+        }
+
         break;
     }
 
diff --git a/src/base/kernel/interfaces/IConfig.h b/src/base/kernel/interfaces/IConfig.h
index dd657c2d8..22f652271 100644
--- a/src/base/kernel/interfaces/IConfig.h
+++ b/src/base/kernel/interfaces/IConfig.h
@@ -77,6 +77,8 @@ public:
         TitleKey             = 1037,
         NoTitleKey           = 1038,
         PauseOnBatteryKey    = 1041,
+        StressKey            = 1042,
+        BenchKey             = 1043,
 
         // xmrig common
         CPUPriorityKey       = 1021,
diff --git a/src/base/net/stratum/Job.cpp b/src/base/net/stratum/Job.cpp
index 1de1c19b1..d790fccf4 100644
--- a/src/base/net/stratum/Job.cpp
+++ b/src/base/net/stratum/Job.cpp
@@ -154,6 +154,7 @@ void xmrig::Job::copy(const Job &other)
 {
     m_algorithm  = other.m_algorithm;
     m_nicehash   = other.m_nicehash;
+    m_bench      = other.m_bench;
     m_size       = other.m_size;
     m_clientId   = other.m_clientId;
     m_id         = other.m_id;
@@ -181,6 +182,7 @@ void xmrig::Job::move(Job &&other)
 {
     m_algorithm  = other.m_algorithm;
     m_nicehash   = other.m_nicehash;
+    m_bench      = other.m_bench;
     m_size       = other.m_size;
     m_clientId   = std::move(other.m_clientId);
     m_id         = std::move(other.m_id);
diff --git a/src/base/net/stratum/Job.h b/src/base/net/stratum/Job.h
index ba5a0aa2e..31e377606 100644
--- a/src/base/net/stratum/Job.h
+++ b/src/base/net/stratum/Job.h
@@ -87,6 +87,7 @@ public:
     inline uint8_t *blob()                              { return m_blob; }
     inline uint8_t fixedByte() const                    { return *(m_blob + 42); }
     inline uint8_t index() const                        { return m_index; }
+    inline uint32_t bench() const                       { return m_bench; }
     inline void reset()                                 { m_size = 0; m_diff = 0; }
     inline void setAlgorithm(const Algorithm::Id id)    { m_algorithm = id; }
     inline void setAlgorithm(const char *algo)          { m_algorithm = algo; }
@@ -96,6 +97,7 @@ public:
     inline void setHeight(uint64_t height)              { m_height = height; }
     inline void setIndex(uint8_t index)                 { m_index = index; }
     inline void setPoolWallet(const String &poolWallet) { m_poolWallet = poolWallet; }
+    inline void setBench(uint32_t bench)                { m_bench = bench; }
 
 #   ifdef XMRIG_PROXY_PROJECT
     inline char *rawBlob()                            { return m_rawBlob; }
@@ -117,6 +119,7 @@ private:
 
     Algorithm m_algorithm;
     bool m_nicehash     = false;
+    uint32_t m_bench    = 0;
     Buffer m_seed;
     size_t m_size       = 0;
     String m_clientId;
diff --git a/src/base/net/stratum/NullClient.cpp b/src/base/net/stratum/NullClient.cpp
new file mode 100644
index 000000000..a18638fe7
--- /dev/null
+++ b/src/base/net/stratum/NullClient.cpp
@@ -0,0 +1,63 @@
+/* XMRig
+ * Copyright 2018-2020 SChernykh   <https://github.com/SChernykh>
+ * Copyright 2016-2020 XMRig       <https://github.com/xmrig>, <support@xmrig.com>
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "3rdparty/rapidjson/document.h"
+#include "base/net/stratum/NullClient.h"
+#include "base/kernel/interfaces/IClientListener.h"
+
+
+xmrig::NullClient::NullClient(IClientListener* listener) :
+    m_listener(listener)
+{
+    m_job.setAlgorithm(Algorithm::RX_0);
+
+    std::vector<char> blob(112 * 2 + 1, '0');
+
+    blob.back() = '\0';
+    m_job.setBlob(blob.data());
+
+    blob[Job::kMaxSeedSize * 2] = '\0';
+    m_job.setSeedHash(blob.data());
+
+    m_job.setDiff(uint64_t(-1));
+    m_job.setHeight(1);
+
+    m_job.setId("00000000");
+}
+
+
+void xmrig::NullClient::connect()
+{
+    m_listener->onLoginSuccess(this);
+
+    rapidjson::Value params;
+    m_listener->onJobReceived(this, m_job, params);
+}
+
+
+void xmrig::NullClient::setPool(const Pool& pool)
+{
+    m_pool = pool;
+
+    if (!m_pool.algorithm().isValid()) {
+        m_pool.setAlgo(Algorithm::RX_0);
+    }
+
+    m_job.setAlgorithm(m_pool.algorithm().id());
+    m_job.setBench(m_pool.benchSize());
+}
diff --git a/src/base/net/stratum/NullClient.h b/src/base/net/stratum/NullClient.h
new file mode 100644
index 000000000..e6b024ba6
--- /dev/null
+++ b/src/base/net/stratum/NullClient.h
@@ -0,0 +1,77 @@
+/* XMRig
+ * Copyright 2018-2020 SChernykh   <https://github.com/SChernykh>
+ * Copyright 2016-2020 XMRig       <https://github.com/xmrig>, <support@xmrig.com>
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef XMRIG_NULLCLIENT_H
+#define XMRIG_NULLCLIENT_H
+
+
+#include "base/net/stratum/Client.h"
+
+
+namespace xmrig {
+
+
+class NullClient : public IClient
+{
+public:
+    XMRIG_DISABLE_COPY_MOVE_DEFAULT(NullClient)
+
+    NullClient(IClientListener* listener);
+    ~NullClient() override = default;
+
+    virtual bool disconnect() override { return true; }
+    virtual bool hasExtension(Extension extension) const noexcept override { return false; }
+    virtual bool isEnabled() const override { return true; }
+    virtual bool isTLS() const override { return false; }
+    virtual const char* mode() const override { return "benchmark"; }
+    virtual const char* tag() const override { return "null"; }
+    virtual const char* tlsFingerprint() const override { return nullptr; }
+    virtual const char* tlsVersion() const override { return nullptr; }
+    virtual const Job& job() const override { return m_job; }
+    virtual const Pool& pool() const override { return m_pool; }
+    virtual const String& ip() const override { return m_ip; }
+    virtual int id() const override { return 0; }
+    virtual int64_t send(const rapidjson::Value& obj, Callback callback) override { return 0; }
+    virtual int64_t send(const rapidjson::Value& obj) override { return 0; }
+    virtual int64_t sequence() const override { return 0; }
+    virtual int64_t submit(const JobResult& result) override { return 0; }
+    virtual void connect() override;
+    virtual void connect(const Pool& pool) override { setPool(pool); }
+    virtual void deleteLater() override {}
+    virtual void setAlgo(const Algorithm& algo) override {}
+    virtual void setEnabled(bool enabled) override {}
+    virtual void setPool(const Pool& pool) override;
+    virtual void setProxy(const ProxyUrl& proxy) override {}
+    virtual void setQuiet(bool quiet) override {}
+    virtual void setRetries(int retries) override {}
+    virtual void setRetryPause(uint64_t ms) override {}
+    virtual void tick(uint64_t now) override {}
+
+private:
+    IClientListener* m_listener;
+
+    Job m_job;
+    Pool m_pool;
+    String m_ip;
+};
+
+
+} /* namespace xmrig */
+
+
+#endif /* XMRIG_NULLCLIENT_H */
diff --git a/src/base/net/stratum/Pool.cpp b/src/base/net/stratum/Pool.cpp
index dc0ac7923..ba7b12bf4 100644
--- a/src/base/net/stratum/Pool.cpp
+++ b/src/base/net/stratum/Pool.cpp
@@ -50,6 +50,9 @@
 #endif
 
 
+#include "base/net/stratum/NullClient.h"
+
+
 namespace xmrig {
 
 
@@ -72,6 +75,7 @@ const char *Pool::kSOCKS5                 = "socks5";
 const char *Pool::kTls                    = "tls";
 const char *Pool::kUrl                    = "url";
 const char *Pool::kUser                   = "user";
+const char *Pool::kBenchmark              = "benchmark";
 
 
 const char *Pool::kNicehashHost = "nicehash.com";
@@ -125,7 +129,20 @@ xmrig::Pool::Pool(const rapidjson::Value &object) :
     m_flags.set(FLAG_NICEHASH, Json::getBool(object, kNicehash) || m_url.host().contains(kNicehashHost));
     m_flags.set(FLAG_TLS,      Json::getBool(object, kTls) || m_url.isTLS());
 
-    if (m_daemon.isValid()) {
+    const char* benchSize = Json::getString(object, kBenchmark, nullptr);
+    if (benchSize) {
+        if (stricmp(benchSize, "1M") == 0) {
+            m_benchSize = 1000000;
+        }
+        else if (stricmp(benchSize, "10M") == 0) {
+            m_benchSize = 10000000;
+        }
+    }
+
+    if (m_benchSize) {
+        m_mode = MODE_BENCHMARK;
+    }
+    else if (m_daemon.isValid()) {
         m_mode = MODE_SELF_SELECT;
     }
     else if (Json::getBool(object, kDaemon)) {
@@ -217,6 +234,9 @@ xmrig::IClient *xmrig::Pool::createClient(int id, IClientListener *listener) con
         client = new AutoClient(id, Platform::userAgent(), listener);
     }
 #   endif
+    else if (m_mode == MODE_BENCHMARK) {
+        client = new NullClient(listener);
+    }
 
     assert(client != nullptr);
 
diff --git a/src/base/net/stratum/Pool.h b/src/base/net/stratum/Pool.h
index 7569eae79..dd4158a93 100644
--- a/src/base/net/stratum/Pool.h
+++ b/src/base/net/stratum/Pool.h
@@ -50,7 +50,8 @@ public:
         MODE_POOL,
         MODE_DAEMON,
         MODE_SELF_SELECT,
-        MODE_AUTO_ETH
+        MODE_AUTO_ETH,
+        MODE_BENCHMARK,
     };
 
     static const String kDefaultPassword;
@@ -71,6 +72,7 @@ public:
     static const char *kTls;
     static const char *kUrl;
     static const char *kUser;
+    static const char* kBenchmark;
     static const char *kNicehashHost;
 
     constexpr static int kKeepAliveTimeout         = 60;
@@ -97,6 +99,7 @@ public:
     inline const Url &daemon() const                    { return m_daemon; }
     inline int keepAlive() const                        { return m_keepAlive; }
     inline Mode mode() const                            { return m_mode; }
+    inline uint64_t benchSize() const                   { return m_benchSize; }
     inline uint16_t port() const                        { return m_url.port(); }
     inline uint64_t pollInterval() const                { return m_pollInterval; }
     inline void setAlgo(const Algorithm &algorithm)     { m_algorithm = algorithm; }
@@ -133,6 +136,7 @@ private:
     Coin m_coin;
     int m_keepAlive                 = 0;
     Mode m_mode                     = MODE_POOL;
+    uint32_t m_benchSize            = 0;
     ProxyUrl m_proxy;
     std::bitset<FLAG_MAX> m_flags   = 0;
     String m_fingerprint;
diff --git a/src/core/Miner.cpp b/src/core/Miner.cpp
index 5dab2ebbb..b3032cf14 100644
--- a/src/core/Miner.cpp
+++ b/src/core/Miner.cpp
@@ -573,8 +573,12 @@ void xmrig::Miner::onTimer(const Timer *)
     double maxHashrate          = 0.0;
     const auto healthPrintTime  = d_ptr->controller->config()->healthPrintTime();
 
+    bool stopMiner = false;
+
     for (IBackend *backend : d_ptr->backends) {
-        backend->tick(d_ptr->ticks);
+        if (!backend->tick(d_ptr->ticks)) {
+            stopMiner = true;
+        }
 
         if (healthPrintTime && d_ptr->ticks && (d_ptr->ticks % (healthPrintTime * 2)) == 0 && backend->isEnabled()) {
             backend->printHealth();
@@ -607,6 +611,10 @@ void xmrig::Miner::onTimer(const Timer *)
             setEnabled(true);
         }
     }
+
+    if (stopMiner) {
+        stop();
+    }
 }
 
 
diff --git a/src/core/config/Config_platform.h b/src/core/config/Config_platform.h
index 3c6329f5b..817da6fe8 100644
--- a/src/core/config/Config_platform.h
+++ b/src/core/config/Config_platform.h
@@ -96,6 +96,8 @@ static const option options[] = {
     { "title",                 1, nullptr, IConfig::TitleKey              },
     { "no-title",              0, nullptr, IConfig::NoTitleKey            },
     { "pause-on-battery",      0, nullptr, IConfig::PauseOnBatteryKey     },
+    { "stress",                0, nullptr, IConfig::StressKey             },
+    { "bench",                 1, nullptr, IConfig::BenchKey              },
 #   ifdef XMRIG_FEATURE_TLS
     { "tls",                   0, nullptr, IConfig::TlsKey                },
     { "tls-fingerprint",       1, nullptr, IConfig::FingerprintKey        },
diff --git a/src/core/config/usage.h b/src/core/config/usage.h
index 1b68973af..15393f9c9 100644
--- a/src/core/config/usage.h
+++ b/src/core/config/usage.h
@@ -178,6 +178,8 @@ static inline const std::string &usage()
     u += "      --no-title                disable setting console window title\n";
 #   endif
     u += "      --pause-on-battery        pause mine on battery power\n";
+    u += "      --stress                  run continuous stress test to check system stability\n";
+    u += "      --bench=N                 run benchmark in offline mode, N can be 1M or 10M\n";
 
     return u;
 }
diff --git a/src/net/Network.cpp b/src/net/Network.cpp
index 0bb741d44..18cec549e 100644
--- a/src/net/Network.cpp
+++ b/src/net/Network.cpp
@@ -74,7 +74,10 @@ xmrig::Network::Network(Controller *controller) :
     m_strategy = pools.createStrategy(m_state);
 
     if (pools.donateLevel() > 0) {
-        m_donate = new DonateStrategy(controller, this);
+        const bool bench = (pools.data().size() == 1) && (pools.data().front().mode() == xmrig::Pool::MODE_BENCHMARK);
+        if (!bench) {
+            m_donate = new DonateStrategy(controller, this);
+        }
     }
 
     m_timer = new Timer(this, kTickInterval, kTickInterval);

From 144f9c440994c735a6b68e4233d1a1783b17d0bd Mon Sep 17 00:00:00 2001
From: SChernykh <sergey.v.chernykh@gmail.com>
Date: Wed, 14 Oct 2020 21:03:21 +0200
Subject: [PATCH 2/2] Fixed compile errors in Linux

---
 src/backend/common/Workers.cpp | 2 +-
 src/base/net/stratum/Pool.cpp  | 9 +++++++--
 2 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/src/backend/common/Workers.cpp b/src/backend/common/Workers.cpp
index 6a4cf0cad..ae09f15bb 100644
--- a/src/backend/common/Workers.cpp
+++ b/src/backend/common/Workers.cpp
@@ -156,7 +156,7 @@ static void getHashrateData(xmrig::IWorker* worker, uint64_t& hashCount, uint64_
 
 
 template<>
-static void getHashrateData<xmrig::CpuLaunchData>(xmrig::IWorker* worker, uint64_t& hashCount, uint64_t&)
+void getHashrateData<xmrig::CpuLaunchData>(xmrig::IWorker* worker, uint64_t& hashCount, uint64_t&)
 {
     hashCount = worker->rawHashes();
 }
diff --git a/src/base/net/stratum/Pool.cpp b/src/base/net/stratum/Pool.cpp
index ba7b12bf4..b09559c2a 100644
--- a/src/base/net/stratum/Pool.cpp
+++ b/src/base/net/stratum/Pool.cpp
@@ -53,6 +53,11 @@
 #include "base/net/stratum/NullClient.h"
 
 
+#ifdef _MSC_VER
+#   define strcasecmp  _stricmp
+#endif
+
+
 namespace xmrig {
 
 
@@ -131,10 +136,10 @@ xmrig::Pool::Pool(const rapidjson::Value &object) :
 
     const char* benchSize = Json::getString(object, kBenchmark, nullptr);
     if (benchSize) {
-        if (stricmp(benchSize, "1M") == 0) {
+        if (strcasecmp(benchSize, "1M") == 0) {
             m_benchSize = 1000000;
         }
-        else if (stricmp(benchSize, "10M") == 0) {
+        else if (strcasecmp(benchSize, "10M") == 0) {
             m_benchSize = 10000000;
         }
     }