diff --git a/executor.h b/executor.h index e93a5b4..c88b1bb 100644 --- a/executor.h +++ b/executor.h @@ -1,180 +1,872 @@ -#pragma once -#include "thdq.hpp" -#include "msgstruct.h" -#include -#include -#include -#include +/* + * 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 . + * + * 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 +#include +#include +#include +#include +#include +#include "executor.h" +#include "jpsock.h" +#include "minethd.h" +#include "jconf.h" +#include "console.h" +#include "donate-level.h" +#include "webdesign.h" + +#ifdef __GNUC__ +#include +#else +#include +#endif // __GNUC__ + +#ifdef _WIN32 +#define strncasecmp _strnicmp +#endif // _WIN32 + +executor* executor::oInst = NULL; + +executor::executor() +{ + cpu_ctx = (cryptonight_ctx*)_mm_malloc(sizeof(cryptonight_ctx), 4096); +} + +void executor::push_timed_event(ex_event&& ev, size_t sec) +{ + std::unique_lock 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 lck(timed_event_mutex); + std::list::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); + } -#include "crypto/cryptonight.h" + 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)); -class jpsock; -class minethd; -class telemetry; + auto work = minethd::miner_work(); + minethd::switch_work(work); -class executor + 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) { -public: - static executor* inst() + iPoolHashes += iPoolDiff; + + size_t ln = iTopDiff.size() - 1; + if(iActualDiff > iTopDiff[ln]) { - if (oInst == nullptr) oInst = new executor; - return oInst; - }; + iTopDiff[ln] = iActualDiff; + std::sort(iTopDiff.rbegin(), iTopDiff.rend()); + } - void ex_start(bool daemon) { daemon ? ex_main() : std::thread(&executor::ex_main, this).detach(); } + vMineResults[0].increment(); +} - void get_http_report(ex_event_name ev_id, std::string& data); +jpsock* executor::pick_pool_by_id(size_t pool_id) +{ + assert(pool_id != invalid_pool_id); - inline void push_event(ex_event&& ev) { oEventQ.push(std::move(ev)); } - void push_timed_event(ex_event&& ev, size_t sec); + if(pool_id == dev_pool_id) + return dev_pool; + else + return usr_pool; +} - constexpr static size_t invalid_pool_id = 0; - constexpr static size_t dev_pool_id = 1; - constexpr static size_t usr_pool_id = 2; +void executor::on_sock_ready(size_t pool_id) +{ + jpsock* pool = pick_pool_by_id(pool_id); -private: - struct timed_event + if(pool_id == dev_pool_id) { - ex_event event; - size_t ticks_left; + if(!pool->cmd_login("42mgJMe9RAgRNWkxvvPAxjCFcekKja3DqWCgfhN8ghaZKNezbNH1ww9fiJWmRLV51Z1jTs4sxrfyEd3rAVK1pvKcCfHKGUM", "x")) + pool->disconnect(); - timed_event(ex_event&& ev, size_t ticks) : event(std::move(ev)), ticks_left(ticks) {} - }; + 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(); - cryptonight_ctx* cpu_ctx; + if(current_pool_id != dev_pool_id) + return; - // In miliseconds, has to divide a second (1000ms) into an integer number - constexpr static size_t iTickTime = 500; + printer::inst()->print_msg(L1, "Dev pool connection error. Switching work."); + on_switch_pool(usr_pool_id); + return; + } - // Dev donation time period in seconds. 100 minutes by default. - // We will divide up this period according to the config setting - constexpr static size_t iDevDonatePeriod = 100 * 60; + log_socket_error(std::move(sError)); + pool->disconnect(); + sched_reconnect(); +} - std::list lTimedEvents; - std::mutex timed_event_mutex; - thdq oEventQ; +void executor::on_pool_have_job(size_t pool_id, pool_job& oPoolJob) +{ + if(pool_id != current_pool_id) + return; - telemetry* telem; - std::vector* pvThreads; + jpsock* pool = pick_pool_by_id(pool_id); - size_t current_pool_id; + minethd::miner_work oWork(oPoolJob.sJobID, oPoolJob.bWorkBlob, + oPoolJob.iWorkLen, oPoolJob.iResumeCnt, oPoolJob.iTarget, pool_id); - jpsock* usr_pool; - jpsock* dev_pool; + minethd::switch_work(oWork); - jpsock* pick_pool_by_id(size_t pool_id); + if(pool_id == dev_pool_id) + return; - bool is_dev_time; + if(iPoolDiff != pool->get_current_diff()) + { + iPoolDiff = pool->get_current_diff(); + printer::inst()->print_msg(L2, "Difficulty changed. Now: %llu.", int_port(iPoolDiff)); + } - executor(); - static executor* oInst; + printer::inst()->print_msg(L3, "New block detected."); +} - void ex_main(); +void executor::on_miner_result(size_t pool_id, job_result& oResult) +{ + jpsock* pool = pick_pool_by_id(pool_id); - void ex_clock_thd(); - void pool_connect(jpsock* pool); + *(uint32_t*)(oResult.bWorkBlob + 39) = oResult.iNonce; - void hashrate_report(std::string& out); - void result_report(std::string& out); - void connection_report(std::string& out); + if(jconf::inst()->HaveHardwareAes()) + cryptonight_hash_ctx(oResult.bWorkBlob, oResult.iWorkLen, oResult.bResult, cpu_ctx); + else + cryptonight_hash_ctx_soft(oResult.bWorkBlob, oResult.iWorkLen, oResult.bResult, cpu_ctx); - void http_hashrate_report(std::string& out); - void http_result_report(std::string& out); - void http_connection_report(std::string& out); + bool bVerified = ((uint32_t*)oResult.bResult)[7] < oResult.iTarget; - void http_report(ex_event_name ev); - void print_report(ex_event_name ev); + if(pool_id == dev_pool_id) + { + //Ignore errors silently + if(pool->is_running() && pool->is_logged_in() && bVerified) + pool->cmd_submit(oResult.sJobID, oResult.iNonce, oResult.bResult); - std::string* pHttpString = nullptr; - std::promise httpReady; - std::mutex httpMutex; + return; + } - size_t iReconnectAttempts = 0; + if (!bVerified) + { + log_result_error("[GPU COMPUTE ERROR]"); + return; + } - struct sck_error_log + if (!pool->is_running() || !pool->is_logged_in()) { - std::chrono::system_clock::time_point time; - std::string msg; + log_result_error("[NETWORK ERROR]"); + return; + } + + using namespace std::chrono; + size_t t_start = time_point_cast(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(high_resolution_clock::now()).time_since_epoch().count() - t_start; - sck_error_log(std::string&& err) : msg(std::move(err)) + 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()) { - time = std::chrono::system_clock::now(); + 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)); } - }; - std::vector vSocketLog; + 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; - // Element zero is always the success element. - // Keep in mind that this is a tally and not a log like above - struct result_tally + jpsock* pool = pick_pool_by_id(pool_id); + if(pool_id == dev_pool_id) { - std::chrono::system_clock::time_point time; - std::string msg; - size_t count; + 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() ? "xmr-eu1.nanopool.org:14444" : "xmr-eu2.nanopool.org:14444"; + 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; - result_tally() : msg("[OK]"), count(0) + if(!pool->get_current_job(oPoolJob)) { - time = std::chrono::system_clock::now(); + pool->disconnect(); + return; } - result_tally(std::string&& err) : msg(std::move(err)), count(1) + minethd::miner_work oWork(oPoolJob.sJobID, oPoolJob.bWorkBlob, + oPoolJob.iWorkLen, oPoolJob.iResumeCnt, oPoolJob.iTarget, pool_id); + + minethd::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); + + minethd::miner_work oWork = minethd::miner_work(); + pvThreads = minethd::thread_starter(oWork); + telem = new 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 postion 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) { - time = std::chrono::system_clock::now(); + 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; + for (i = 0; i < pvThreads->size(); i++) + fHps += telem->calc_telemetry_data(10000, i); + + if(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: + 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); - void increment() + 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(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++) { - count++; - time = std::chrono::system_clock::now(); + 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); - bool compare(std::string& err) + jpsock* pool = pick_pool_by_id(dev_pool_id + 1); + + out.append("CONNECTION REPORT\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 : \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++) { - if(msg == err) - { - increment(); - return true; - } - else - return false; + snprintf(num, sizeof(num), "| %s | %-54.54s |\n", + time_format(date, sizeof(date), vSocketLog[i].time), vSocketLog[i].msg.c_str()); + out.append(num); } - }; - std::vector vMineResults; + } + 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(10000, 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); - //More result statistics - std::array iTopDiff { { } }; //Initialize to zero + size_t iGoodRes = vMineResults[0].count, iTotalRes = iGoodRes; + size_t ln = vMineResults.size(); - std::chrono::system_clock::time_point tPoolConnTime; - size_t iPoolHashes = 0; - uint64_t iPoolDiff = 0; + for(size_t i=1; i < ln; i++) + iTotalRes += vMineResults[i].count; - // Set it to 16 bit so that we can just let it grow - // Maximum realistic growth rate - 5MB / month - std::vector iPoolCallTimes; + double fGoodResPrc = 0.0; + if(iTotalRes > 0) + fGoodResPrc = 100.0 * iGoodRes / iTotalRes; - //Those stats are reset if we disconnect - inline void reset_stats() + double fAvgResTime = 0.0; + if(iPoolCallTimes.size() > 0) { - iPoolCallTimes.clear(); - tPoolConnTime = std::chrono::system_clock::now(); - iPoolHashes = 0; - iPoolDiff = 0; + using namespace std::chrono; + fAvgResTime = ((double)duration_cast(system_clock::now() - tPoolConnTime).count()) + / iPoolCallTimes.size(); } - double fHighestHps = 0.0; + 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])); - void log_socket_error(std::string&& sError); - void log_result_error(std::string&& sError); - void log_result_ok(uint64_t iActualDiff); + 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); +} + +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; + default: + assert(false); + break; + } + + httpReady.set_value(); +} + +void executor::get_http_report(ex_event_name ev_id, std::string& data) +{ + std::lock_guard lck(httpMutex); - void sched_reconnect(); + assert(pHttpString == nullptr); + assert(ev_id == EV_HTML_HASHRATE || ev_id == EV_HTML_RESULTS || ev_id == EV_HTML_CONNSTAT); - void on_sock_ready(size_t pool_id); - void on_sock_error(size_t pool_id, std::string&& sError); - void on_pool_have_job(size_t pool_id, pool_job& oPoolJob); - void on_miner_result(size_t pool_id, job_result& oResult); - void on_reconnect(size_t pool_id); - void on_switch_pool(size_t pool_id); + pHttpString = &data; + httpReady = std::promise(); + std::future ready = httpReady.get_future(); - inline size_t sec_to_ticks(size_t sec) { return sec * (1000 / iTickTime); } -}; + push_event(ex_event(ev_id)); + ready.wait(); + pHttpString = nullptr; +}