summaryrefslogtreecommitdiffstats
path: root/xmrstak/misc/executor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xmrstak/misc/executor.cpp')
-rw-r--r--xmrstak/misc/executor.cpp1005
1 files changed, 1005 insertions, 0 deletions
diff --git a/xmrstak/misc/executor.cpp b/xmrstak/misc/executor.cpp
new file mode 100644
index 0000000..64f3d40
--- /dev/null
+++ b/xmrstak/misc/executor.cpp
@@ -0,0 +1,1005 @@
+/*
+ * 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
+ * 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/>.
+ *
+ * Additional permission under GNU GPL version 3 section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining
+ * it with OpenSSL (or a modified version of that library), containing parts
+ * covered by the terms of OpenSSL License and SSLeay License, the licensors
+ * of this Program grant you additional permission to convey the resulting work.
+ *
+ */
+
+#include <thread>
+#include <string>
+#include <cmath>
+#include <algorithm>
+#include <assert.h>
+#include <time.h>
+#include "executor.h"
+#include "jpsock.h"
+
+#include "telemetry.h"
+#include "backend/miner_work.h"
+#include "backend/GlobalStates.hpp"
+#include "backend/BackendConnector.hpp"
+
+#include "jconf.h"
+#include "console.h"
+#include "donate-level.h"
+#include "webdesign.h"
+
+#ifdef _WIN32
+#define strncasecmp _strnicmp
+#endif // _WIN32
+
+executor::executor()
+{
+}
+
+void executor::push_timed_event(ex_event&& ev, size_t sec)
+{
+ std::unique_lock<std::mutex> lck(timed_event_mutex);
+ lTimedEvents.emplace_back(std::move(ev), sec_to_ticks(sec));
+}
+
+void executor::ex_clock_thd()
+{
+ size_t iSwitchPeriod = sec_to_ticks(iDevDonatePeriod);
+ size_t iDevPortion = (size_t)floor(((double)iSwitchPeriod) * fDevDonationLevel);
+
+ //No point in bothering with less than 10 sec
+ if(iDevPortion < sec_to_ticks(10))
+ iDevPortion = 0;
+
+ //Add 2 seconds to compensate for connect
+ if(iDevPortion != 0)
+ iDevPortion += sec_to_ticks(2);
+
+ while (true)
+ {
+ std::this_thread::sleep_for(std::chrono::milliseconds(size_t(iTickTime)));
+
+ push_event(ex_event(EV_PERF_TICK));
+
+ // Service timed events
+ std::unique_lock<std::mutex> lck(timed_event_mutex);
+ std::list<timed_event>::iterator ev = lTimedEvents.begin();
+ while (ev != lTimedEvents.end())
+ {
+ ev->ticks_left--;
+ if(ev->ticks_left == 0)
+ {
+ push_event(std::move(ev->event));
+ ev = lTimedEvents.erase(ev);
+ }
+ else
+ ev++;
+ }
+ lck.unlock();
+
+ if(iDevPortion == 0)
+ continue;
+
+ iSwitchPeriod--;
+ if(iSwitchPeriod == 0)
+ {
+ push_event(ex_event(EV_SWITCH_POOL, usr_pool_id));
+ iSwitchPeriod = sec_to_ticks(iDevDonatePeriod);
+ }
+ else if(iSwitchPeriod == iDevPortion)
+ {
+ push_event(ex_event(EV_SWITCH_POOL, dev_pool_id));
+ }
+ }
+}
+
+void executor::sched_reconnect()
+{
+ iReconnectAttempts++;
+ size_t iLimit = jconf::inst()->GetGiveUpLimit();
+ if(iLimit != 0 && iReconnectAttempts > iLimit)
+ {
+ printer::inst()->print_msg(L0, "Give up limit reached. Exitting.");
+ exit(0);
+ }
+
+ long long unsigned int rt = jconf::inst()->GetNetRetry();
+ printer::inst()->print_msg(L1, "Pool connection lost. Waiting %lld s before retry (attempt %llu).",
+ rt, int_port(iReconnectAttempts));
+
+ auto work = xmrstak::miner_work();
+ xmrstak::GlobalStates::inst().switch_work(work);
+
+ push_timed_event(ex_event(EV_RECONNECT, usr_pool_id), rt);
+}
+
+void executor::log_socket_error(std::string&& sError)
+{
+ vSocketLog.emplace_back(std::move(sError));
+ printer::inst()->print_msg(L1, "SOCKET ERROR - %s", vSocketLog.back().msg.c_str());
+}
+
+void executor::log_result_error(std::string&& sError)
+{
+ size_t i = 1, ln = vMineResults.size();
+ for(; i < ln; i++)
+ {
+ if(vMineResults[i].compare(sError))
+ {
+ vMineResults[i].increment();
+ break;
+ }
+ }
+
+ if(i == ln) //Not found
+ vMineResults.emplace_back(std::move(sError));
+ else
+ sError.clear();
+}
+
+void executor::log_result_ok(uint64_t iActualDiff)
+{
+ iPoolHashes += iPoolDiff;
+
+ size_t ln = iTopDiff.size() - 1;
+ if(iActualDiff > iTopDiff[ln])
+ {
+ iTopDiff[ln] = iActualDiff;
+ std::sort(iTopDiff.rbegin(), iTopDiff.rend());
+ }
+
+ vMineResults[0].increment();
+}
+
+jpsock* executor::pick_pool_by_id(size_t pool_id)
+{
+ assert(pool_id != invalid_pool_id);
+
+ if(pool_id == dev_pool_id)
+ return dev_pool;
+ else
+ return usr_pool;
+}
+
+void executor::on_sock_ready(size_t pool_id)
+{
+ jpsock* pool = pick_pool_by_id(pool_id);
+
+ if(pool_id == dev_pool_id)
+ {
+ if(!pool->cmd_login("", ""))
+ pool->disconnect();
+
+ current_pool_id = dev_pool_id;
+ printer::inst()->print_msg(L1, "Dev pool logged in. Switching work.");
+ return;
+ }
+
+ printer::inst()->print_msg(L1, "Connected. Logging in...");
+
+ if (!pool->cmd_login(jconf::inst()->GetWalletAddress(), jconf::inst()->GetPoolPwd()))
+ {
+ if(!pool->have_sock_error())
+ {
+ log_socket_error(pool->get_call_error());
+ pool->disconnect();
+ }
+ }
+ else
+ {
+ iReconnectAttempts = 0;
+ reset_stats();
+ }
+}
+
+void executor::on_sock_error(size_t pool_id, std::string&& sError)
+{
+ jpsock* pool = pick_pool_by_id(pool_id);
+
+ if(pool_id == dev_pool_id)
+ {
+ pool->disconnect();
+
+ if(current_pool_id != dev_pool_id)
+ return;
+
+ printer::inst()->print_msg(L1, "Dev pool connection error. Switching work.");
+ on_switch_pool(usr_pool_id);
+ return;
+ }
+
+ log_socket_error(std::move(sError));
+ pool->disconnect();
+ sched_reconnect();
+}
+
+void executor::on_pool_have_job(size_t pool_id, pool_job& oPoolJob)
+{
+ if(pool_id != current_pool_id)
+ return;
+
+ jpsock* pool = pick_pool_by_id(pool_id);
+
+ xmrstak::miner_work oWork(oPoolJob.sJobID, oPoolJob.bWorkBlob,
+ oPoolJob.iWorkLen, oPoolJob.iResumeCnt, oPoolJob.iTarget,
+ pool_id);
+
+ oWork.iTarget32 = oPoolJob.iTarget32;
+
+ xmrstak::GlobalStates::inst().switch_work(oWork);
+
+ if(pool_id == dev_pool_id)
+ return;
+
+ if(iPoolDiff != pool->get_current_diff())
+ {
+ iPoolDiff = pool->get_current_diff();
+ printer::inst()->print_msg(L2, "Difficulty changed. Now: %llu.", int_port(iPoolDiff));
+ }
+
+ printer::inst()->print_msg(L3, "New block detected.");
+}
+
+void executor::on_miner_result(size_t pool_id, job_result& oResult)
+{
+ jpsock* pool = pick_pool_by_id(pool_id);
+
+ if(pool_id == dev_pool_id)
+ {
+ //Ignore errors silently
+ if(pool->is_running() && pool->is_logged_in())
+ pool->cmd_submit(oResult.sJobID, oResult.iNonce, oResult.bResult);
+
+ return;
+ }
+
+ if (!pool->is_running() || !pool->is_logged_in())
+ {
+ log_result_error("[NETWORK ERROR]");
+ return;
+ }
+
+ using namespace std::chrono;
+ size_t t_start = time_point_cast<milliseconds>(high_resolution_clock::now()).time_since_epoch().count();
+ bool bResult = pool->cmd_submit(oResult.sJobID, oResult.iNonce, oResult.bResult);
+ size_t t_len = time_point_cast<milliseconds>(high_resolution_clock::now()).time_since_epoch().count() - t_start;
+
+ if(t_len > 0xFFFF)
+ t_len = 0xFFFF;
+ iPoolCallTimes.push_back((uint16_t)t_len);
+
+ if(bResult)
+ {
+ uint64_t* targets = (uint64_t*)oResult.bResult;
+ log_result_ok(jpsock::t64_to_diff(targets[3]));
+ printer::inst()->print_msg(L3, "Result accepted by the pool.");
+ }
+ else
+ {
+ if(!pool->have_sock_error())
+ {
+ printer::inst()->print_msg(L3, "Result rejected by the pool.");
+
+ std::string error = pool->get_call_error();
+
+ if(strncasecmp(error.c_str(), "Unauthenticated", 15) == 0)
+ {
+ printer::inst()->print_msg(L2, "Your miner was unable to find a share in time. Either the pool difficulty is too high, or the pool timeout is too low.");
+ pool->disconnect();
+ }
+
+ log_result_error(std::move(error));
+ }
+ else
+ log_result_error("[NETWORK ERROR]");
+ }
+}
+
+void executor::on_reconnect(size_t pool_id)
+{
+ jpsock* pool = pick_pool_by_id(pool_id);
+
+ std::string error;
+ if(pool_id == dev_pool_id)
+ return;
+
+ printer::inst()->print_msg(L1, "Connecting to pool %s ...", jconf::inst()->GetPoolAddress());
+
+ if(!pool->connect(jconf::inst()->GetPoolAddress(), error))
+ {
+ log_socket_error(std::move(error));
+ sched_reconnect();
+ }
+}
+
+void executor::on_switch_pool(size_t pool_id)
+{
+ if(pool_id == current_pool_id)
+ return;
+
+ jpsock* pool = pick_pool_by_id(pool_id);
+ if(pool_id == dev_pool_id)
+ {
+ std::string error;
+
+ // If it fails, it fails, we carry on on the usr pool
+ // as we never receive further events
+ printer::inst()->print_msg(L1, "Connecting to dev pool...");
+ const char* dev_pool_addr = jconf::inst()->GetTlsSetting() ? "donate.xmr-stak.net:6666" : "donate.xmr-stak.net:3333";
+ if(!pool->connect(dev_pool_addr, error))
+ printer::inst()->print_msg(L1, "Error connecting to dev pool. Staying with user pool.");
+ }
+ else
+ {
+ printer::inst()->print_msg(L1, "Switching back to user pool.");
+
+ current_pool_id = pool_id;
+ pool_job oPoolJob;
+
+ if(!pool->get_current_job(oPoolJob))
+ {
+ pool->disconnect();
+ return;
+ }
+
+ xmrstak::miner_work oWork(oPoolJob.sJobID, oPoolJob.bWorkBlob,
+ oPoolJob.iWorkLen, oPoolJob.iResumeCnt, oPoolJob.iTarget,
+ pool_id);
+
+ oWork.iTarget32 = oPoolJob.iTarget32;
+
+ xmrstak::GlobalStates::inst().switch_work(oWork);
+
+ if(dev_pool->is_running())
+ push_timed_event(ex_event(EV_DEV_POOL_EXIT), 5);
+ }
+}
+
+void executor::ex_main()
+{
+ assert(1000 % iTickTime == 0);
+
+ xmrstak::miner_work oWork = xmrstak::miner_work();
+
+ // \todo collect all backend threads
+ pvThreads = xmrstak::BackendConnector::thread_starter(oWork);
+
+ if(pvThreads->size()==0)
+ {
+ printer::inst()->print_msg(L1, "ERROR: No miner backend enabled.");
+ win_exit();
+ }
+
+ telem = new xmrstak::telemetry(pvThreads->size());
+
+ current_pool_id = usr_pool_id;
+ usr_pool = new jpsock(usr_pool_id, jconf::inst()->GetTlsSetting());
+ dev_pool = new jpsock(dev_pool_id, jconf::inst()->GetTlsSetting());
+
+ ex_event ev;
+ std::thread clock_thd(&executor::ex_clock_thd, this);
+
+ //This will connect us to the pool for the first time
+ push_event(ex_event(EV_RECONNECT, usr_pool_id));
+
+ // Place the default success result at position 0, it needs to
+ // be here even if our first result is a failure
+ vMineResults.emplace_back();
+
+ // If the user requested it, start the autohash printer
+ if(jconf::inst()->GetVerboseLevel() >= 4)
+ push_timed_event(ex_event(EV_HASHRATE_LOOP), jconf::inst()->GetAutohashTime());
+
+ size_t cnt = 0, i;
+ while (true)
+ {
+ ev = oEventQ.pop();
+ switch (ev.iName)
+ {
+ case EV_SOCK_READY:
+ on_sock_ready(ev.iPoolId);
+ break;
+
+ case EV_SOCK_ERROR:
+ on_sock_error(ev.iPoolId, std::move(ev.sSocketError));
+ break;
+
+ case EV_POOL_HAVE_JOB:
+ on_pool_have_job(ev.iPoolId, ev.oPoolJob);
+ break;
+
+ case EV_MINER_HAVE_RESULT:
+ on_miner_result(ev.iPoolId, ev.oJobResult);
+ break;
+
+ case EV_RECONNECT:
+ on_reconnect(ev.iPoolId);
+ break;
+
+ case EV_SWITCH_POOL:
+ on_switch_pool(ev.iPoolId);
+ break;
+
+ case EV_DEV_POOL_EXIT:
+ dev_pool->disconnect();
+ break;
+
+ case EV_PERF_TICK:
+ for (i = 0; i < pvThreads->size(); i++)
+ telem->push_perf_value(i, pvThreads->at(i)->iHashCount.load(std::memory_order_relaxed),
+ pvThreads->at(i)->iTimestamp.load(std::memory_order_relaxed));
+
+ if((cnt++ & 0xF) == 0) //Every 16 ticks
+ {
+ double fHps = 0.0;
+ double fTelem;
+ bool normal = true;
+
+ for (i = 0; i < pvThreads->size(); i++)
+ {
+ fTelem = telem->calc_telemetry_data(2500, i);
+ if(std::isnormal(fTelem))
+ {
+ fHps += fTelem;
+ }
+ else
+ {
+ normal = false;
+ break;
+ }
+ }
+
+ if(normal && fHighestHps < fHps)
+ fHighestHps = fHps;
+ }
+ break;
+
+ case EV_USR_HASHRATE:
+ case EV_USR_RESULTS:
+ case EV_USR_CONNSTAT:
+ print_report(ev.iName);
+ break;
+
+ case EV_HTML_HASHRATE:
+ case EV_HTML_RESULTS:
+ case EV_HTML_CONNSTAT:
+ case EV_HTML_JSON:
+ http_report(ev.iName);
+ break;
+
+ case EV_HASHRATE_LOOP:
+ print_report(EV_USR_HASHRATE);
+ push_timed_event(ex_event(EV_HASHRATE_LOOP), jconf::inst()->GetAutohashTime());
+ break;
+
+ case EV_INVALID_VAL:
+ default:
+ assert(false);
+ break;
+ }
+ }
+}
+
+inline const char* hps_format(double h, char* buf, size_t l)
+{
+ if(std::isnormal(h) || h == 0.0)
+ {
+ snprintf(buf, l, " %03.1f", h);
+ return buf;
+ }
+ else
+ return " (na)";
+}
+
+void executor::hashrate_report(std::string& out)
+{
+ char num[32];
+ size_t nthd = pvThreads->size();
+
+ out.reserve(256 + nthd * 64);
+
+ double fTotal[3] = { 0.0, 0.0, 0.0};
+ size_t i;
+
+ out.append("HASHRATE REPORT\n");
+ out.append("| ID | 10s | 60s | 15m |");
+ if(nthd != 1)
+ out.append(" ID | 10s | 60s | 15m |\n");
+ else
+ out.append(1, '\n');
+
+ for (i = 0; i < nthd; i++)
+ {
+ double fHps[3];
+
+ fHps[0] = telem->calc_telemetry_data(10000, i);
+ fHps[1] = telem->calc_telemetry_data(60000, i);
+ fHps[2] = telem->calc_telemetry_data(900000, i);
+
+ snprintf(num, sizeof(num), "| %2u |", (unsigned int)i);
+ out.append(num);
+ out.append(hps_format(fHps[0], num, sizeof(num))).append(" |");
+ out.append(hps_format(fHps[1], num, sizeof(num))).append(" |");
+ out.append(hps_format(fHps[2], num, sizeof(num))).append(1, ' ');
+
+ fTotal[0] += fHps[0];
+ fTotal[1] += fHps[1];
+ fTotal[2] += fHps[2];
+
+ if((i & 0x1) == 1) //Odd i's
+ out.append("|\n");
+ }
+
+ if((i & 0x1) == 1) //We had odd number of threads
+ out.append("|\n");
+
+ if(nthd != 1)
+ out.append("-----------------------------------------------------\n");
+ else
+ out.append("---------------------------\n");
+
+ out.append("Totals: ");
+ out.append(hps_format(fTotal[0], num, sizeof(num)));
+ out.append(hps_format(fTotal[1], num, sizeof(num)));
+ out.append(hps_format(fTotal[2], num, sizeof(num)));
+ out.append(" H/s\nHighest: ");
+ out.append(hps_format(fHighestHps, num, sizeof(num)));
+ out.append(" H/s\n");
+}
+
+char* time_format(char* buf, size_t len, std::chrono::system_clock::time_point time)
+{
+ time_t ctime = std::chrono::system_clock::to_time_t(time);
+ tm stime;
+
+ /*
+ * Oh for god's sake... this feels like we are back to the 90's...
+ * and don't get me started on lack strcpy_s because NIH - use non-standard strlcpy...
+ * And of course C++ implements unsafe version because... reasons
+ */
+
+#ifdef _WIN32
+ localtime_s(&stime, &ctime);
+#else
+ localtime_r(&ctime, &stime);
+#endif // __WIN32
+ strftime(buf, len, "%F %T", &stime);
+
+ return buf;
+}
+
+void executor::result_report(std::string& out)
+{
+ char num[128];
+ char date[32];
+
+ out.reserve(1024);
+
+ size_t iGoodRes = vMineResults[0].count, iTotalRes = iGoodRes;
+ size_t ln = vMineResults.size();
+
+ for(size_t i=1; i < ln; i++)
+ iTotalRes += vMineResults[i].count;
+
+ out.append("RESULT REPORT\n");
+ if(iTotalRes == 0)
+ {
+ out.append("You haven't found any results yet.\n");
+ return;
+ }
+
+ double dConnSec;
+ {
+ using namespace std::chrono;
+ dConnSec = (double)duration_cast<seconds>(system_clock::now() - tPoolConnTime).count();
+ }
+
+ snprintf(num, sizeof(num), " (%.1f %%)\n", 100.0 * iGoodRes / iTotalRes);
+
+ out.append("Difficulty : ").append(std::to_string(iPoolDiff)).append(1, '\n');
+ out.append("Good results : ").append(std::to_string(iGoodRes)).append(" / ").
+ append(std::to_string(iTotalRes)).append(num);
+
+ if(iPoolCallTimes.size() != 0)
+ {
+ // Here we use iPoolCallTimes since it also gets reset when we disconnect
+ snprintf(num, sizeof(num), "%.1f sec\n", dConnSec / iPoolCallTimes.size());
+ out.append("Avg result time : ").append(num);
+ }
+ out.append("Pool-side hashes : ").append(std::to_string(iPoolHashes)).append(2, '\n');
+ out.append("Top 10 best results found:\n");
+
+ for(size_t i=0; i < 10; i += 2)
+ {
+ snprintf(num, sizeof(num), "| %2llu | %16llu | %2llu | %16llu |\n",
+ int_port(i), int_port(iTopDiff[i]), int_port(i+1), int_port(iTopDiff[i+1]));
+ out.append(num);
+ }
+
+ out.append("\nError details:\n");
+ if(ln > 1)
+ {
+ out.append("| Count | Error text | Last seen |\n");
+ for(size_t i=1; i < ln; i++)
+ {
+ snprintf(num, sizeof(num), "| %5llu | %-32.32s | %s |\n", int_port(vMineResults[i].count),
+ vMineResults[i].msg.c_str(), time_format(date, sizeof(date), vMineResults[i].time));
+ out.append(num);
+ }
+ }
+ else
+ out.append("Yay! No errors.\n");
+}
+
+void executor::connection_report(std::string& out)
+{
+ char num[128];
+ char date[32];
+
+ out.reserve(512);
+
+ jpsock* pool = pick_pool_by_id(dev_pool_id + 1);
+
+ out.append("CONNECTION REPORT\n");
+ out.append("Pool address : ").append(jconf::inst()->GetPoolAddress()).append(1, '\n');
+ if (pool->is_running() && pool->is_logged_in())
+ out.append("Connected since : ").append(time_format(date, sizeof(date), tPoolConnTime)).append(1, '\n');
+ else
+ out.append("Connected since : <not connected>\n");
+
+ size_t n_calls = iPoolCallTimes.size();
+ if (n_calls > 1)
+ {
+ //Not-really-but-good-enough median
+ std::nth_element(iPoolCallTimes.begin(), iPoolCallTimes.begin() + n_calls/2, iPoolCallTimes.end());
+ out.append("Pool ping time : ").append(std::to_string(iPoolCallTimes[n_calls/2])).append(" ms\n");
+ }
+ else
+ out.append("Pool ping time : (n/a)\n");
+
+ out.append("\nNetwork error log:\n");
+ size_t ln = vSocketLog.size();
+ if(ln > 0)
+ {
+ out.append("| Date | Error text |\n");
+ for(size_t i=0; i < ln; i++)
+ {
+ snprintf(num, sizeof(num), "| %s | %-54.54s |\n",
+ time_format(date, sizeof(date), vSocketLog[i].time), vSocketLog[i].msg.c_str());
+ out.append(num);
+ }
+ }
+ else
+ out.append("Yay! No errors.\n");
+}
+
+void executor::print_report(ex_event_name ev)
+{
+ std::string out;
+ switch(ev)
+ {
+ case EV_USR_HASHRATE:
+ hashrate_report(out);
+ break;
+
+ case EV_USR_RESULTS:
+ result_report(out);
+ break;
+
+ case EV_USR_CONNSTAT:
+ connection_report(out);
+ break;
+ default:
+ assert(false);
+ break;
+ }
+
+ printer::inst()->print_str(out.c_str());
+}
+
+void executor::http_hashrate_report(std::string& out)
+{
+ char num_a[32], num_b[32], num_c[32], num_d[32];
+ char buffer[4096];
+ size_t nthd = pvThreads->size();
+
+ out.reserve(4096);
+
+ snprintf(buffer, sizeof(buffer), sHtmlCommonHeader, "Hashrate Report", "Hashrate Report");
+ out.append(buffer);
+
+ snprintf(buffer, sizeof(buffer), sHtmlHashrateBodyHigh, (unsigned int)nthd + 3);
+ out.append(buffer);
+
+ double fTotal[3] = { 0.0, 0.0, 0.0};
+ for(size_t i=0; i < nthd; i++)
+ {
+ double fHps[3];
+
+ fHps[0] = telem->calc_telemetry_data(2500, i);
+ fHps[1] = telem->calc_telemetry_data(60000, i);
+ fHps[2] = telem->calc_telemetry_data(900000, i);
+
+ num_a[0] = num_b[0] = num_c[0] ='\0';
+ hps_format(fHps[0], num_a, sizeof(num_a));
+ hps_format(fHps[1], num_b, sizeof(num_b));
+ hps_format(fHps[2], num_c, sizeof(num_c));
+
+ fTotal[0] += fHps[0];
+ fTotal[1] += fHps[1];
+ fTotal[2] += fHps[2];
+
+ snprintf(buffer, sizeof(buffer), sHtmlHashrateTableRow, (unsigned int)i, num_a, num_b, num_c);
+ out.append(buffer);
+ }
+
+ num_a[0] = num_b[0] = num_c[0] = num_d[0] ='\0';
+ hps_format(fTotal[0], num_a, sizeof(num_a));
+ hps_format(fTotal[1], num_b, sizeof(num_b));
+ hps_format(fTotal[2], num_c, sizeof(num_c));
+ hps_format(fHighestHps, num_d, sizeof(num_d));
+
+ snprintf(buffer, sizeof(buffer), sHtmlHashrateBodyLow, num_a, num_b, num_c, num_d);
+ out.append(buffer);
+}
+
+void executor::http_result_report(std::string& out)
+{
+ char date[128];
+ char buffer[4096];
+
+ out.reserve(4096);
+
+ snprintf(buffer, sizeof(buffer), sHtmlCommonHeader, "Result Report", "Result Report");
+ out.append(buffer);
+
+ size_t iGoodRes = vMineResults[0].count, iTotalRes = iGoodRes;
+ size_t ln = vMineResults.size();
+
+ for(size_t i=1; i < ln; i++)
+ iTotalRes += vMineResults[i].count;
+
+ double fGoodResPrc = 0.0;
+ if(iTotalRes > 0)
+ fGoodResPrc = 100.0 * iGoodRes / iTotalRes;
+
+ double fAvgResTime = 0.0;
+ if(iPoolCallTimes.size() > 0)
+ {
+ using namespace std::chrono;
+ fAvgResTime = ((double)duration_cast<seconds>(system_clock::now() - tPoolConnTime).count())
+ / iPoolCallTimes.size();
+ }
+
+ snprintf(buffer, sizeof(buffer), sHtmlResultBodyHigh,
+ iPoolDiff, iGoodRes, iTotalRes, fGoodResPrc, fAvgResTime, iPoolHashes,
+ int_port(iTopDiff[0]), int_port(iTopDiff[1]), int_port(iTopDiff[2]), int_port(iTopDiff[3]),
+ int_port(iTopDiff[4]), int_port(iTopDiff[5]), int_port(iTopDiff[6]), int_port(iTopDiff[7]),
+ int_port(iTopDiff[8]), int_port(iTopDiff[9]));
+
+ out.append(buffer);
+
+ for(size_t i=1; i < vMineResults.size(); i++)
+ {
+ snprintf(buffer, sizeof(buffer), sHtmlResultTableRow, vMineResults[i].msg.c_str(),
+ int_port(vMineResults[i].count), time_format(date, sizeof(date), vMineResults[i].time));
+ out.append(buffer);
+ }
+
+ out.append(sHtmlResultBodyLow);
+}
+
+void executor::http_connection_report(std::string& out)
+{
+ char date[128];
+ char buffer[4096];
+
+ out.reserve(4096);
+
+ snprintf(buffer, sizeof(buffer), sHtmlCommonHeader, "Connection Report", "Connection Report");
+ out.append(buffer);
+
+ jpsock* pool = pick_pool_by_id(dev_pool_id + 1);
+ const char* cdate = "not connected";
+ if (pool->is_running() && pool->is_logged_in())
+ cdate = time_format(date, sizeof(date), tPoolConnTime);
+
+ size_t n_calls = iPoolCallTimes.size();
+ unsigned int ping_time = 0;
+ if (n_calls > 1)
+ {
+ //Not-really-but-good-enough median
+ std::nth_element(iPoolCallTimes.begin(), iPoolCallTimes.begin() + n_calls/2, iPoolCallTimes.end());
+ ping_time = iPoolCallTimes[n_calls/2];
+ }
+
+ snprintf(buffer, sizeof(buffer), sHtmlConnectionBodyHigh,
+ jconf::inst()->GetPoolAddress(),
+ cdate, ping_time);
+ out.append(buffer);
+
+
+ for(size_t i=0; i < vSocketLog.size(); i++)
+ {
+ snprintf(buffer, sizeof(buffer), sHtmlConnectionTableRow,
+ time_format(date, sizeof(date), vSocketLog[i].time), vSocketLog[i].msg.c_str());
+ out.append(buffer);
+ }
+
+ out.append(sHtmlConnectionBodyLow);
+}
+
+inline const char* hps_format_json(double h, char* buf, size_t l)
+{
+ if(std::isnormal(h) || h == 0.0)
+ {
+ snprintf(buf, l, "%.1f", h);
+ return buf;
+ }
+ else
+ return "null";
+}
+
+void executor::http_json_report(std::string& out)
+{
+ const char *a, *b, *c;
+ char num_a[32], num_b[32], num_c[32];
+ char hr_buffer[64];
+ std::string hr_thds, res_error, cn_error;
+
+ size_t nthd = pvThreads->size();
+ double fTotal[3] = { 0.0, 0.0, 0.0};
+ hr_thds.reserve(nthd * 32);
+
+ for(size_t i=0; i < nthd; i++)
+ {
+ if(i != 0) hr_thds.append(1, ',');
+
+ double fHps[3];
+ fHps[0] = telem->calc_telemetry_data(2500, i);
+ fHps[1] = telem->calc_telemetry_data(60000, i);
+ fHps[2] = telem->calc_telemetry_data(900000, i);
+
+ fTotal[0] += fHps[0];
+ fTotal[1] += fHps[1];
+ fTotal[2] += fHps[2];
+
+ a = hps_format_json(fHps[0], num_a, sizeof(num_a));
+ b = hps_format_json(fHps[1], num_b, sizeof(num_b));
+ c = hps_format_json(fHps[2], num_c, sizeof(num_c));
+ snprintf(hr_buffer, sizeof(hr_buffer), sJsonApiThdHashrate, a, b, c);
+ hr_thds.append(hr_buffer);
+ }
+
+ a = hps_format_json(fTotal[0], num_a, sizeof(num_a));
+ b = hps_format_json(fTotal[1], num_b, sizeof(num_b));
+ c = hps_format_json(fTotal[2], num_c, sizeof(num_c));
+ snprintf(hr_buffer, sizeof(hr_buffer), sJsonApiThdHashrate, a, b, c);
+
+ a = hps_format_json(fHighestHps, num_a, sizeof(num_a));
+
+ size_t iGoodRes = vMineResults[0].count, iTotalRes = iGoodRes;
+ size_t ln = vMineResults.size();
+
+ for(size_t i=1; i < ln; i++)
+ iTotalRes += vMineResults[i].count;
+
+ jpsock* pool = pick_pool_by_id(dev_pool_id + 1);
+
+ size_t iConnSec = 0;
+ if(pool->is_running() && pool->is_logged_in())
+ {
+ using namespace std::chrono;
+ iConnSec = duration_cast<seconds>(system_clock::now() - tPoolConnTime).count();
+ }
+
+ double fAvgResTime = 0.0;
+ if(iPoolCallTimes.size() > 0)
+ fAvgResTime = double(iConnSec) / iPoolCallTimes.size();
+
+ res_error.reserve((vMineResults.size() - 1) * 128);
+ char buffer[256];
+ for(size_t i=1; i < vMineResults.size(); i++)
+ {
+ using namespace std::chrono;
+ if(i != 1) res_error.append(1, ',');
+
+ snprintf(buffer, sizeof(buffer), sJsonApiResultError, int_port(vMineResults[i].count),
+ int_port(duration_cast<seconds>(vMineResults[i].time.time_since_epoch()).count()),
+ vMineResults[i].msg.c_str());
+ res_error.append(buffer);
+ }
+
+ size_t n_calls = iPoolCallTimes.size();
+ size_t iPoolPing = 0;
+ if (n_calls > 1)
+ {
+ //Not-really-but-good-enough median
+ std::nth_element(iPoolCallTimes.begin(), iPoolCallTimes.begin() + n_calls/2, iPoolCallTimes.end());
+ iPoolPing = iPoolCallTimes[n_calls/2];
+ }
+
+ cn_error.reserve(vSocketLog.size() * 128);
+ for(size_t i=0; i < vSocketLog.size(); i++)
+ {
+ using namespace std::chrono;
+ if(i != 0) cn_error.append(1, ',');
+
+ snprintf(buffer, sizeof(buffer), sJsonApiConnectionError,
+ int_port(duration_cast<seconds>(vMineResults[i].time.time_since_epoch()).count()),
+ vSocketLog[i].msg.c_str());
+ cn_error.append(buffer);
+ }
+
+ size_t bb_size = 1024 + hr_thds.size() + res_error.size() + cn_error.size();
+ std::unique_ptr<char[]> bigbuf( new char[ bb_size ] );
+
+ int bb_len = snprintf(bigbuf.get(), bb_size, sJsonApiFormat,
+ hr_thds.c_str(), hr_buffer, a,
+ int_port(iPoolDiff), int_port(iGoodRes), int_port(iTotalRes), fAvgResTime, int_port(iPoolHashes),
+ int_port(iTopDiff[0]), int_port(iTopDiff[1]), int_port(iTopDiff[2]), int_port(iTopDiff[3]), int_port(iTopDiff[4]),
+ int_port(iTopDiff[5]), int_port(iTopDiff[6]), int_port(iTopDiff[7]), int_port(iTopDiff[8]), int_port(iTopDiff[9]),
+ res_error.c_str(), jconf::inst()->GetPoolAddress(), int_port(iConnSec), int_port(iPoolPing), cn_error.c_str());
+
+ out = std::string(bigbuf.get(), bigbuf.get() + bb_len);
+}
+
+void executor::http_report(ex_event_name ev)
+{
+ assert(pHttpString != nullptr);
+
+ switch(ev)
+ {
+ case EV_HTML_HASHRATE:
+ http_hashrate_report(*pHttpString);
+ break;
+
+ case EV_HTML_RESULTS:
+ http_result_report(*pHttpString);
+ break;
+
+ case EV_HTML_CONNSTAT:
+ http_connection_report(*pHttpString);
+ break;
+
+ case EV_HTML_JSON:
+ http_json_report(*pHttpString);
+ break;
+
+ default:
+ assert(false);
+ break;
+ }
+
+ httpReady.set_value();
+}
+
+void executor::get_http_report(ex_event_name ev_id, std::string& data)
+{
+ std::lock_guard<std::mutex> lck(httpMutex);
+
+ assert(pHttpString == nullptr);
+ assert(ev_id == EV_HTML_HASHRATE || ev_id == EV_HTML_RESULTS
+ || ev_id == EV_HTML_CONNSTAT || ev_id == EV_HTML_JSON);
+
+ pHttpString = &data;
+ httpReady = std::promise<void>();
+ std::future<void> ready = httpReady.get_future();
+
+ push_event(ex_event(ev_id));
+
+ ready.wait();
+ pHttpString = nullptr;
+}
OpenPOWER on IntegriCloud