/* This file is part of progminer. progminer 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. progminer 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 progminer. If not, see . */ #include #if ETH_ETHASHCL #include #endif #if ETH_ETHASHCUDA #include #endif #if ETH_ETHASHCPU #include #endif namespace dev { namespace eth { Farm* Farm::m_this = nullptr; Farm::Farm(std::map& _DevicesCollection, FarmSettings _settings, CUSettings _CUSettings, CLSettings _CLSettings, CPSettings _CPSettings) : m_Settings(std::move(_settings)), m_CUSettings(std::move(_CUSettings)), m_CLSettings(std::move(_CLSettings)), m_CPSettings(std::move(_CPSettings)), m_io_strand(g_io_service), m_collectTimer(g_io_service), m_DevicesCollection(_DevicesCollection) { m_this = this; // Init HWMON if needed if (m_Settings.hwMon) { m_telemetry.hwmon = true; #if defined(__linux) bool need_sysfsh = false; #else bool need_adlh = false; #endif bool need_nvmlh = false; // Scan devices collection to identify which hw monitors to initialize for (auto it = m_DevicesCollection.begin(); it != m_DevicesCollection.end(); it++) { if (it->second.subscriptionType == DeviceSubscriptionTypeEnum::Cuda) { need_nvmlh = true; continue; } if (it->second.subscriptionType == DeviceSubscriptionTypeEnum::OpenCL) { if (it->second.clPlatformType == ClPlatformTypeEnum::Nvidia) { need_nvmlh = true; continue; } if (it->second.clPlatformType == ClPlatformTypeEnum::Amd) { #if defined(__linux) need_sysfsh = true; #else need_adlh = true; #endif continue; } } } #if defined(__linux) if (need_sysfsh) sysfsh = wrap_amdsysfs_create(); if (sysfsh) { // Build Pci identification mapping as done in miners. for (int i = 0; i < sysfsh->sysfs_gpucount; i++) { std::ostringstream oss; std::string uniqueId; oss << std::setfill('0') << std::setw(2) << std::hex << (unsigned int)sysfsh->sysfs_pci_bus_id[i] << ":" << std::setw(2) << (unsigned int)(sysfsh->sysfs_pci_device_id[i]) << ".0"; uniqueId = oss.str(); map_amdsysfs_handle[uniqueId] = i; } } #else if (need_adlh) adlh = wrap_adl_create(); if (adlh) { // Build Pci identification as done in miners. for (int i = 0; i < adlh->adl_gpucount; i++) { std::ostringstream oss; std::string uniqueId; oss << std::setfill('0') << std::setw(2) << std::hex << (unsigned int)adlh->devs[adlh->phys_logi_device_id[i]].iBusNumber << ":" << std::setw(2) << (unsigned int)(adlh->devs[adlh->phys_logi_device_id[i]].iDeviceNumber) << ".0"; uniqueId = oss.str(); map_adl_handle[uniqueId] = i; } } #endif if (need_nvmlh) nvmlh = wrap_nvml_create(); if (nvmlh) { // Build Pci identification as done in miners. for (int i = 0; i < nvmlh->nvml_gpucount; i++) { std::ostringstream oss; std::string uniqueId; oss << std::setfill('0') << std::setw(2) << std::hex << (unsigned int)nvmlh->nvml_pci_bus_id[i] << ":" << std::setw(2) << (unsigned int)(nvmlh->nvml_pci_device_id[i] >> 3) << ".0"; uniqueId = oss.str(); map_nvml_handle[uniqueId] = i; } } } // Initialize nonce_scrambler shuffle(); // Start data collector timer // It should work for the whole lifetime of Farm // regardless it's mining state m_collectTimer.expires_from_now(boost::posix_time::milliseconds(m_collectInterval)); m_collectTimer.async_wait( m_io_strand.wrap(boost::bind(&Farm::collectData, this, boost::asio::placeholders::error))); } Farm::~Farm() { // Stop data collector (before monitors !!!) m_collectTimer.cancel(); // Deinit HWMON #if defined(__linux) if (sysfsh) wrap_amdsysfs_destroy(sysfsh); #else if (adlh) wrap_adl_destroy(adlh); #endif if (nvmlh) wrap_nvml_destroy(nvmlh); // Stop mining (if needed) if (m_isMining.load(std::memory_order_relaxed)) stop(); } /** * @brief Randomizes the nonce scrambler */ void Farm::shuffle() { // Given that all nonces are equally likely to solve the problem // we could reasonably always start the nonce search ranges // at a fixed place, but that would be boring. Provide a once // per run randomized start place, without creating much overhead. random_device engine; m_nonce_scrambler = uniform_int_distribution()(engine); } void Farm::setWork(WorkPackage const& _newWp) { // Set work to each miner giving it's own starting nonce Guard l(x_minerWork); // Retrieve appropriate EpochContext if (m_currentWp.epoch != _newWp.epoch) { ethash::epoch_context _ec = ethash::get_global_epoch_context(_newWp.epoch); m_currentEc.epochNumber = _newWp.epoch; m_currentEc.lightNumItems = _ec.light_cache_num_items; m_currentEc.lightSize = ethash::get_light_cache_size(_ec.light_cache_num_items); m_currentEc.dagNumItems = ethash::calculate_full_dataset_num_items(_newWp.epoch); m_currentEc.dagSize = ethash::get_full_dataset_size(m_currentEc.dagNumItems); m_currentEc.lightCache = _ec.light_cache; for (auto const& miner : m_miners) miner->setEpoch(m_currentEc); } m_currentWp = _newWp; // Check if we need to shuffle per work (ergodicity == 2) if (m_Settings.ergodicity == 2 && m_currentWp.exSizeBytes == 0) shuffle(); uint64_t _startNonce; if (m_currentWp.exSizeBytes > 0) { // Equally divide the residual segment among miners _startNonce = m_currentWp.startNonce; m_nonce_segment_with = (unsigned int)log2(pow(2, 64 - (m_currentWp.exSizeBytes * 4)) / m_miners.size()); } else { // Get the randomly selected nonce _startNonce = m_nonce_scrambler; } for (unsigned int i = 0; i < m_miners.size(); i++) { m_currentWp.startNonce = _startNonce + ((uint64_t)i << m_nonce_segment_with); m_miners.at(i)->setWork(m_currentWp); } } /** * @brief Start a number of miners. */ bool Farm::start() { // Prevent recursion if (m_isMining.load(std::memory_order_relaxed)) return true; Guard l(x_minerWork); // Start all subscribed miners if none yet if (!m_miners.size()) { for (auto it = m_DevicesCollection.begin(); it != m_DevicesCollection.end(); it++) { TelemetryAccountType minerTelemetry; #if ETH_ETHASHCUDA if (it->second.subscriptionType == DeviceSubscriptionTypeEnum::Cuda) { minerTelemetry.prefix = "cu"; m_miners.push_back(std::shared_ptr( new CUDAMiner(m_miners.size(), m_CUSettings, it->second))); } #endif #if ETH_ETHASHCL if (it->second.subscriptionType == DeviceSubscriptionTypeEnum::OpenCL) { minerTelemetry.prefix = "cl"; m_miners.push_back(std::shared_ptr( new CLMiner(m_miners.size(), m_CLSettings, it->second))); } #endif #if ETH_ETHASHCPU if (it->second.subscriptionType == DeviceSubscriptionTypeEnum::Cpu) { minerTelemetry.prefix = "cp"; m_miners.push_back(std::shared_ptr( new CPUMiner(m_miners.size(), m_CPSettings, it->second))); } #endif if (minerTelemetry.prefix.empty()) continue; m_telemetry.miners.push_back(minerTelemetry); m_miners.back()->startWorking(); } // Initialize DAG Load mode Miner::setDagLoadInfo(m_Settings.dagLoadMode, (unsigned int)m_miners.size()); m_isMining.store(true, std::memory_order_relaxed); } else { for (auto const& miner : m_miners) miner->startWorking(); m_isMining.store(true, std::memory_order_relaxed); } return m_isMining.load(std::memory_order_relaxed); } /** * @brief Stop all mining activities. */ void Farm::stop() { // Avoid re-entering if not actually mining. // This, in fact, is also called by destructor if (isMining()) { { Guard l(x_minerWork); for (auto const& miner : m_miners) { miner->triggerStopWorking(); miner->kick_miner(); } m_miners.clear(); m_isMining.store(false, std::memory_order_relaxed); } } } /** * @brief Pauses the whole collection of miners */ void Farm::pause() { // Signal each miner to suspend mining Guard l(x_minerWork); m_paused.store(true, std::memory_order_relaxed); for (auto const& m : m_miners) m->pause(MinerPauseEnum::PauseDueToFarmPaused); } /** * @brief Returns whether or not this farm is paused for any reason */ bool Farm::paused() { return m_paused.load(std::memory_order_relaxed); } /** * @brief Resumes from a pause condition */ void Farm::resume() { // Signal each miner to resume mining // Note ! Miners may stay suspended if other reasons Guard l(x_minerWork); m_paused.store(false, std::memory_order_relaxed); for (auto const& m : m_miners) m->resume(MinerPauseEnum::PauseDueToFarmPaused); } /** * @brief Stop all mining activities and Starts them again */ void Farm::restart() { if (m_onMinerRestart) m_onMinerRestart(); } /** * @brief Stop all mining activities and Starts them again (async post) */ void Farm::restart_async() { g_io_service.post(m_io_strand.wrap(boost::bind(&Farm::restart, this))); } /** * @brief Spawn a reboot script (reboot.bat/reboot.sh) * @return false if no matching file was found */ bool Farm::reboot(const std::vector& args) { #if defined(_WIN32) const char* filename = "reboot.bat"; #else const char* filename = "reboot.sh"; #endif return spawn_file_in_bin_dir(filename, args); } /** * @brief Account solutions for miner and for farm */ void Farm::accountSolution(unsigned _minerIdx, SolutionAccountingEnum _accounting) { if (_accounting == SolutionAccountingEnum::Accepted) { m_telemetry.farm.solutions.accepted++; m_telemetry.farm.solutions.tstamp = std::chrono::steady_clock::now(); m_telemetry.miners.at(_minerIdx).solutions.accepted++; m_telemetry.miners.at(_minerIdx).solutions.tstamp = std::chrono::steady_clock::now(); return; } if (_accounting == SolutionAccountingEnum::Wasted) { m_telemetry.farm.solutions.wasted++; m_telemetry.farm.solutions.tstamp = std::chrono::steady_clock::now(); m_telemetry.miners.at(_minerIdx).solutions.wasted++; m_telemetry.miners.at(_minerIdx).solutions.tstamp = std::chrono::steady_clock::now(); return; } if (_accounting == SolutionAccountingEnum::Rejected) { m_telemetry.farm.solutions.rejected++; m_telemetry.farm.solutions.tstamp = std::chrono::steady_clock::now(); m_telemetry.miners.at(_minerIdx).solutions.rejected++; m_telemetry.miners.at(_minerIdx).solutions.tstamp = std::chrono::steady_clock::now(); return; } if (_accounting == SolutionAccountingEnum::Failed) { m_telemetry.farm.solutions.failed++; m_telemetry.farm.solutions.tstamp = std::chrono::steady_clock::now(); m_telemetry.miners.at(_minerIdx).solutions.failed++; m_telemetry.miners.at(_minerIdx).solutions.tstamp = std::chrono::steady_clock::now(); return; } } /** * @brief Gets the solutions account for the whole farm */ SolutionAccountType Farm::getSolutions() { return m_telemetry.farm.solutions; } /** * @brief Gets the solutions account for single miner */ SolutionAccountType Farm::getSolutions(unsigned _minerIdx) { try { return m_telemetry.miners.at(_minerIdx).solutions; } catch (const std::exception&) { return SolutionAccountType(); } } /** * @brief Provides the description of segments each miner is working on * @return a JsonObject */ Json::Value Farm::get_nonce_scrambler_json() { Json::Value jRes; jRes["start_nonce"] = toHex(m_nonce_scrambler, HexPrefix::Add); jRes["device_width"] = m_nonce_segment_with; jRes["device_count"] = (uint64_t)m_miners.size(); return jRes; } void Farm::setTStartTStop(unsigned tstart, unsigned tstop) { m_Settings.tempStart = tstart; m_Settings.tempStop = tstop; } void Farm::submitProof(Solution const& _s) { g_io_service.post(m_io_strand.wrap(boost::bind(&Farm::submitProofAsync, this, _s))); } void Farm::submitProofAsync(Solution const& _s) { #ifdef DEV_BUILD const bool dbuild = true; #else const bool dbuild = false; #endif if (!m_Settings.noEval || dbuild) { Result r = EthashAux::eval(_s.work.epoch, _s.work.block, _s.work.header, _s.nonce); if (r.value > _s.work.boundary) { accountSolution(_s.midx, SolutionAccountingEnum::Failed); cwarn << "GPU " << _s.midx << " gave incorrect result. Lower overclocking values if it happens frequently."; return; } if (dbuild && (_s.mixHash != r.mixHash)) cwarn << "GPU " << _s.midx << " mix missmatch"; m_onSolutionFound(Solution{_s.nonce, r.mixHash, _s.work, _s.tstamp, _s.midx}); } else m_onSolutionFound(_s); #ifdef DEV_BUILD if (g_logOptions & LOG_SUBMIT) cnote << "Submit time: " << std::chrono::duration_cast( std::chrono::steady_clock::now() - _s.tstamp) .count() << " us."; #endif } // Collects data about hashing and hardware status void Farm::collectData(const boost::system::error_code& ec) { if (ec) return; // Reset hashrate (it will accumulate from miners) float farm_hr = 0.0f; // Process miners for (auto const& miner : m_miners) { int minerIdx = miner->Index(); float hr = (miner->paused() ? 0.0f : miner->RetrieveHashRate()); farm_hr += hr; m_telemetry.miners.at(minerIdx).hashrate = hr; m_telemetry.miners.at(minerIdx).paused = miner->paused(); if (m_Settings.hwMon) { HwMonitorInfo hwInfo = miner->hwmonInfo(); unsigned int tempC = 0, fanpcnt = 0, powerW = 0; if (hwInfo.deviceType == HwMonitorInfoType::NVIDIA && nvmlh) { int devIdx = hwInfo.deviceIndex; if (devIdx == -1 && !hwInfo.devicePciId.empty()) { if (map_nvml_handle.find(hwInfo.devicePciId) != map_nvml_handle.end()) { devIdx = map_nvml_handle[hwInfo.devicePciId]; miner->setHwmonDeviceIndex(devIdx); } else { // This will prevent further tries to map miner->setHwmonDeviceIndex(-2); } } if (devIdx >= 0) { wrap_nvml_get_tempC(nvmlh, devIdx, &tempC); wrap_nvml_get_fanpcnt(nvmlh, devIdx, &fanpcnt); if (m_Settings.hwMon == 2) wrap_nvml_get_power_usage(nvmlh, devIdx, &powerW); } } else if (hwInfo.deviceType == HwMonitorInfoType::AMD) { #if defined(__linux) if (sysfsh) { int devIdx = hwInfo.deviceIndex; if (devIdx == -1 && !hwInfo.devicePciId.empty()) { if (map_amdsysfs_handle.find(hwInfo.devicePciId) != map_amdsysfs_handle.end()) { devIdx = map_amdsysfs_handle[hwInfo.devicePciId]; miner->setHwmonDeviceIndex(devIdx); } else { // This will prevent further tries to map miner->setHwmonDeviceIndex(-2); } } if (devIdx >= 0) { wrap_amdsysfs_get_tempC(sysfsh, devIdx, &tempC); wrap_amdsysfs_get_fanpcnt(sysfsh, devIdx, &fanpcnt); if (m_Settings.hwMon == 2) wrap_amdsysfs_get_power_usage(sysfsh, devIdx, &powerW); } } #else if (adlh) // Windows only for AMD { int devIdx = hwInfo.deviceIndex; if (devIdx == -1 && !hwInfo.devicePciId.empty()) { if (map_adl_handle.find(hwInfo.devicePciId) != map_adl_handle.end()) { devIdx = map_adl_handle[hwInfo.devicePciId]; miner->setHwmonDeviceIndex(devIdx); } else { // This will prevent further tries to map miner->setHwmonDeviceIndex(-2); } } if (devIdx >= 0) { wrap_adl_get_tempC(adlh, devIdx, &tempC); wrap_adl_get_fanpcnt(adlh, devIdx, &fanpcnt); if (m_Settings.hwMon == 2) wrap_adl_get_power_usage(adlh, devIdx, &powerW); } } #endif } // If temperature control has been enabled call // check threshold if (m_Settings.tempStop) { bool paused = miner->pauseTest(MinerPauseEnum::PauseDueToOverHeating); if (!paused && (tempC >= m_Settings.tempStop)) miner->pause(MinerPauseEnum::PauseDueToOverHeating); if (paused && (tempC <= m_Settings.tempStart)) miner->resume(MinerPauseEnum::PauseDueToOverHeating); } m_telemetry.miners.at(minerIdx).sensors.tempC = tempC; m_telemetry.miners.at(minerIdx).sensors.fanP = fanpcnt; m_telemetry.miners.at(minerIdx).sensors.powerW = powerW / ((double)1000.0); } m_telemetry.farm.hashrate = farm_hr; miner->TriggerHashRateUpdate(); } // Resubmit timer for another loop m_collectTimer.expires_from_now(boost::posix_time::milliseconds(m_collectInterval)); m_collectTimer.async_wait( m_io_strand.wrap(boost::bind(&Farm::collectData, this, boost::asio::placeholders::error))); } bool Farm::spawn_file_in_bin_dir(const char* filename, const std::vector& args) { std::string fn = boost::dll::program_location().parent_path().string() + "/" + // boost::filesystem::path::preferred_separator filename; try { if (!boost::filesystem::exists(fn)) return false; /* anything in the file */ if (!boost::filesystem::file_size(fn)) return false; #if defined(__linux) struct stat sb; if (stat(fn.c_str(), &sb) != 0) return false; /* just check if any exec flag is set. still execution can fail (not the uid, not in the group, selinux, ...) */ if ((sb.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) == 0) return false; #endif /* spawn it (no wait,...) - fire and forget! */ boost::process::spawn(fn, args); return true; } catch (...) { } return false; } } // namespace eth } // namespace dev